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