touch_device.py revision 1dfb83324d02b4f82a8ee2bd32525e78197adf86
1# Copyright (c) 2012 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"""Touch device module provides some touch device related attributes."""
6
7import collections
8import glob
9import os
10import re
11
12import common_util
13
14
15# Define AbsAxis class with axis attributes: min, max, and resolution
16AbsAxis = collections.namedtuple('AbsAxis', ['min', 'max', 'resolution'])
17
18
19class TouchDevice:
20    """A class about touch device properties."""
21    def __init__(self, device_node=None, is_touchscreen=False,
22                 device_description=None):
23        """If the device_description is provided (i.e., not None), it is
24        used to create a mocked device for testing purpose.
25        """
26        self.device_node = (device_node if device_node
27                                else self.get_device_node(is_touchscreen))
28        self.axis_x, self.axis_y = self.parse_abs_axes(device_description)
29
30    def get_device_node(self, is_touchscreen):
31        """Get the touch device node through xinput
32           Touchscreens have a different device name, so this
33           chooses between them.  Otherwise they are the same.
34
35           The resulting string looks like /dev/input/event8
36        """
37        cmd = '/opt/google/'
38        if is_touchscreen:
39            cmd = os.path.join(cmd, 'touchscreen/tscontrol')
40        else:
41            cmd = os.path.join(cmd, 'touchpad/tpcontrol')
42        cmd += ' status | grep "Device Node"'
43        device_node_str = common_util.simple_system_output(cmd)
44
45        # Extract and return the device node if device_node_str is not None
46        return (device_node_str.split(':')[-1].strip().strip('"')
47                if device_node_str else None)
48
49    def get_dimensions_in_mm(self):
50        """Get the width and height in mm of the device."""
51        (left, right, top, bottom,
52                resolution_x, resolution_y) = self.get_resolutions()
53        width = float((right - left)) / resolution_x
54        height = float((bottom - top)) / resolution_y
55        return (width, height)
56
57    def get_resolutions(self):
58        """Get the resolutions in x and y axis of the device."""
59        return (self.axis_x.resolution, self.axis_y.resolution)
60
61    def get_edges(self):
62        """Get the left, right, top, and bottom edges of the device."""
63        return (self.axis_x.min, self.axis_x.max,
64                self.axis_y.min, self.axis_y.max)
65
66    def parse_abs_axes(self, device_description):
67        """Prase to get information about min, max, and resolution of
68           ABS_X and ABS_Y
69
70        Example of ABS_X:
71                A: 00 0 1280 0 0 12
72        Example of ABS_y:
73                A: 01 0 1280 0 0 12
74        """
75        pattern = 'A:\s*%s\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)'
76        pattern_x = pattern % '00'
77        pattern_y = pattern % '01'
78        cmd = 'evemu-describe %s' % self.device_node
79        if device_description is None:
80            device_description = common_util.simple_system_output(cmd)
81        axis_x = axis_y = None
82        if device_description:
83            for line in device_description.splitlines():
84                if not axis_x:
85                    result = re.search(pattern_x, line, re.I)
86                    if result:
87                        min_x = int(result.group(1))
88                        max_x = int(result.group(2))
89                        resolution_x = int(result.group(5))
90                        axis_x = AbsAxis(min_x, max_x, resolution_x)
91                if not axis_y:
92                    result = re.search(pattern_y, line, re.I)
93                    if result:
94                        min_y = int(result.group(1))
95                        max_y = int(result.group(2))
96                        resolution_y = int(result.group(5))
97                        axis_y = AbsAxis(min_y, max_y, resolution_y)
98        return (axis_x, axis_y)
99
100    def pixel_to_mm(self, (pixel_x, pixel_y)):
101        """Convert the point coordinate from pixel to mm."""
102        mm_x = float(pixel_x - self.axis_x.min) / self.axis_x.resolution
103        mm_y = float(pixel_y - self.axis_y.min) / self.axis_y.resolution
104        return (mm_x, mm_y)
105
106    def pixel_to_mm_single_axis(self, value_pixel, axis):
107        """Convert the coordinate from pixel to mm."""
108        value_mm = float(value_pixel - axis.min) / axis.resolution
109        return value_mm
110
111    def get_dimensions(self):
112        """Get the vendor-specified dimensions of the touch device."""
113        return (self.axis_x.max - self.axis_x.min,
114                self.axis_y.max - self.axis_y.min)
115
116    def get_display_geometry(self, screen_size, display_ratio):
117        """Get a preferred display geometry when running the test."""
118        display_ratio = 0.8
119        dev_width, dev_height = self.get_dimensions()
120        screen_width, screen_height = screen_size
121
122        if 1.0 * screen_width / screen_height <= 1.0 * dev_width / dev_height:
123            disp_width = int(screen_width * display_ratio)
124            disp_height = int(disp_width * dev_height / dev_width)
125            disp_offset_x = 0
126            disp_offset_y = screen_height - disp_height
127        else:
128            disp_height = int(screen_height * display_ratio)
129            disp_width = int(disp_height * dev_width / dev_height)
130            disp_offset_x = 0
131            disp_offset_y = screen_height - disp_height
132
133        return (disp_width, disp_height, disp_offset_x, disp_offset_y)
134
135    def _touch_input_name_re_str(self):
136        pattern_str = ('touchpad', 'trackpad')
137        return '(?:%s)' % '|'.join(pattern_str)
138
139    def get_touch_input_dir(self):
140        """Get touch device input directory."""
141        input_root_dir = '/sys/class/input'
142        input_dirs = glob.glob(os.path.join(input_root_dir, 'input*'))
143        re_pattern = re.compile(self._touch_input_name_re_str(), re.I)
144        for input_dir in input_dirs:
145            filename = os.path.join(input_dir, 'name')
146            if os.path.isfile(filename):
147                with open(filename) as f:
148                    for line in f:
149                        if re_pattern.search(line) is not None:
150                            return input_dir
151        return None
152
153    def get_firmware_version(self):
154        """Probe the firmware version."""
155        input_dir = self.get_touch_input_dir()
156        device_dir = 'device'
157
158        # Get the re search pattern for firmware_version file name
159        fw_list = ('firmware', 'fw')
160        ver_list = ('version', 'id')
161        sep_list = ('_', '-')
162        re_str = '%s%s%s' % ('(?:%s)' % '|'.join(fw_list),
163                             '(?:%s)' % '|'.join(sep_list),
164                             '(?:%s)' % '|'.join(ver_list))
165        re_pattern = re.compile(re_str, re.I)
166
167        if input_dir is not None:
168            device_dir = os.path.join(input_dir, 'device', '*')
169            for f in glob.glob(device_dir):
170                if os.path.isfile(f) and re_pattern.search(f):
171                    with open (f) as f:
172                        for line in f:
173                            return line.strip('\n')
174        return 'unknown'
175