blob: d38ad068433a5ea598399c5bae81e6aeea078ae5 [file] [log] [blame]
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +08001# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Tom Wai-Hong Tam4c8022f2014-07-17 09:12:08 +08005import Image
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +08006import httplib
7import logging
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +08008import os
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +08009import socket
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +080010import tempfile
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +080011import xmlrpclib
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080012
13from autotest_lib.client.bin import utils
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +080014from autotest_lib.client.common_lib.cros import retry
Wai-Hong Tam44cb5452014-03-18 16:14:24 -070015from autotest_lib.client.cros import constants
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080016from autotest_lib.server import autotest
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +080017from autotest_lib.server.cros.chameleon import image_generator
18
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080019
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +080020
21class DisplayInfo(object):
22 """The class match displayInfo object from chrome.system.display API.
23 """
24
25 class Bounds(object):
26 """The class match Bounds object from chrome.system.display API.
27
28 @param left: The x-coordinate of the upper-left corner.
29 @param top: The y-coordinate of the upper-left corner.
30 @param width: The width of the display in pixels.
31 @param height: The height of the display in pixels.
32 """
33 def __init__(self, d):
34 self.left = d['left'];
35 self.top = d['top'];
36 self.width = d['width'];
37 self.height = d['height'];
38
39
40 class Insets(object):
41 """The class match Insets object from chrome.system.display API.
42
43 @param left: The x-axis distance from the left bound.
44 @param left: The y-axis distance from the top bound.
45 @param left: The x-axis distance from the right bound.
46 @param left: The y-axis distance from the bottom bound.
47 """
48
49 def __init__(self, d):
50 self.left = d['left'];
51 self.top = d['top'];
52 self.right = d['right'];
53 self.bottom = d['bottom'];
54
55
56 def __init__(self, d):
57 self.display_id = d['id'];
58 self.name = d['name'];
59 self.mirroring_source_id = d['mirroringSourceId'];
60 self.is_primary = d['isPrimary'];
61 self.is_internal = d['isInternal'];
62 self.is_enabled = d['isEnabled'];
63 self.dpi_x = d['dpiX'];
64 self.dpi_y = d['dpiY'];
65 self.rotation = d['rotation'];
66 self.bounds = self.Bounds(d['bounds']);
67 self.overscan = self.Insets(d['overscan']);
68 self.work_area = self.Bounds(d['workArea']);
69
70
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080071class DisplayClient(object):
72 """DisplayClient is a layer to control display logic over a remote DUT.
73
74 The Autotest host object representing the remote DUT, passed to this
75 class on initialization, can be accessed from its _client property.
76
77 """
78
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080079 X_ENV_VARIABLES = 'DISPLAY=:0.0 XAUTHORITY=/home/chronos/.Xauthority'
80 XMLRPC_CONNECT_TIMEOUT = 30
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +080081 XMLRPC_RETRY_TIMEOUT = 180
82 XMLRPC_RETRY_DELAY = 10
Tom Wai-Hong Tam23362632014-04-09 16:38:37 +080083 HTTP_PORT = 8000
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +080084 DEST_TMP_DIR = '/tmp'
85 DEST_IMAGE_FILENAME = 'calibration.svg'
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080086
87
88 def __init__(self, host):
89 """Construct a DisplayClient.
90
91 @param host: Host object representing a remote host.
92 """
93 self._client = host
94 self._display_xmlrpc_client = None
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +080095 self._image_generator = image_generator.ImageGenerator()
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080096
97
Wai-Hong Tam44cb5452014-03-18 16:14:24 -070098 def initialize(self):
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080099 """Initializes some required servers, like HTTP daemon, RPC connection.
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800100 """
101 # Make sure the client library is on the device so that the proxy code
102 # is there when we try to call it.
103 client_at = autotest.Autotest(self._client)
104 client_at.install()
Tom Wai-Hong Tamfe395092014-02-12 20:16:22 +0800105 self.connect()
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800106
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800107
Tom Wai-Hong Tamfe395092014-02-12 20:16:22 +0800108 def connect(self):
109 """Connects the XML-RPC proxy on the client."""
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800110 @retry.retry((socket.error,
111 xmlrpclib.ProtocolError,
112 httplib.BadStatusLine),
113 timeout_min=self.XMLRPC_RETRY_TIMEOUT / 60.0,
114 delay_sec=self.XMLRPC_RETRY_DELAY)
115 def connect_with_retries():
116 """Connects the XML-RPC proxy with retries."""
117 self._display_xmlrpc_client = self._client.xmlrpc_connect(
118 constants.DISPLAY_TESTING_XMLRPC_SERVER_COMMAND,
119 constants.DISPLAY_TESTING_XMLRPC_SERVER_PORT,
120 command_name=(
121 constants.DISPLAY_TESTING_XMLRPC_SERVER_CLEANUP_PATTERN
122 ),
123 ready_test_name=(
124 constants.DISPLAY_TESTING_XMLRPC_SERVER_READY_METHOD),
125 timeout_seconds=self.XMLRPC_CONNECT_TIMEOUT)
126
127 logging.info('Setup the display_client RPC server, with retries...')
128 connect_with_retries()
Tom Wai-Hong Tamfe395092014-02-12 20:16:22 +0800129
130
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800131 def cleanup(self):
132 """Cleans up."""
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800133 self._client.rpc_disconnect(
134 constants.DISPLAY_TESTING_XMLRPC_SERVER_PORT)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800135
136
137 def __del__(self):
138 """Destructor of DisplayClient."""
139 self.cleanup()
140
141
142 def get_connector_name(self):
143 """Gets the name of the external output connector.
144
145 @return The external output connector name as a string.
146 """
147 return self._display_xmlrpc_client.get_ext_connector_name()
148
149
150 def load_calibration_image(self, resolution):
151 """Load a full screen calibration image from the HTTP server.
152
153 @param resolution: A tuple (width, height) of resolution.
154 """
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +0800155 with tempfile.NamedTemporaryFile() as f:
156 self._image_generator.generate_image(
157 resolution[0], resolution[1], f.name)
158 os.chmod(f.name, 0644)
159 self._client.send_file(
160 f.name,
161 os.path.join(self.DEST_TMP_DIR, self.DEST_IMAGE_FILENAME))
162
163 page_url = 'file://%s/%s' % (self.DEST_TMP_DIR,
164 self.DEST_IMAGE_FILENAME)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800165 self._display_xmlrpc_client.load_url(page_url)
166
167
168 def close_tab(self, index=-1):
169 """Closes the tab of the given index.
170
171 @param index: The tab index to close. Defaults to the last tab.
172 """
173 return self._display_xmlrpc_client.close_tab(index)
174
175
176 def set_mirrored(self, is_mirrored):
177 """Sets mirrored mode.
178
179 @param is_mirrored: True or False to indicate mirrored state.
180 """
181 return self._display_xmlrpc_client.set_mirrored(is_mirrored)
182
183
Tom Wai-Hong Tam328dbeb2014-02-14 11:20:19 +0800184 def suspend_resume(self, suspend_time=10):
185 """Suspends the DUT for a given time in second.
186
187 @param suspend_time: Suspend time in second, default: 10s.
188 """
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800189 # TODO(waihong): Use other general API instead of this RPC.
Tom Wai-Hong Tam328dbeb2014-02-14 11:20:19 +0800190 return self._display_xmlrpc_client.suspend_resume(suspend_time)
191
192
193 def suspend_resume_bg(self, suspend_time=10):
194 """Suspends the DUT for a given time in second in the background.
195
196 @param suspend_time: Suspend time in second, default: 10s.
197 """
198 # TODO(waihong): Use other general API instead of this RPC.
199 return self._display_xmlrpc_client.suspend_resume_bg(suspend_time)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800200
201
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800202 def reconnect_output_and_wait(self, reconnect=True,
203 expected_display_count=2):
204 """Reconnects output and waits it available.
205
206 @param reconnect: True to perform a re-connection from the DUT; False
207 otherwise.
208 @param expected_display_count:
209 number of displays expected to be connected.
210 """
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800211 output = self.get_connector_name()
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800212 if reconnect:
213 self._display_xmlrpc_client.reconnect_output(output)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800214 self._display_xmlrpc_client.wait_output_connected(output)
215 utils.wait_for_value(lambda: (
216 len(self._display_xmlrpc_client.get_display_info())),
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800217 expected_value=expected_display_count)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800218
219
Hung-ying Tyan67541652014-03-12 11:44:46 +0800220 def hide_cursor(self):
221 """Hides mouse cursor by sending a keystroke."""
Tom Wai-Hong Tambd22f8f2014-06-03 03:05:56 +0800222 self._display_xmlrpc_client.press_key('Up')
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800223
224
Tom Wai-Hong Tam4c8022f2014-07-17 09:12:08 +0800225 def capture_external_screen(self):
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800226 """Captures the external screen framebuffer.
227
Tom Wai-Hong Tam4c8022f2014-07-17 09:12:08 +0800228 @return: An Image object.
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800229 """
230 output = self.get_connector_name()
231 fb_w, fb_h, fb_x, fb_y = (
232 self._display_xmlrpc_client.get_resolution(output))
Tom Wai-Hong Tam4c8022f2014-07-17 09:12:08 +0800233 with tempfile.NamedTemporaryFile(suffix='.png') as f:
234 basename = os.path.basename(f.name)
235 remote_path = os.path.join('/tmp', basename)
236 command = ('%s import -window root -depth 8 -crop %dx%d+%d+%d %s' %
237 (self.X_ENV_VARIABLES, fb_w, fb_h, fb_x, fb_y,
238 remote_path))
239 self._client.run(command)
240 self._client.get_file(remote_path, f.name)
241 return Image.open(f.name)
Tom Wai-Hong Tamf6bb17f2014-04-24 13:36:49 +0800242
243
244 def get_resolution(self):
245 """Gets the external resolution on framebuffer.
246
247 @return The resolution tuple (width, height)
248 """
249 output = self.get_connector_name()
250 width, height, _, _ = self._display_xmlrpc_client.get_resolution(output)
251 return (width, height)
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800252
253
254 def set_resolution(self, display_index, width, height):
255 """Sets the resolution on the specified display.
256
257 @param display_index: index of the display to set resolutions for; 0 is
258 the internal one for chromebooks.
259 @param width: width of the resolution
260 @param height: height of the resolution
261
262 @return: True if the new resolution meets the set value else False.
263 """
264 return self._display_xmlrpc_client.set_resolution(
265 display_index, width, height)
266
267
268 def get_display_info(self):
269 """Gets the information of all the displays that are connected to the
270 DUT.
271
272 @return: array of object DisplayInfo for display informtion
273 """
274 return map(DisplayInfo, self._display_xmlrpc_client.get_display_info())
275
276
277 def get_available_resolutions(self, display_index):
278 """Gets the available list of the specified display.
279
280 @param display_index: index of the display to get resolutions from; the
281 index is from the DisplayInfo array obtained by get_display_info().
282 For Chromebooks, index 0 indicates the internal display.
283
284 @return: array of available resolutions tuple (width, height)
285 """
286 return [(resolution['width'], resolution['height']) for resolution in
287 self._display_xmlrpc_client.get_available_resolutions(
288 display_index)]