blob: 6d053979c6a750f1c3229edb7f0685a6c73afdd8 [file] [log] [blame]
Dennis Kempin1a8a5be2013-06-18 11:00:02 -07001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
Dennis Kempin037675e2013-06-14 14:12:39 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4#
Dennis Kempin58d9a742014-05-08 18:34:59 -07005""" This module manages the platform properties in mttools/platforms. """
6from cros_remote import CrOSRemote
7from util import AskUser
8from xorg_conf import XorgInputClassParser
Dennis Kempin1a8a5be2013-06-18 11:00:02 -07009import json
Dennis Kempin037675e2013-06-14 14:12:39 -070010import os
11import re
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070012import sys
Dennis Kempin037675e2013-06-14 14:12:39 -070013
14# path to current script directory
15script_dir = os.path.dirname(os.path.realpath(__file__))
Dennis Kempin17766a62013-06-17 14:09:33 -070016platforms_dir = os.path.realpath(os.path.join(script_dir, '..', 'platforms'))
Chung-yih Wang35613f12014-04-25 13:57:23 +080017xorg_conf_project_path = 'src/platform/xorg-conf'
Dennis Kempin037675e2013-06-14 14:12:39 -070018
Dennis Kempind0b722a2014-04-15 11:54:48 -070019props_template = """\
20{
21 "gestures": {
22 },
23 "xorg": {
24 "file": "%s",
25 "identifiers": %s
26 },
27 "ignore": [
28 ]
29}"""
30
Dennis Kempin037675e2013-06-14 14:12:39 -070031class PlatformProperties(object):
Dennis Kempin17766a62013-06-17 14:09:33 -070032 """ A class containing hardware and xorg properties for a platform.
33
34 The class can be created from an activity log or by providing
35 the name of the platform. Information will then be read from the
36 'platforms_dir' directory.
Dennis Kempin037675e2013-06-14 14:12:39 -070037 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070038 def __init__(self, platform=None, log=None):
39 self.required_axis = []
40 self.has_axis = []
41 self.device_class = "touchpad"
Dennis Kempin58d9a742014-05-08 18:34:59 -070042 self.properties = {}
43 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070044 if platform:
45 basename = os.path.join(platforms_dir, platform)
46 self.name = platform
Dennis Kempin17766a62013-06-17 14:09:33 -070047 self.hwprops_file = basename + '.hwprops'
48 self.props_file = basename + '.props'
Dennis Kempin037675e2013-06-14 14:12:39 -070049 self.xorg_parser = XorgInputClassParser()
50 self._ParseHWProperties(open(self.hwprops_file).read())
51 self._ParseProperties(open(self.props_file).read())
52 self._UpdateDimensions()
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070053 elif log:
Dennis Kempin17766a62013-06-17 14:09:33 -070054 self.name = ''
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070055 if log.evdev:
56 self._ParseEvdevLog(log.evdev)
Dennis Kempincd7caba2014-04-16 13:37:18 -070057 self._ParseActivityLog(log.activity)
58
Dennis Kempin037675e2013-06-14 14:12:39 -070059
60 def _ParseActivityLog(self, activity_data):
Dennis Kempin58d9a742014-05-08 18:34:59 -070061 """ Parse property information from an activity log."""
Dennis Kempin037675e2013-06-14 14:12:39 -070062 activity = json.loads(activity_data)
Dennis Kempin17766a62013-06-17 14:09:33 -070063 self.properties = activity['properties']
Dennis Kempin037675e2013-06-14 14:12:39 -070064
Dennis Kempin17766a62013-06-17 14:09:33 -070065 hwprops = activity['hardwareProperties']
66 self.x_min = int(hwprops['left'])
67 self.x_max = int(hwprops['right'])
68 self.x_res = int(hwprops['xResolution'])
69 self.y_min = int(hwprops['top'])
70 self.y_max = int(hwprops['bottom'])
71 self.y_res = int(hwprops['yResolution'])
Dennis Kempin037675e2013-06-14 14:12:39 -070072
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070073 def _ParseEvdevLog(self, evdev_data):
Dennis Kempin143ef162014-04-09 13:46:50 -070074 # Look for embedded hwproperties in header. Format:
75 # absinfo: axis min max 0 0 res
Andrew de los Reyesa29db692014-07-29 10:00:27 -070076 abs_regex = 5 * ' ([-0-9]+)'
Dennis Kempin143ef162014-04-09 13:46:50 -070077 xregex = re.compile('# absinfo: 53' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -070078 xmatch = xregex.search(evdev_data)
Dennis Kempin143ef162014-04-09 13:46:50 -070079 self.x_min = int(xmatch.group(1))
80 self.x_max = int(xmatch.group(2))
81 self.x_res = int(xmatch.group(5))
82
83 yregex = re.compile('# absinfo: 54' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -070084 ymatch = yregex.search(evdev_data)
Dennis Kempin143ef162014-04-09 13:46:50 -070085 self.y_min = int(ymatch.group(1))
86 self.y_max = int(ymatch.group(2))
87 self.y_res = int(ymatch.group(5))
88
89 axis_regex = re.compile('# absinfo: ([0-9]+)')
90 for match in axis_regex.finditer(evdev_data):
91 self.has_axis.append(int(match.group(1)))
92
93 # look for axes used in the log itself.
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070094 # The format of ABS (0003) reports is:
95 # timestamp 0003 axis value
96 report_regex = re.compile(' 0003 ([0-9a-f]{4}) ([0-9a-f]+)')
97 for match in report_regex.finditer(evdev_data):
98 axis = int(match.group(1), 16)
99 if axis not in self.required_axis:
100 self.required_axis.append(axis)
101
Dennis Kempin143ef162014-04-09 13:46:50 -0700102
Dennis Kempin037675e2013-06-14 14:12:39 -0700103 def _ParseHWProperties(self, data):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700104 """Parse x and y dimensions and resolution from hwprops file."""
105 abs_regex = 5 * ' ([0-9\\-]+)'
Dennis Kempin143ef162014-04-09 13:46:50 -0700106 xregex = re.compile('A: 35' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -0700107 xmatch = xregex.search(data)
Dennis Kempin037675e2013-06-14 14:12:39 -0700108 self.x_min = int(xmatch.group(1))
109 self.x_max = int(xmatch.group(2))
110 self.x_res = int(xmatch.group(5))
111
Dennis Kempin143ef162014-04-09 13:46:50 -0700112 yregex = re.compile('A: 36' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -0700113 ymatch = yregex.search(data)
Dennis Kempin037675e2013-06-14 14:12:39 -0700114 self.y_min = int(ymatch.group(1))
115 self.y_max = int(ymatch.group(2))
116 self.y_res = int(ymatch.group(5))
117
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700118 axis_regex = re.compile('A: ([0-9a-f]+)')
119 for match in axis_regex.finditer(data):
120 self.has_axis.append(int(match.group(1), 16))
121
Dennis Kempin037675e2013-06-14 14:12:39 -0700122 def _ParseProperties(self, data):
Dennis Kempin17766a62013-06-17 14:09:33 -0700123 """ Parse properties from file and inject xorg properties. """
Dennis Kempin037675e2013-06-14 14:12:39 -0700124 self.properties = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700125 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -0700126 data = json.loads(data)
127
Dennis Kempin17766a62013-06-17 14:09:33 -0700128 if 'gestures' in data:
129 self.properties.update(data['gestures'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700130
Dennis Kempin17766a62013-06-17 14:09:33 -0700131 if 'device_class' in data:
132 self.device_class = data['device_class']
Dennis Kempin037675e2013-06-14 14:12:39 -0700133
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700134 if 'ignore' in data:
135 self.ignore_properties.extend(data['ignore'])
136
Chung-yih Wang35613f12014-04-25 13:57:23 +0800137 # Sanity check: Make sure it is not used inside ebuild.
138 if os.environ.get('PN') and os.environ.get('S'):
139 raise Exception("Should not be executed inside ebuild")
140
141 # If run on a Chromebook device, access xorg-conf files from their normal
142 # installed location. If run from inside chroot, access xorg-conf files
143 # from the xorg-conf project repository.
144 src_root = os.environ.get('CROS_WORKON_SRCROOT')
145 if src_root:
146 xorg_conf_path = os.path.join(src_root, xorg_conf_project_path)
147 else:
Andrew de los Reyesc0125972015-01-28 10:04:44 -0800148 xorg_conf_path = '/etc/gesture'
149 if not os.path.exists(xorg_conf_path):
150 xorg_conf_path = '/etc/X11/xorg.conf.d'
Chung-yih Wang35613f12014-04-25 13:57:23 +0800151
Dennis Kempin17766a62013-06-17 14:09:33 -0700152 if xorg_conf_path and 'xorg' in data and 'file' in data['xorg']:
153 filename = os.path.join(xorg_conf_path, data['xorg']['file'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700154 input_classes = self.xorg_parser.Parse(file=filename)
Dennis Kempin17766a62013-06-17 14:09:33 -0700155 if 'identifier' in data['xorg']:
156 properties = input_classes[data['xorg']['identifier']]
Dennis Kempin037675e2013-06-14 14:12:39 -0700157 self.properties.update(properties)
Dennis Kempin17766a62013-06-17 14:09:33 -0700158 if 'identifiers' in data['xorg']:
159 for identifier in data['xorg']['identifiers']:
Dennis Kempin037675e2013-06-14 14:12:39 -0700160 properties = input_classes[identifier]
161 self.properties.update(properties)
162
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700163 for prop in self.ignore_properties:
164 if prop in self.properties:
165 del self.properties[prop]
166
Dennis Kempin037675e2013-06-14 14:12:39 -0700167 def _UpdateDimensions(self):
Dennis Kempin17766a62013-06-17 14:09:33 -0700168 """ Update x/y min/max with xorg properties.
169
170 CMT allows hardware properties to be overwritten by xorg properties.
171 Do the same in this class.
Dennis Kempin037675e2013-06-14 14:12:39 -0700172 """
Dennis Kempin17766a62013-06-17 14:09:33 -0700173 if 'Active Area Left' in self.properties:
174 self.x_min = int(self.properties['Active Area Left'])
175 if 'Active Area Right' in self.properties:
176 self.x_max = int(self.properties['Active Area Right'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700177 if 'Active Area Top' in self.properties:
178 self.y_min = int(self.properties['Active Area Top'])
179 if 'Active Area Bottom' in self.properties:
180 self.y_max = int(self.properties['Active Area Bottom'])
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700181
182 if 'Horizontal Resolution' in self.properties:
183 self.x_res = int(self.properties['Horizontal Resolution'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700184 if 'Vertical Resolution' in self.properties:
185 self.y_res = int(self.properties['Vertical Resolution'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700186
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700187 if 'SemiMT Non Linear Area Left' in self.properties:
188 self.x_min = int(self.properties['SemiMT Non Linear Area Left'])
189 if 'SemiMT Non Linear Area Right' in self.properties:
190 self.x_max = int(self.properties['SemiMT Non Linear Area Right'])
191 if 'SemiMT Non Linear Area Top' in self.properties:
192 self.y_min = int(self.properties['SemiMT Non Linear Area Top'])
193 if 'SemiMT Non Linear Area Bottom' in self.properties:
194 self.y_max = int(self.properties['SemiMT Non Linear Area Bottom'])
195
196
197 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700198 """ Compare properties and return similarity.
199
200 Compare these properties to another PlatformProperties instance.
201 The return value is a score between 1. 0 meaning there is a big mismatch
202 and 1 meaning the properties match completely.
203 Only a selected range of properties are compared in order to
204 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700205 """
206 scores = []
207 def compare(a, b, what):
208 value = abs(float(a) - float(b))
209 if value > 0:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700210 value = min(1, value / max(abs(float(a)), abs(float(b))))
Dennis Kempin037675e2013-06-14 14:12:39 -0700211 scores.append(1-value)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700212 if debug:
213 print "%s: %s == %s" % (what, str(a), str(b))
Dennis Kempin037675e2013-06-14 14:12:39 -0700214 def compare_attr(what):
215 compare(getattr(self, what), getattr(other, what), what)
216 def compare_prop(what):
217 if what not in self.properties or what not in other.properties:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700218 scores.append(0)
219 else:
220 compare(self.properties[what], other.properties[what], what)
221 def check_axis(required, available):
222 for axis in required:
223 if axis not in available:
224 scores.append(0)
225 return
226
Dennis Kempin17766a62013-06-17 14:09:33 -0700227 compare_attr('x_min')
228 compare_attr('x_max')
229 compare_attr('x_res')
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700230 compare_attr('y_min')
231 compare_attr('y_max')
232 compare_attr('y_res')
233 if not loose:
234 compare_prop('Pressure Calibration Offset')
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700235
236 if self.required_axis:
237 if debug:
238 print "axis:", self.required_axis, "in", other.has_axis
239 check_axis(self.required_axis, other.has_axis)
240
241 if other.required_axis:
242 if debug:
243 print "axis:", other.required_axis, "in", self.has_axis
244 check_axis(other.required_axis, self.has_axis)
245
Dennis Kempin58d9a742014-05-08 18:34:59 -0700246 return reduce(lambda x, y: (x * y), scores)
Dennis Kempin037675e2013-06-14 14:12:39 -0700247
248class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700249 """ Class for managing platforms.
250
251 This class reads all available platforms from the platforms_dir and allows
252 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700253 """
254 def __init__(self):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700255 platform_files = [f for f in os.listdir(platforms_dir)
256 if f.endswith('.hwprops')]
Dennis Kempin037675e2013-06-14 14:12:39 -0700257 self.platforms = {}
Dennis Kempin58d9a742014-05-08 18:34:59 -0700258 for filename in platform_files:
259 name = filename.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700260 self.platforms[name] = PlatformProperties(platform=name)
261
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700262 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700263 """ Find platform matching activity_data.
264
265 Returns the PlatformProperties instance of the platform matching
266 the activity log data. This method might terminate the program in
267 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700268 """
269 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700270 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700271 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700272 if debug:
273 print "#" * 10, name
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700274 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700275 if debug:
276 print name, "score =", score
Andrew de los Reyesf56af272014-07-21 14:32:21 -0700277 if score > 0.96:
Dennis Kempin037675e2013-06-14 14:12:39 -0700278 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700279 if loose:
280 if debug:
281 print "-" * 10, "Multiple matches. Try strict"
282 return self.FindMatching(log, False, debug)
Dennis Kempin17766a62013-06-17 14:09:33 -0700283 print ('multiple matching platforms:', result.name,
284 'and', platform.name)
Dennis Kempin19e972b2013-06-20 13:21:38 -0700285 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700286 result = platform
287 if not result:
Dennis Kempin17766a62013-06-17 14:09:33 -0700288 print 'cannot find matching platform'
Dennis Kempin19e972b2013-06-20 13:21:38 -0700289 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700290 return result
Dennis Kempind0b722a2014-04-15 11:54:48 -0700291
292 @staticmethod
293 def RegisterPlatformFromDevice(ip):
294 # get list of multitouch devices
295 remote = CrOSRemote(ip)
Dennis Kempin58d9a742014-05-08 18:34:59 -0700296 devices = remote.SafeExecute(
Dennis Kempind0b722a2014-04-15 11:54:48 -0700297 "/opt/google/input/inputcontrol -t multitouch --names",
298 verbose=True)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700299
300 # Each line has the format:
301 # id: Device Name
302 # devices[*][0] will have the id
303 # devices[*][1] will have the name
304 devices = devices.splitlines()
305 devices = [l.split(":", 1) for l in devices]
306
307 # select one device from list
Dennis Kempin58d9a742014-05-08 18:34:59 -0700308 idx = AskUser.Select([d[1] for d in devices],
Dennis Kempind0b722a2014-04-15 11:54:48 -0700309 "Which device would you like to register?")
310 device_id = devices[idx][0]
311
312 # read hardware properties
Dennis Kempin58d9a742014-05-08 18:34:59 -0700313 hwprops = remote.SafeExecute(
Dennis Kempind0b722a2014-04-15 11:54:48 -0700314 "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
315 verbose=True)
316 if not hwprops:
317 print "Please update your device to latest canary or:"
318 print " emerge-${BOARD} inputcontrol"
319 print " cros deploy $DEVICE_IP inputcontrol"
320 return None
321
Dennis Kempincd7caba2014-04-16 13:37:18 -0700322 xorg_files = [
Dennis Kempin58d9a742014-05-08 18:34:59 -0700323 "/etc/X11/xorg.conf.d/60-touchpad-cmt-*.conf",
324 "/etc/X11/xorg.conf.d/50-touchpad-cmt-*.conf",
325 "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf"
Dennis Kempincd7caba2014-04-16 13:37:18 -0700326 ]
327
328 for pattern in xorg_files:
329 # find filename of xorg configuration file
Dennis Kempin58d9a742014-05-08 18:34:59 -0700330 xorg_file = remote.Execute("ls " + pattern, verbose=False)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700331 if not xorg_file:
332 continue
333 xorg_file = xorg_file.strip()
334
335 # extract identifiers
Dennis Kempind0b722a2014-04-15 11:54:48 -0700336 print "Selecting Xorg identifiers from", xorg_file
337 conf = remote.Read(xorg_file)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700338 all_ids = []
Dennis Kempin58d9a742014-05-08 18:34:59 -0700339 for match in re.finditer("Identifier\\s+\"([a-zA-Z0-9-_ ]+)\"", conf):
Dennis Kempincd7caba2014-04-16 13:37:18 -0700340 all_ids.append(match.group(1))
Dennis Kempind0b722a2014-04-15 11:54:48 -0700341
Dennis Kempincd7caba2014-04-16 13:37:18 -0700342 # ask user to select
Dennis Kempin58d9a742014-05-08 18:34:59 -0700343 idxs = AskUser.SelectMulti(
344 all_ids, "Which xorg identifiers apply to this device?",
345 allow_none=True)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700346 ids = [all_ids[i] for i in idxs]
347 if ids:
348 break
Dennis Kempind0b722a2014-04-15 11:54:48 -0700349
Dennis Kempind0b722a2014-04-15 11:54:48 -0700350 if not ids:
Dennis Kempincd7caba2014-04-16 13:37:18 -0700351 print "Please configure the platform properties manually"
352 xorg_file = "todo: add correct xorg conf file"
353 ids = ["todo: add correct xorg identifier"]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700354
Dennis Kempin58d9a742014-05-08 18:34:59 -0700355 ids_string = "[" + ", ".join(["\"%s\"" % i for i in ids]) + "]"
Dennis Kempind0b722a2014-04-15 11:54:48 -0700356 xorg_file = os.path.basename(xorg_file)
357
358 sys.stdout.write("Please name this platform: ")
359 sys.stdout.flush()
360 platform_name = sys.stdin.readline().strip()
361
362 # write platform info to files
363 hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
364 props_file = os.path.join(platforms_dir, platform_name + ".props")
365
366 open(hwprops_file, "w").write(hwprops)
367 open(props_file, "w").write(props_template % (xorg_file, ids_string))
368
369 print "Created files: "
370 print " ", hwprops_file
371 print " ", props_file
372
373 return platform_name