blob: 163766f3777b2244f16a111ffed778f180948af7 [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 Tam0d836592014-04-23 11:27:40 +08005import httplib
6import logging
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +08007import os
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +08008import socket
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +08009import tempfile
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +080010import xmlrpclib
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080011
J. Richard Barnette428b3442014-07-22 11:38:55 -070012from PIL import Image
13
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080014from autotest_lib.client.bin import utils
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +080015from autotest_lib.client.common_lib.cros import retry
Wai-Hong Tam44cb5452014-03-18 16:14:24 -070016from autotest_lib.client.cros import constants
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080017from autotest_lib.server import autotest
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +080018from autotest_lib.server.cros.chameleon import image_generator
19
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080020
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +080021
22class DisplayInfo(object):
23 """The class match displayInfo object from chrome.system.display API.
24 """
25
26 class Bounds(object):
27 """The class match Bounds object from chrome.system.display API.
28
29 @param left: The x-coordinate of the upper-left corner.
30 @param top: The y-coordinate of the upper-left corner.
31 @param width: The width of the display in pixels.
32 @param height: The height of the display in pixels.
33 """
34 def __init__(self, d):
35 self.left = d['left'];
36 self.top = d['top'];
37 self.width = d['width'];
38 self.height = d['height'];
39
40
41 class Insets(object):
42 """The class match Insets object from chrome.system.display API.
43
44 @param left: The x-axis distance from the left bound.
45 @param left: The y-axis distance from the top bound.
46 @param left: The x-axis distance from the right bound.
47 @param left: The y-axis distance from the bottom bound.
48 """
49
50 def __init__(self, d):
51 self.left = d['left'];
52 self.top = d['top'];
53 self.right = d['right'];
54 self.bottom = d['bottom'];
55
56
57 def __init__(self, d):
58 self.display_id = d['id'];
59 self.name = d['name'];
60 self.mirroring_source_id = d['mirroringSourceId'];
61 self.is_primary = d['isPrimary'];
62 self.is_internal = d['isInternal'];
63 self.is_enabled = d['isEnabled'];
64 self.dpi_x = d['dpiX'];
65 self.dpi_y = d['dpiY'];
66 self.rotation = d['rotation'];
67 self.bounds = self.Bounds(d['bounds']);
68 self.overscan = self.Insets(d['overscan']);
69 self.work_area = self.Bounds(d['workArea']);
70
71
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080072class DisplayClient(object):
73 """DisplayClient is a layer to control display logic over a remote DUT.
74
75 The Autotest host object representing the remote DUT, passed to this
76 class on initialization, can be accessed from its _client property.
77
78 """
79
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080080 X_ENV_VARIABLES = 'DISPLAY=:0.0 XAUTHORITY=/home/chronos/.Xauthority'
Tom Wai-Hong Tama2f21cb2014-07-25 08:00:49 +080081 XMLRPC_CONNECT_TIMEOUT = 60
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +080082 XMLRPC_RETRY_TIMEOUT = 180
83 XMLRPC_RETRY_DELAY = 10
Tom Wai-Hong Tam23362632014-04-09 16:38:37 +080084 HTTP_PORT = 8000
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +080085 DEST_TMP_DIR = '/tmp'
86 DEST_IMAGE_FILENAME = 'calibration.svg'
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080087
88
89 def __init__(self, host):
90 """Construct a DisplayClient.
91
92 @param host: Host object representing a remote host.
93 """
94 self._client = host
95 self._display_xmlrpc_client = None
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +080096 self._image_generator = image_generator.ImageGenerator()
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +080097
98
Wai-Hong Tam44cb5452014-03-18 16:14:24 -070099 def initialize(self):
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800100 """Initializes some required servers, like HTTP daemon, RPC connection.
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800101 """
102 # Make sure the client library is on the device so that the proxy code
103 # is there when we try to call it.
104 client_at = autotest.Autotest(self._client)
105 client_at.install()
Tom Wai-Hong Tamfe395092014-02-12 20:16:22 +0800106 self.connect()
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800107
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800108
Tom Wai-Hong Tamfe395092014-02-12 20:16:22 +0800109 def connect(self):
110 """Connects the XML-RPC proxy on the client."""
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800111 @retry.retry((socket.error,
112 xmlrpclib.ProtocolError,
113 httplib.BadStatusLine),
114 timeout_min=self.XMLRPC_RETRY_TIMEOUT / 60.0,
115 delay_sec=self.XMLRPC_RETRY_DELAY)
116 def connect_with_retries():
117 """Connects the XML-RPC proxy with retries."""
Tom Wai-Hong Tam1a197c92014-08-27 14:31:11 +0800118 multimedia_xmlrpc_client = self._client.xmlrpc_connect(
Tom Wai-Hong Tam017d9022014-08-27 11:31:47 +0800119 constants.MULTIMEDIA_XMLRPC_SERVER_COMMAND,
120 constants.MULTIMEDIA_XMLRPC_SERVER_PORT,
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800121 command_name=(
Tom Wai-Hong Tam017d9022014-08-27 11:31:47 +0800122 constants.MULTIMEDIA_XMLRPC_SERVER_CLEANUP_PATTERN
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800123 ),
124 ready_test_name=(
Tom Wai-Hong Tam017d9022014-08-27 11:31:47 +0800125 constants.MULTIMEDIA_XMLRPC_SERVER_READY_METHOD),
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800126 timeout_seconds=self.XMLRPC_CONNECT_TIMEOUT)
Tom Wai-Hong Tam1a197c92014-08-27 14:31:11 +0800127 self._display_xmlrpc_client = multimedia_xmlrpc_client.display
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800128
129 logging.info('Setup the display_client RPC server, with retries...')
130 connect_with_retries()
Tom Wai-Hong Tamfe395092014-02-12 20:16:22 +0800131
132
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800133 def cleanup(self):
134 """Cleans up."""
Tom Wai-Hong Tam0d836592014-04-23 11:27:40 +0800135 self._client.rpc_disconnect(
Tom Wai-Hong Tam017d9022014-08-27 11:31:47 +0800136 constants.MULTIMEDIA_XMLRPC_SERVER_PORT)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800137
138
139 def __del__(self):
140 """Destructor of DisplayClient."""
141 self.cleanup()
142
143
Ting-Yuan Cheng0c481502014-08-13 19:03:47 +0800144 def get_external_connector_name(self):
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800145 """Gets the name of the external output connector.
146
147 @return The external output connector name as a string.
148 """
Ting-Yuan Cheng0c481502014-08-13 19:03:47 +0800149 return self._display_xmlrpc_client.get_external_connector_name()
150
151
152 def get_internal_connector_name(self):
153 """Gets the name of the internal output connector.
154
155 @return The internal output connector name as a string.
156 """
157 return self._display_xmlrpc_client.get_internal_connector_name()
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800158
159
160 def load_calibration_image(self, resolution):
161 """Load a full screen calibration image from the HTTP server.
162
163 @param resolution: A tuple (width, height) of resolution.
164 """
Tom Wai-Hong Tam897fcf72014-04-25 14:02:54 +0800165 with tempfile.NamedTemporaryFile() as f:
166 self._image_generator.generate_image(
167 resolution[0], resolution[1], f.name)
168 os.chmod(f.name, 0644)
169 self._client.send_file(
170 f.name,
171 os.path.join(self.DEST_TMP_DIR, self.DEST_IMAGE_FILENAME))
172
173 page_url = 'file://%s/%s' % (self.DEST_TMP_DIR,
174 self.DEST_IMAGE_FILENAME)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800175 self._display_xmlrpc_client.load_url(page_url)
176
177
178 def close_tab(self, index=-1):
179 """Closes the tab of the given index.
180
181 @param index: The tab index to close. Defaults to the last tab.
182 """
183 return self._display_xmlrpc_client.close_tab(index)
184
185
186 def set_mirrored(self, is_mirrored):
187 """Sets mirrored mode.
188
189 @param is_mirrored: True or False to indicate mirrored state.
190 """
191 return self._display_xmlrpc_client.set_mirrored(is_mirrored)
192
193
Tom Wai-Hong Tam328dbeb2014-02-14 11:20:19 +0800194 def suspend_resume(self, suspend_time=10):
195 """Suspends the DUT for a given time in second.
196
197 @param suspend_time: Suspend time in second, default: 10s.
198 """
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800199 # TODO(waihong): Use other general API instead of this RPC.
Tom Wai-Hong Tam328dbeb2014-02-14 11:20:19 +0800200 return self._display_xmlrpc_client.suspend_resume(suspend_time)
201
202
203 def suspend_resume_bg(self, suspend_time=10):
204 """Suspends the DUT for a given time in second in the background.
205
206 @param suspend_time: Suspend time in second, default: 10s.
207 """
208 # TODO(waihong): Use other general API instead of this RPC.
209 return self._display_xmlrpc_client.suspend_resume_bg(suspend_time)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800210
211
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800212 def reconnect_output_and_wait(self, reconnect=True,
213 expected_display_count=2):
214 """Reconnects output and waits it available.
215
216 @param reconnect: True to perform a re-connection from the DUT; False
217 otherwise.
218 @param expected_display_count:
219 number of displays expected to be connected.
220 """
Ting-Yuan Cheng0c481502014-08-13 19:03:47 +0800221 output = self.get_external_connector_name()
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800222 if reconnect:
223 self._display_xmlrpc_client.reconnect_output(output)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800224 self._display_xmlrpc_client.wait_output_connected(output)
225 utils.wait_for_value(lambda: (
226 len(self._display_xmlrpc_client.get_display_info())),
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800227 expected_value=expected_display_count)
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800228
229
Hung-ying Tyan67541652014-03-12 11:44:46 +0800230 def hide_cursor(self):
231 """Hides mouse cursor by sending a keystroke."""
Tom Wai-Hong Tambd22f8f2014-06-03 03:05:56 +0800232 self._display_xmlrpc_client.press_key('Up')
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800233
234
Ting-Yuan Cheng0c481502014-08-13 19:03:47 +0800235 def _read_root_window_rect(self, w, h, x, y):
236 """Reads the given rectangle from the X root window.
237
238 @param w: The width of the rectangle to read.
239 @param h: The height of the rectangle to read.
240 @param x: The x coordinate.
241 @param y: The y coordinate.
242
243 @return: An Image object.
244 """
245 with tempfile.NamedTemporaryFile(suffix='.rgb') as f:
246 basename = os.path.basename(f.name)
247 remote_path = os.path.join('/tmp', basename)
248 # TODO(waihong): Abstract this X11 specific method.
249 command = ('%s import -window root -depth 8 -crop %dx%d+%d+%d %s' %
250 (self.X_ENV_VARIABLES, w, h, x, y, remote_path))
251 self._client.run(command)
252 self._client.get_file(remote_path, f.name)
253 return Image.fromstring('RGB', (w, h), open(f.name).read())
254
255
256 def get_internal_display_resolution(self):
257 """Gets the resolution of internal display on framebuffer.
258
259 @return The resolution tuple (width, height). None if any error.
260 """
261 connector = self.get_internal_connector_name()
262 if not connector:
263 return None
264 w, h, _, _ = self._display_xmlrpc_client.get_resolution(connector)
265 return (w, h)
266
267
268 def capture_internal_screen(self):
269 """Captures the internal screen framebuffer.
270
271 @return: An Image object. None if any error.
272 """
273 connector = self.get_internal_connector_name()
274 if not connector:
275 return None
276 return self._read_root_window_rect(
277 *self._display_xmlrpc_client.get_resolution(connector))
278
279
Tom Wai-Hong Tam4c8022f2014-07-17 09:12:08 +0800280 def capture_external_screen(self):
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800281 """Captures the external screen framebuffer.
282
Tom Wai-Hong Tam4c8022f2014-07-17 09:12:08 +0800283 @return: An Image object.
Tom Wai-Hong Tamb2d39dc2014-01-21 15:26:23 +0800284 """
Ting-Yuan Cheng0c481502014-08-13 19:03:47 +0800285 output = self.get_external_connector_name()
286 w, h, x, y = self._display_xmlrpc_client.get_resolution(output)
287 return self._read_root_window_rect(w, h, x, y)
Tom Wai-Hong Tamf6bb17f2014-04-24 13:36:49 +0800288
289
290 def get_resolution(self):
291 """Gets the external resolution on framebuffer.
292
293 @return The resolution tuple (width, height)
294 """
Ting-Yuan Cheng0c481502014-08-13 19:03:47 +0800295 output = self.get_external_connector_name()
Tom Wai-Hong Tamf6bb17f2014-04-24 13:36:49 +0800296 width, height, _, _ = self._display_xmlrpc_client.get_resolution(output)
297 return (width, height)
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800298
299
300 def set_resolution(self, display_index, width, height):
301 """Sets the resolution on the specified display.
302
Hung-ying Tyaneea5a792014-08-15 19:07:54 +0800303 @param display_index: index of the display to set resolutions for.
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800304 @param width: width of the resolution
305 @param height: height of the resolution
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800306 """
Ting-Yuan Cheng325ee942014-07-29 16:33:58 +0800307 self._display_xmlrpc_client.set_resolution(
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800308 display_index, width, height)
309
310
311 def get_display_info(self):
312 """Gets the information of all the displays that are connected to the
313 DUT.
314
Hung-ying Tyaneea5a792014-08-15 19:07:54 +0800315 @return: list of object DisplayInfo for display informtion
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800316 """
317 return map(DisplayInfo, self._display_xmlrpc_client.get_display_info())
318
319
Hung-ying Tyaneea5a792014-08-15 19:07:54 +0800320 def get_display_modes(self, display_index):
321 """Gets the display modes of the specified display.
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800322
Hung-ying Tyaneea5a792014-08-15 19:07:54 +0800323 @param display_index: index of the display to get modes from; the index
324 is from the DisplayInfo list obtained by get_display_info().
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800325
Hung-ying Tyaneea5a792014-08-15 19:07:54 +0800326 @return: list of DisplayMode dicts.
Ting-Yuan Cheng568083c2014-07-08 18:19:10 +0800327 """
Hung-ying Tyaneea5a792014-08-15 19:07:54 +0800328 return self._display_xmlrpc_client.get_display_modes(display_index)
Ting-Yuan Cheng5d8f3412014-08-14 14:02:14 +0800329
Hung-ying Tyaneea5a792014-08-15 19:07:54 +0800330
331 def get_available_resolutions(self, display_index):
332 """Gets the resolutions from the specified display.
333
334 @return a list of (width, height) tuples.
335 """
336 # Start from M38 (refer to http://codereview.chromium.org/417113012),
337 # a DisplayMode dict contains 'originalWidth'/'originalHeight'
338 # in addition to 'width'/'height'.
339 # OriginalWidth/originalHeight is what is supported by the display
340 # while width/height is what is shown to users in the display setting.
341 modes = self.get_display_modes(display_index)
342 if modes:
343 if 'originalWidth' in modes[0]:
344 # M38 or newer
345 # TODO(tingyuan): fix loading image for cases where original
346 # width/height is different from width/height.
347 return list(set([(mode['originalWidth'], mode['originalHeight'])
348 for mode in modes]))
349
350 # pre-M38
351 return [(mode['width'], mode['height']) for mode in modes
352 if 'scale' not in mode]