1*9c5db199SXin Li# Copyright 2017 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport logging 6*9c5db199SXin Liimport os 7*9c5db199SXin Li 8*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 9*9c5db199SXin Lifrom autotest_lib.client.cros.graphics import graphics_utils 10*9c5db199SXin Lifrom autotest_lib.client.cros.input_playback import input_playback 11*9c5db199SXin Li 12*9c5db199SXin Li_CLICK_EVENTS = '/tmp/click_events' 13*9c5db199SXin Li_CLICK_TEMPLATE = 'click_events.template' 14*9c5db199SXin Li_PREFIX_RESOLUTION = 'RESOLUTION' 15*9c5db199SXin Li_PREFIX_POSITION = 'POSITION' 16*9c5db199SXin Li_STYLUS_DEVICE = 'stylus' 17*9c5db199SXin Li_STYLUS_PROPERTY = '/tmp/stylus.prop' 18*9c5db199SXin Li_STYLUS_TEMPLATE = 'stylus.prop.template' 19*9c5db199SXin Li 20*9c5db199SXin Li 21*9c5db199SXin Liclass Stylus(object): 22*9c5db199SXin Li """An emulated stylus device used for UI automation.""" 23*9c5db199SXin Li 24*9c5db199SXin Li def __init__(self): 25*9c5db199SXin Li """Prepare an emulated stylus device based on the internal display.""" 26*9c5db199SXin Li self.dirname = os.path.dirname(__file__) 27*9c5db199SXin Li width, height = graphics_utils.get_internal_resolution() 28*9c5db199SXin Li logging.info('internal display W = %d H = %d ', width, height) 29*9c5db199SXin Li # Skip the test if there is no internal display 30*9c5db199SXin Li if width == -1: 31*9c5db199SXin Li raise error.TestNAError('No internal display') 32*9c5db199SXin Li 33*9c5db199SXin Li # Enlarge resolution of the emulated stylus. 34*9c5db199SXin Li self.width = width * 10 35*9c5db199SXin Li self.height = height * 10 36*9c5db199SXin Li stylus_template = os.path.join(self.dirname, _STYLUS_TEMPLATE) 37*9c5db199SXin Li self.replace_with_prefix(stylus_template, _STYLUS_PROPERTY, 38*9c5db199SXin Li _PREFIX_RESOLUTION, self.width, self.height) 39*9c5db199SXin Li # Create an emulated stylus device. 40*9c5db199SXin Li self.stylus = input_playback.InputPlayback() 41*9c5db199SXin Li self.stylus.emulate(input_type=_STYLUS_DEVICE, 42*9c5db199SXin Li property_file=_STYLUS_PROPERTY) 43*9c5db199SXin Li self.stylus.find_connected_inputs() 44*9c5db199SXin Li 45*9c5db199SXin Li def replace_with_prefix(self, in_file, out_file, prefix, x_value, y_value): 46*9c5db199SXin Li """Substitute with the real positions and write to an output file. 47*9c5db199SXin Li 48*9c5db199SXin Li Replace the keywords in template file with the real values and save 49*9c5db199SXin Li the results into a file. 50*9c5db199SXin Li 51*9c5db199SXin Li @param in_file: the template file containing keywords for substitution. 52*9c5db199SXin Li @param out_file: the generated file after substitution. 53*9c5db199SXin Li @param prefix: the prefix of the keywords for substituion. 54*9c5db199SXin Li @param x_value: the target value of X. 55*9c5db199SXin Li @param y_value: the target value of Y. 56*9c5db199SXin Li 57*9c5db199SXin Li """ 58*9c5db199SXin Li with open(in_file) as infile: 59*9c5db199SXin Li content = infile.readlines() 60*9c5db199SXin Li 61*9c5db199SXin Li with open(out_file, 'w') as outfile: 62*9c5db199SXin Li for line in content: 63*9c5db199SXin Li if line.find(prefix + '_X') > 0: 64*9c5db199SXin Li line = line.replace(prefix + '_X', str(x_value)) 65*9c5db199SXin Li x_value += 1 66*9c5db199SXin Li else: 67*9c5db199SXin Li if line.find(prefix + '_Y') > 0: 68*9c5db199SXin Li line = line.replace(prefix + '_Y', str(y_value)) 69*9c5db199SXin Li y_value += 1 70*9c5db199SXin Li outfile.write(line) 71*9c5db199SXin Li 72*9c5db199SXin Li def click(self, position_x, position_y): 73*9c5db199SXin Li """Click the point(x,y) on the emulated stylus panel. 74*9c5db199SXin Li 75*9c5db199SXin Li @param position_x: the X position of the click point. 76*9c5db199SXin Li @param position_y: the Y position of the click point. 77*9c5db199SXin Li 78*9c5db199SXin Li """ 79*9c5db199SXin Li click_template = os.path.join(self.dirname, _CLICK_TEMPLATE) 80*9c5db199SXin Li self.replace_with_prefix(click_template, 81*9c5db199SXin Li _CLICK_EVENTS, 82*9c5db199SXin Li _PREFIX_POSITION, 83*9c5db199SXin Li position_x * 10, 84*9c5db199SXin Li position_y * 10) 85*9c5db199SXin Li self.stylus.blocking_playback(_CLICK_EVENTS, input_type=_STYLUS_DEVICE) 86*9c5db199SXin Li 87*9c5db199SXin Li def click_with_percentage(self, percent_x, percent_y): 88*9c5db199SXin Li """Click a point based on the percentage of the display. 89*9c5db199SXin Li 90*9c5db199SXin Li @param percent_x: the percentage of X position over display width. 91*9c5db199SXin Li @param percent_y: the percentage of Y position over display height. 92*9c5db199SXin Li 93*9c5db199SXin Li """ 94*9c5db199SXin Li position_x = int(percent_x * self.width / 10) 95*9c5db199SXin Li position_y = int(percent_y * self.height / 10) 96*9c5db199SXin Li self.click(position_x, position_y) 97*9c5db199SXin Li 98*9c5db199SXin Li def close(self): 99*9c5db199SXin Li """Clean up the files/handles created in the class.""" 100*9c5db199SXin Li if self.stylus: 101*9c5db199SXin Li self.stylus.close() 102*9c5db199SXin Li if os.path.exists(_STYLUS_PROPERTY): 103*9c5db199SXin Li os.remove(_STYLUS_PROPERTY) 104*9c5db199SXin Li if os.path.exists(_CLICK_EVENTS): 105*9c5db199SXin Li os.remove(_CLICK_EVENTS) 106*9c5db199SXin Li 107*9c5db199SXin Li def __enter__(self): 108*9c5db199SXin Li """Allow usage in 'with' statements.""" 109*9c5db199SXin Li return self 110*9c5db199SXin Li 111*9c5db199SXin Li def __exit__(self, exc_type, exc_val, exc_tb): 112*9c5db199SXin Li """Release resources on completion of a 'with' statement.""" 113*9c5db199SXin Li self.close() 114