1# Lint as: python2, python3 2# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""An adapter to remotely access the display facade on DUT.""" 7 8from __future__ import absolute_import 9from __future__ import division 10from __future__ import print_function 11 12import logging 13import os 14import tempfile 15from six.moves import map 16import six.moves.xmlrpc_client 17 18from PIL import Image 19 20from autotest_lib.client.common_lib import error 21from autotest_lib.client.cros.multimedia.display_info import DisplayInfo 22 23 24class DisplayFacadeRemoteAdapter(object): 25 """DisplayFacadeRemoteAdapter is an adapter to remotely control DUT display. 26 27 The Autotest host object representing the remote DUT, passed to this 28 class on initialization, can be accessed from its _client property. 29 30 """ 31 def __init__(self, host, remote_facade_proxy): 32 """Construct a DisplayFacadeRemoteAdapter. 33 34 @param host: Host object representing a remote host. 35 @param remote_facade_proxy: RemoteFacadeProxy object. 36 """ 37 self._client = host 38 self._proxy = remote_facade_proxy 39 40 41 @property 42 def _display_proxy(self): 43 """Gets the proxy to DUT display facade. 44 45 @return XML RPC proxy to DUT display facade. 46 """ 47 return self._proxy.display 48 49 50 def get_external_connector_name(self): 51 """Gets the name of the external output connector. 52 53 @return The external output connector name as a string; False if nothing 54 is connected. 55 """ 56 return self._display_proxy.get_external_connector_name() 57 58 59 def get_internal_connector_name(self): 60 """Gets the name of the internal output connector. 61 62 @return The internal output connector name as a string; False if nothing 63 is connected. 64 """ 65 return self._display_proxy.get_internal_connector_name() 66 67 68 def get_display_notifications(self): 69 """Gets the display notifications 70 71 @return: Returns a list of display related notifications only. 72 """ 73 return self._display_proxy.get_display_notifications() 74 75 def move_to_display(self, display_id): 76 """Moves the current window to the indicated display. 77 78 @param display_id: The id of the indicated display. 79 """ 80 self._display_proxy.move_to_display(display_id) 81 82 83 def create_window(self, url='chrome://newtab'): 84 """Creates a new window from chrome.windows.create API. 85 86 @param url: Optional URL for the new window. 87 88 @return Identifier for the new window. 89 """ 90 return self._display_proxy.create_window(url) 91 92 93 def update_window(self, window_id, state=None, bounds=None): 94 """Updates an existing window using the chrome.windows.update API. 95 96 @param window_id: Identifier for the window to update. 97 @param state: Optional string to set the state such as 'normal', 98 'maximized', or 'fullscreen'. 99 @param bounds: Optional dictionary with keys top, left, width, and 100 height to reposition the window. 101 """ 102 self._display_proxy.update_window(window_id, state, bounds) 103 104 105 def set_fullscreen(self, is_fullscreen): 106 """Sets the current window to full screen. 107 108 @param is_fullscreen: True or False to indicate fullscreen state. 109 @return True if success, False otherwise. 110 """ 111 return self._display_proxy.set_fullscreen(is_fullscreen) 112 113 114 def load_url(self, url): 115 """Loads the given url in a new tab. The new tab will be active. 116 117 @param url: The url to load as a string. 118 @return a str, the tab descriptor of the opened tab. 119 """ 120 return self._display_proxy.load_url(url) 121 122 123 def load_calibration_image(self, resolution): 124 """Load a full screen calibration image from the HTTP server. 125 126 @param resolution: A tuple (width, height) of resolution. 127 @return a str, the tab descriptor of the opened tab. 128 """ 129 return self._display_proxy.load_calibration_image(resolution) 130 131 132 def load_color_sequence(self, tab_descriptor, color_sequence): 133 """Displays a series of colors on full screen on the tab. 134 tab_descriptor is returned by any open tab API of display facade. 135 e.g., 136 tab_descriptor = load_url('about:blank') 137 load_color_sequence(tab_descriptor, color) 138 139 @param tab_descriptor: Indicate which tab to test. 140 @param color_sequence: An integer list for switching colors. 141 @return A list of the timestamp for each switch. 142 """ 143 return self._display_proxy.load_color_sequence(tab_descriptor, 144 color_sequence) 145 146 147 def close_tab(self, tab_descriptor): 148 """Disables fullscreen and closes the tab of the given tab descriptor. 149 tab_descriptor is returned by any open tab API of display facade. 150 e.g., 151 1. 152 tab_descriptor = load_url(url) 153 close_tab(tab_descriptor) 154 155 2. 156 tab_descriptor = load_calibration_image(resolution) 157 close_tab(tab_descriptor) 158 159 @param tab_descriptor: Indicate which tab to close. 160 """ 161 self._display_proxy.close_tab(tab_descriptor) 162 163 164 def is_mirrored_enabled(self): 165 """Checks the mirrored state. 166 167 @return True if mirrored mode is enabled. 168 """ 169 return self._display_proxy.is_mirrored_enabled() 170 171 172 def set_mirrored(self, is_mirrored): 173 """Sets mirrored mode. 174 175 @param is_mirrored: True or False to indicate mirrored state. 176 @throws error.TestError when the call fails. 177 """ 178 if not self._display_proxy.set_mirrored(is_mirrored): 179 raise error.TestError('Failed to set_mirrored(%s)' % is_mirrored) 180 181 182 def is_display_primary(self, internal=True): 183 """Checks if internal screen is primary display. 184 185 @param internal: is internal/external screen primary status requested 186 @return boolean True if internal display is primary. 187 """ 188 return self._display_proxy.is_display_primary(internal) 189 190 191 def suspend_resume(self, suspend_time=10): 192 """Suspends the DUT for a given time in second. 193 194 @param suspend_time: Suspend time in second, default: 10s. 195 """ 196 try: 197 self._display_proxy.suspend_resume(suspend_time) 198 except six.moves.xmlrpc_client.Fault as e: 199 # Log suspend/resume errors but continue the test. 200 logging.error('suspend_resume error: %s', str(e)) 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 self._display_proxy.suspend_resume_bg(suspend_time) 210 211 212 def wait_external_display_connected(self, display): 213 """Waits for the specified display to be connected. 214 215 @param display: The display name as a string, like 'HDMI1', or 216 False if no external display is expected. 217 @return: True if display is connected; False otherwise. 218 """ 219 return self._display_proxy.wait_external_display_connected(display) 220 221 222 def hide_cursor(self): 223 """Hides mouse cursor by sending a keystroke.""" 224 self._display_proxy.hide_cursor() 225 226 227 def hide_typing_cursor(self): 228 """Hides typing cursor by moving outside typing bar.""" 229 self._display_proxy.hide_typing_cursor() 230 231 232 def set_content_protection(self, state): 233 """Sets the content protection of the external screen. 234 235 @param state: One of the states 'Undesired', 'Desired', or 'Enabled' 236 """ 237 self._display_proxy.set_content_protection(state) 238 239 240 def get_content_protection(self): 241 """Gets the state of the content protection. 242 243 @param output: The output name as a string. 244 @return: A string of the state, like 'Undesired', 'Desired', or 'Enabled'. 245 False if not supported. 246 """ 247 return self._display_proxy.get_content_protection() 248 249 250 def _take_screenshot(self, screenshot_func): 251 """Gets screenshot from DUT. 252 253 @param screenshot_func: function to take a screenshot and save the image 254 to specified path on DUT. Usage: screenshot_func(remote_path). 255 256 @return: An Image object. 257 Notice that the returned image may not be in RGB format, 258 depending on PIL implementation. 259 """ 260 with tempfile.NamedTemporaryFile(suffix='.png') as f: 261 basename = os.path.basename(f.name) 262 remote_path = os.path.join('/tmp', basename) 263 screenshot_func(remote_path) 264 self._client.get_file(remote_path, f.name) 265 return Image.open(f.name) 266 267 268 def capture_internal_screen(self): 269 """Captures the internal screen framebuffer. 270 271 @return: An Image object. 272 """ 273 screenshot_func = self._display_proxy.take_internal_screenshot 274 return self._take_screenshot(screenshot_func) 275 276 277 # TODO(ihf): This needs to be fixed for multiple external screens. 278 def capture_external_screen(self): 279 """Captures the external screen framebuffer. 280 281 @return: An Image object. 282 """ 283 screenshot_func = self._display_proxy.take_external_screenshot 284 return self._take_screenshot(screenshot_func) 285 286 287 def capture_calibration_image(self): 288 """Captures the calibration image. 289 290 @return: An Image object. 291 """ 292 screenshot_func = self._display_proxy.save_calibration_image 293 return self._take_screenshot(screenshot_func) 294 295 296 def get_external_resolution(self): 297 """Gets the resolution of the external screen. 298 299 @return The resolution tuple (width, height) or None if no external 300 display is connected. 301 """ 302 resolution = self._display_proxy.get_external_resolution() 303 return tuple(resolution) if resolution else None 304 305 306 def get_internal_resolution(self): 307 """Gets the resolution of the internal screen. 308 309 @return The resolution tuple (width, height) or None if no internal 310 display. 311 """ 312 resolution = self._display_proxy.get_internal_resolution() 313 return tuple(resolution) if resolution else None 314 315 316 def set_resolution(self, display_id, width, height): 317 """Sets the resolution on the specified display. 318 319 @param display_id: id of the display to set resolutions for. 320 @param width: width of the resolution 321 @param height: height of the resolution 322 """ 323 self._display_proxy.set_resolution(display_id, width, height) 324 325 326 # pylint: disable = W0141 327 def get_display_info(self): 328 """Gets the information of all the displays that are connected to the 329 DUT. 330 331 @return: list of object DisplayInfo for display informtion 332 """ 333 return list(map(DisplayInfo, self._display_proxy.get_display_info())) 334 335 336 def get_display_modes(self, display_id): 337 """Gets the display modes of the specified display. 338 339 @param display_id: id of the display to get modes from; the id is from 340 the DisplayInfo list obtained by get_display_info(). 341 342 @return: list of DisplayMode dicts. 343 """ 344 return self._display_proxy.get_display_modes(display_id) 345 346 347 def get_available_resolutions(self, display_id): 348 """Gets the resolutions from the specified display. 349 350 @param display_id: id of the display to get modes from. 351 352 @return a list of (width, height) tuples. 353 """ 354 return [tuple(r) for r in 355 self._display_proxy.get_available_resolutions(display_id)] 356 357 358 def get_display_rotation(self, display_id): 359 """Gets the display rotation for the specified display. 360 361 @param display_id: id of the display to get modes from. 362 363 @return: Degree of rotation. 364 """ 365 return self._display_proxy.get_display_rotation(display_id) 366 367 368 def set_display_rotation(self, display_id, rotation, 369 delay_before_rotation=0, delay_after_rotation=0): 370 """Sets the display rotation for the specified display. 371 372 @param display_id: id of the display to get modes from. 373 @param rotation: degree of rotation 374 @param delay_before_rotation: time in second for delay before rotation 375 @param delay_after_rotation: time in second for delay after rotation 376 """ 377 self._display_proxy.set_display_rotation( 378 display_id, rotation, delay_before_rotation, 379 delay_after_rotation) 380 381 382 def get_internal_display_id(self): 383 """Gets the internal display id. 384 385 @return the id of the internal display. 386 """ 387 return self._display_proxy.get_internal_display_id() 388 389 390 def get_first_external_display_id(self): 391 """Gets the first external display id. 392 393 @return the id of the first external display; -1 if not found. 394 """ 395 return self._display_proxy.get_first_external_display_id() 396 397 398 def reset_connector_if_applicable(self, connector_type): 399 """Resets Type-C video connector from host end if applicable. 400 401 It's the workaround sequence since sometimes Type-C dongle becomes 402 corrupted and needs to be re-plugged. 403 404 @param connector_type: A string, like "VGA", "DVI", "HDMI", or "DP". 405 """ 406 logging.info('Connector Type %s.', connector_type) 407 return self._display_proxy.reset_connector_if_applicable(connector_type) 408 409 410 def get_window_info(self): 411 """Gets the current window info from Chrome.system.window API. 412 413 @return a dict for the information of the current window. 414 """ 415 return self._display_proxy.get_window_info() 416