blob: e12bed15cfd96257dcd04d60467c17907d3c5f05 [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:
148 xorg_conf_path = '/etc/X11/xorg.conf.d'
149
Dennis Kempin17766a62013-06-17 14:09:33 -0700150 if xorg_conf_path and 'xorg' in data and 'file' in data['xorg']:
151 filename = os.path.join(xorg_conf_path, data['xorg']['file'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700152 input_classes = self.xorg_parser.Parse(file=filename)
Dennis Kempin17766a62013-06-17 14:09:33 -0700153 if 'identifier' in data['xorg']:
154 properties = input_classes[data['xorg']['identifier']]
Dennis Kempin037675e2013-06-14 14:12:39 -0700155 self.properties.update(properties)
Dennis Kempin17766a62013-06-17 14:09:33 -0700156 if 'identifiers' in data['xorg']:
157 for identifier in data['xorg']['identifiers']:
Dennis Kempin037675e2013-06-14 14:12:39 -0700158 properties = input_classes[identifier]
159 self.properties.update(properties)
160
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700161 for prop in self.ignore_properties:
162 if prop in self.properties:
163 del self.properties[prop]
164
Dennis Kempin037675e2013-06-14 14:12:39 -0700165 def _UpdateDimensions(self):
Dennis Kempin17766a62013-06-17 14:09:33 -0700166 """ Update x/y min/max with xorg properties.
167
168 CMT allows hardware properties to be overwritten by xorg properties.
169 Do the same in this class.
Dennis Kempin037675e2013-06-14 14:12:39 -0700170 """
Dennis Kempin17766a62013-06-17 14:09:33 -0700171 if 'Active Area Left' in self.properties:
172 self.x_min = int(self.properties['Active Area Left'])
173 if 'Active Area Right' in self.properties:
174 self.x_max = int(self.properties['Active Area Right'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700175 if 'Active Area Top' in self.properties:
176 self.y_min = int(self.properties['Active Area Top'])
177 if 'Active Area Bottom' in self.properties:
178 self.y_max = int(self.properties['Active Area Bottom'])
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700179
180 if 'Horizontal Resolution' in self.properties:
181 self.x_res = int(self.properties['Horizontal Resolution'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700182 if 'Vertical Resolution' in self.properties:
183 self.y_res = int(self.properties['Vertical Resolution'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700184
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700185 if 'SemiMT Non Linear Area Left' in self.properties:
186 self.x_min = int(self.properties['SemiMT Non Linear Area Left'])
187 if 'SemiMT Non Linear Area Right' in self.properties:
188 self.x_max = int(self.properties['SemiMT Non Linear Area Right'])
189 if 'SemiMT Non Linear Area Top' in self.properties:
190 self.y_min = int(self.properties['SemiMT Non Linear Area Top'])
191 if 'SemiMT Non Linear Area Bottom' in self.properties:
192 self.y_max = int(self.properties['SemiMT Non Linear Area Bottom'])
193
194
195 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700196 """ Compare properties and return similarity.
197
198 Compare these properties to another PlatformProperties instance.
199 The return value is a score between 1. 0 meaning there is a big mismatch
200 and 1 meaning the properties match completely.
201 Only a selected range of properties are compared in order to
202 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700203 """
204 scores = []
205 def compare(a, b, what):
206 value = abs(float(a) - float(b))
207 if value > 0:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700208 value = min(1, value / max(abs(float(a)), abs(float(b))))
Dennis Kempin037675e2013-06-14 14:12:39 -0700209 scores.append(1-value)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700210 if debug:
211 print "%s: %s == %s" % (what, str(a), str(b))
Dennis Kempin037675e2013-06-14 14:12:39 -0700212 def compare_attr(what):
213 compare(getattr(self, what), getattr(other, what), what)
214 def compare_prop(what):
215 if what not in self.properties or what not in other.properties:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700216 scores.append(0)
217 else:
218 compare(self.properties[what], other.properties[what], what)
219 def check_axis(required, available):
220 for axis in required:
221 if axis not in available:
222 scores.append(0)
223 return
224
Dennis Kempin17766a62013-06-17 14:09:33 -0700225 compare_attr('x_min')
226 compare_attr('x_max')
227 compare_attr('x_res')
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700228 compare_attr('y_min')
229 compare_attr('y_max')
230 compare_attr('y_res')
231 if not loose:
232 compare_prop('Pressure Calibration Offset')
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700233
234 if self.required_axis:
235 if debug:
236 print "axis:", self.required_axis, "in", other.has_axis
237 check_axis(self.required_axis, other.has_axis)
238
239 if other.required_axis:
240 if debug:
241 print "axis:", other.required_axis, "in", self.has_axis
242 check_axis(other.required_axis, self.has_axis)
243
Dennis Kempin58d9a742014-05-08 18:34:59 -0700244 return reduce(lambda x, y: (x * y), scores)
Dennis Kempin037675e2013-06-14 14:12:39 -0700245
246class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700247 """ Class for managing platforms.
248
249 This class reads all available platforms from the platforms_dir and allows
250 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700251 """
252 def __init__(self):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700253 platform_files = [f for f in os.listdir(platforms_dir)
254 if f.endswith('.hwprops')]
Dennis Kempin037675e2013-06-14 14:12:39 -0700255 self.platforms = {}
Dennis Kempin58d9a742014-05-08 18:34:59 -0700256 for filename in platform_files:
257 name = filename.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700258 self.platforms[name] = PlatformProperties(platform=name)
259
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700260 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700261 """ Find platform matching activity_data.
262
263 Returns the PlatformProperties instance of the platform matching
264 the activity log data. This method might terminate the program in
265 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700266 """
267 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700268 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700269 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700270 if debug:
271 print "#" * 10, name
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700272 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700273 if debug:
274 print name, "score =", score
Andrew de los Reyesf56af272014-07-21 14:32:21 -0700275 if score > 0.96:
Dennis Kempin037675e2013-06-14 14:12:39 -0700276 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700277 if loose:
278 if debug:
279 print "-" * 10, "Multiple matches. Try strict"
280 return self.FindMatching(log, False, debug)
Dennis Kempin17766a62013-06-17 14:09:33 -0700281 print ('multiple matching platforms:', result.name,
282 'and', platform.name)
Dennis Kempin19e972b2013-06-20 13:21:38 -0700283 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700284 result = platform
285 if not result:
Dennis Kempin17766a62013-06-17 14:09:33 -0700286 print 'cannot find matching platform'
Dennis Kempin19e972b2013-06-20 13:21:38 -0700287 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700288 return result
Dennis Kempind0b722a2014-04-15 11:54:48 -0700289
290 @staticmethod
291 def RegisterPlatformFromDevice(ip):
292 # get list of multitouch devices
293 remote = CrOSRemote(ip)
Dennis Kempin58d9a742014-05-08 18:34:59 -0700294 devices = remote.SafeExecute(
Dennis Kempind0b722a2014-04-15 11:54:48 -0700295 "/opt/google/input/inputcontrol -t multitouch --names",
296 verbose=True)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700297
298 # Each line has the format:
299 # id: Device Name
300 # devices[*][0] will have the id
301 # devices[*][1] will have the name
302 devices = devices.splitlines()
303 devices = [l.split(":", 1) for l in devices]
304
305 # select one device from list
Dennis Kempin58d9a742014-05-08 18:34:59 -0700306 idx = AskUser.Select([d[1] for d in devices],
Dennis Kempind0b722a2014-04-15 11:54:48 -0700307 "Which device would you like to register?")
308 device_id = devices[idx][0]
309
310 # read hardware properties
Dennis Kempin58d9a742014-05-08 18:34:59 -0700311 hwprops = remote.SafeExecute(
Dennis Kempind0b722a2014-04-15 11:54:48 -0700312 "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
313 verbose=True)
314 if not hwprops:
315 print "Please update your device to latest canary or:"
316 print " emerge-${BOARD} inputcontrol"
317 print " cros deploy $DEVICE_IP inputcontrol"
318 return None
319
Dennis Kempincd7caba2014-04-16 13:37:18 -0700320 xorg_files = [
Dennis Kempin58d9a742014-05-08 18:34:59 -0700321 "/etc/X11/xorg.conf.d/60-touchpad-cmt-*.conf",
322 "/etc/X11/xorg.conf.d/50-touchpad-cmt-*.conf",
323 "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf"
Dennis Kempincd7caba2014-04-16 13:37:18 -0700324 ]
325
326 for pattern in xorg_files:
327 # find filename of xorg configuration file
Dennis Kempin58d9a742014-05-08 18:34:59 -0700328 xorg_file = remote.Execute("ls " + pattern, verbose=False)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700329 if not xorg_file:
330 continue
331 xorg_file = xorg_file.strip()
332
333 # extract identifiers
Dennis Kempind0b722a2014-04-15 11:54:48 -0700334 print "Selecting Xorg identifiers from", xorg_file
335 conf = remote.Read(xorg_file)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700336 all_ids = []
Dennis Kempin58d9a742014-05-08 18:34:59 -0700337 for match in re.finditer("Identifier\\s+\"([a-zA-Z0-9-_ ]+)\"", conf):
Dennis Kempincd7caba2014-04-16 13:37:18 -0700338 all_ids.append(match.group(1))
Dennis Kempind0b722a2014-04-15 11:54:48 -0700339
Dennis Kempincd7caba2014-04-16 13:37:18 -0700340 # ask user to select
Dennis Kempin58d9a742014-05-08 18:34:59 -0700341 idxs = AskUser.SelectMulti(
342 all_ids, "Which xorg identifiers apply to this device?",
343 allow_none=True)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700344 ids = [all_ids[i] for i in idxs]
345 if ids:
346 break
Dennis Kempind0b722a2014-04-15 11:54:48 -0700347
Dennis Kempind0b722a2014-04-15 11:54:48 -0700348 if not ids:
Dennis Kempincd7caba2014-04-16 13:37:18 -0700349 print "Please configure the platform properties manually"
350 xorg_file = "todo: add correct xorg conf file"
351 ids = ["todo: add correct xorg identifier"]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700352
Dennis Kempin58d9a742014-05-08 18:34:59 -0700353 ids_string = "[" + ", ".join(["\"%s\"" % i for i in ids]) + "]"
Dennis Kempind0b722a2014-04-15 11:54:48 -0700354 xorg_file = os.path.basename(xorg_file)
355
356 sys.stdout.write("Please name this platform: ")
357 sys.stdout.flush()
358 platform_name = sys.stdin.readline().strip()
359
360 # write platform info to files
361 hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
362 props_file = os.path.join(platforms_dir, platform_name + ".props")
363
364 open(hwprops_file, "w").write(hwprops)
365 open(props_file, "w").write(props_template % (xorg_file, ids_string))
366
367 print "Created files: "
368 print " ", hwprops_file
369 print " ", props_file
370
371 return platform_name