blob: 0b12320769a7063f103018a6e4d9c859316b7582 [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
Amirhossein Simjour9127ff12015-03-11 15:47:14 -04009from util import ExecuteException
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070010import json
Dennis Kempin037675e2013-06-14 14:12:39 -070011import os
12import re
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070013import sys
Dennis Kempin037675e2013-06-14 14:12:39 -070014
15# path to current script directory
16script_dir = os.path.dirname(os.path.realpath(__file__))
Dennis Kempin17766a62013-06-17 14:09:33 -070017platforms_dir = os.path.realpath(os.path.join(script_dir, '..', 'platforms'))
Chung-yih Wang35613f12014-04-25 13:57:23 +080018xorg_conf_project_path = 'src/platform/xorg-conf'
Dennis Kempin037675e2013-06-14 14:12:39 -070019
Dennis Kempind0b722a2014-04-15 11:54:48 -070020props_template = """\
21{
22 "gestures": {
23 },
24 "xorg": {
25 "file": "%s",
26 "identifiers": %s
27 },
28 "ignore": [
29 ]
30}"""
31
Dennis Kempin037675e2013-06-14 14:12:39 -070032class PlatformProperties(object):
Dennis Kempin17766a62013-06-17 14:09:33 -070033 """ A class containing hardware and xorg properties for a platform.
34
35 The class can be created from an activity log or by providing
36 the name of the platform. Information will then be read from the
37 'platforms_dir' directory.
Dennis Kempin037675e2013-06-14 14:12:39 -070038 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070039 def __init__(self, platform=None, log=None):
40 self.required_axis = []
41 self.has_axis = []
42 self.device_class = "touchpad"
Dennis Kempin58d9a742014-05-08 18:34:59 -070043 self.properties = {}
44 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070045 if platform:
46 basename = os.path.join(platforms_dir, platform)
47 self.name = platform
Dennis Kempin17766a62013-06-17 14:09:33 -070048 self.hwprops_file = basename + '.hwprops'
49 self.props_file = basename + '.props'
Dennis Kempin037675e2013-06-14 14:12:39 -070050 self.xorg_parser = XorgInputClassParser()
51 self._ParseHWProperties(open(self.hwprops_file).read())
52 self._ParseProperties(open(self.props_file).read())
53 self._UpdateDimensions()
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070054 elif log:
Dennis Kempin17766a62013-06-17 14:09:33 -070055 self.name = ''
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070056 if log.evdev:
57 self._ParseEvdevLog(log.evdev)
Dennis Kempincd7caba2014-04-16 13:37:18 -070058 self._ParseActivityLog(log.activity)
59
Dennis Kempin037675e2013-06-14 14:12:39 -070060
61 def _ParseActivityLog(self, activity_data):
Dennis Kempin58d9a742014-05-08 18:34:59 -070062 """ Parse property information from an activity log."""
Dennis Kempin037675e2013-06-14 14:12:39 -070063 activity = json.loads(activity_data)
Dennis Kempin17766a62013-06-17 14:09:33 -070064 self.properties = activity['properties']
Dennis Kempin037675e2013-06-14 14:12:39 -070065
Dennis Kempin17766a62013-06-17 14:09:33 -070066 hwprops = activity['hardwareProperties']
67 self.x_min = int(hwprops['left'])
68 self.x_max = int(hwprops['right'])
69 self.x_res = int(hwprops['xResolution'])
70 self.y_min = int(hwprops['top'])
71 self.y_max = int(hwprops['bottom'])
72 self.y_res = int(hwprops['yResolution'])
Dennis Kempin037675e2013-06-14 14:12:39 -070073
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070074 def _ParseEvdevLog(self, evdev_data):
Dennis Kempin143ef162014-04-09 13:46:50 -070075 # Look for embedded hwproperties in header. Format:
76 # absinfo: axis min max 0 0 res
Andrew de los Reyesa29db692014-07-29 10:00:27 -070077 abs_regex = 5 * ' ([-0-9]+)'
Dennis Kempin143ef162014-04-09 13:46:50 -070078 xregex = re.compile('# absinfo: 53' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -070079 xmatch = xregex.search(evdev_data)
Dennis Kempin143ef162014-04-09 13:46:50 -070080 self.x_min = int(xmatch.group(1))
81 self.x_max = int(xmatch.group(2))
82 self.x_res = int(xmatch.group(5))
83
84 yregex = re.compile('# absinfo: 54' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -070085 ymatch = yregex.search(evdev_data)
Dennis Kempin143ef162014-04-09 13:46:50 -070086 self.y_min = int(ymatch.group(1))
87 self.y_max = int(ymatch.group(2))
88 self.y_res = int(ymatch.group(5))
89
90 axis_regex = re.compile('# absinfo: ([0-9]+)')
91 for match in axis_regex.finditer(evdev_data):
92 self.has_axis.append(int(match.group(1)))
93
94 # look for axes used in the log itself.
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070095 # The format of ABS (0003) reports is:
96 # timestamp 0003 axis value
97 report_regex = re.compile(' 0003 ([0-9a-f]{4}) ([0-9a-f]+)')
98 for match in report_regex.finditer(evdev_data):
99 axis = int(match.group(1), 16)
100 if axis not in self.required_axis:
101 self.required_axis.append(axis)
102
Dennis Kempin143ef162014-04-09 13:46:50 -0700103
Dennis Kempin037675e2013-06-14 14:12:39 -0700104 def _ParseHWProperties(self, data):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700105 """Parse x and y dimensions and resolution from hwprops file."""
106 abs_regex = 5 * ' ([0-9\\-]+)'
Dennis Kempin143ef162014-04-09 13:46:50 -0700107 xregex = re.compile('A: 35' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -0700108 xmatch = xregex.search(data)
Dennis Kempin037675e2013-06-14 14:12:39 -0700109 self.x_min = int(xmatch.group(1))
110 self.x_max = int(xmatch.group(2))
111 self.x_res = int(xmatch.group(5))
112
Dennis Kempin143ef162014-04-09 13:46:50 -0700113 yregex = re.compile('A: 36' + abs_regex)
Dennis Kempin58d9a742014-05-08 18:34:59 -0700114 ymatch = yregex.search(data)
Dennis Kempin037675e2013-06-14 14:12:39 -0700115 self.y_min = int(ymatch.group(1))
116 self.y_max = int(ymatch.group(2))
117 self.y_res = int(ymatch.group(5))
118
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700119 axis_regex = re.compile('A: ([0-9a-f]+)')
120 for match in axis_regex.finditer(data):
121 self.has_axis.append(int(match.group(1), 16))
122
Dennis Kempin037675e2013-06-14 14:12:39 -0700123 def _ParseProperties(self, data):
Dennis Kempin17766a62013-06-17 14:09:33 -0700124 """ Parse properties from file and inject xorg properties. """
Dennis Kempin037675e2013-06-14 14:12:39 -0700125 self.properties = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700126 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -0700127 data = json.loads(data)
128
Dennis Kempin17766a62013-06-17 14:09:33 -0700129 if 'gestures' in data:
130 self.properties.update(data['gestures'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700131
Dennis Kempin17766a62013-06-17 14:09:33 -0700132 if 'device_class' in data:
133 self.device_class = data['device_class']
Dennis Kempin037675e2013-06-14 14:12:39 -0700134
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700135 if 'ignore' in data:
136 self.ignore_properties.extend(data['ignore'])
137
Chung-yih Wang35613f12014-04-25 13:57:23 +0800138 # Sanity check: Make sure it is not used inside ebuild.
139 if os.environ.get('PN') and os.environ.get('S'):
140 raise Exception("Should not be executed inside ebuild")
141
142 # If run on a Chromebook device, access xorg-conf files from their normal
143 # installed location. If run from inside chroot, access xorg-conf files
144 # from the xorg-conf project repository.
145 src_root = os.environ.get('CROS_WORKON_SRCROOT')
146 if src_root:
147 xorg_conf_path = os.path.join(src_root, xorg_conf_project_path)
148 else:
Andrew de los Reyesc0125972015-01-28 10:04:44 -0800149 xorg_conf_path = '/etc/gesture'
150 if not os.path.exists(xorg_conf_path):
151 xorg_conf_path = '/etc/X11/xorg.conf.d'
Chung-yih Wang35613f12014-04-25 13:57:23 +0800152
Dennis Kempin17766a62013-06-17 14:09:33 -0700153 if xorg_conf_path and 'xorg' in data and 'file' in data['xorg']:
154 filename = os.path.join(xorg_conf_path, data['xorg']['file'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700155 input_classes = self.xorg_parser.Parse(file=filename)
Dennis Kempin17766a62013-06-17 14:09:33 -0700156 if 'identifier' in data['xorg']:
157 properties = input_classes[data['xorg']['identifier']]
Dennis Kempin037675e2013-06-14 14:12:39 -0700158 self.properties.update(properties)
Dennis Kempin17766a62013-06-17 14:09:33 -0700159 if 'identifiers' in data['xorg']:
160 for identifier in data['xorg']['identifiers']:
Dennis Kempin037675e2013-06-14 14:12:39 -0700161 properties = input_classes[identifier]
162 self.properties.update(properties)
163
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700164 for prop in self.ignore_properties:
165 if prop in self.properties:
166 del self.properties[prop]
167
Dennis Kempin037675e2013-06-14 14:12:39 -0700168 def _UpdateDimensions(self):
Dennis Kempin17766a62013-06-17 14:09:33 -0700169 """ Update x/y min/max with xorg properties.
170
171 CMT allows hardware properties to be overwritten by xorg properties.
172 Do the same in this class.
Dennis Kempin037675e2013-06-14 14:12:39 -0700173 """
Dennis Kempin17766a62013-06-17 14:09:33 -0700174 if 'Active Area Left' in self.properties:
175 self.x_min = int(self.properties['Active Area Left'])
176 if 'Active Area Right' in self.properties:
177 self.x_max = int(self.properties['Active Area Right'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700178 if 'Active Area Top' in self.properties:
179 self.y_min = int(self.properties['Active Area Top'])
180 if 'Active Area Bottom' in self.properties:
181 self.y_max = int(self.properties['Active Area Bottom'])
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700182
183 if 'Horizontal Resolution' in self.properties:
184 self.x_res = int(self.properties['Horizontal Resolution'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700185 if 'Vertical Resolution' in self.properties:
186 self.y_res = int(self.properties['Vertical Resolution'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700187
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700188 if 'SemiMT Non Linear Area Left' in self.properties:
189 self.x_min = int(self.properties['SemiMT Non Linear Area Left'])
190 if 'SemiMT Non Linear Area Right' in self.properties:
191 self.x_max = int(self.properties['SemiMT Non Linear Area Right'])
192 if 'SemiMT Non Linear Area Top' in self.properties:
193 self.y_min = int(self.properties['SemiMT Non Linear Area Top'])
194 if 'SemiMT Non Linear Area Bottom' in self.properties:
195 self.y_max = int(self.properties['SemiMT Non Linear Area Bottom'])
196
197
198 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700199 """ Compare properties and return similarity.
200
201 Compare these properties to another PlatformProperties instance.
202 The return value is a score between 1. 0 meaning there is a big mismatch
203 and 1 meaning the properties match completely.
204 Only a selected range of properties are compared in order to
205 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700206 """
207 scores = []
208 def compare(a, b, what):
209 value = abs(float(a) - float(b))
210 if value > 0:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700211 value = min(1, value / max(abs(float(a)), abs(float(b))))
Dennis Kempin037675e2013-06-14 14:12:39 -0700212 scores.append(1-value)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700213 if debug:
214 print "%s: %s == %s" % (what, str(a), str(b))
Dennis Kempin037675e2013-06-14 14:12:39 -0700215 def compare_attr(what):
216 compare(getattr(self, what), getattr(other, what), what)
217 def compare_prop(what):
218 if what not in self.properties or what not in other.properties:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700219 scores.append(0)
220 else:
221 compare(self.properties[what], other.properties[what], what)
222 def check_axis(required, available):
223 for axis in required:
224 if axis not in available:
225 scores.append(0)
226 return
227
Dennis Kempin17766a62013-06-17 14:09:33 -0700228 compare_attr('x_min')
229 compare_attr('x_max')
230 compare_attr('x_res')
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700231 compare_attr('y_min')
232 compare_attr('y_max')
233 compare_attr('y_res')
234 if not loose:
235 compare_prop('Pressure Calibration Offset')
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700236
237 if self.required_axis:
238 if debug:
239 print "axis:", self.required_axis, "in", other.has_axis
240 check_axis(self.required_axis, other.has_axis)
241
242 if other.required_axis:
243 if debug:
244 print "axis:", other.required_axis, "in", self.has_axis
245 check_axis(other.required_axis, self.has_axis)
246
Dennis Kempin58d9a742014-05-08 18:34:59 -0700247 return reduce(lambda x, y: (x * y), scores)
Dennis Kempin037675e2013-06-14 14:12:39 -0700248
249class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700250 """ Class for managing platforms.
251
252 This class reads all available platforms from the platforms_dir and allows
253 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700254 """
255 def __init__(self):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700256 platform_files = [f for f in os.listdir(platforms_dir)
257 if f.endswith('.hwprops')]
Dennis Kempin037675e2013-06-14 14:12:39 -0700258 self.platforms = {}
Dennis Kempin58d9a742014-05-08 18:34:59 -0700259 for filename in platform_files:
260 name = filename.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700261 self.platforms[name] = PlatformProperties(platform=name)
262
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700263 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700264 """ Find platform matching activity_data.
265
266 Returns the PlatformProperties instance of the platform matching
267 the activity log data. This method might terminate the program in
268 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700269 """
270 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700271 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700272 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700273 if debug:
274 print "#" * 10, name
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700275 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700276 if debug:
277 print name, "score =", score
Andrew de los Reyesf56af272014-07-21 14:32:21 -0700278 if score > 0.96:
Dennis Kempin037675e2013-06-14 14:12:39 -0700279 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700280 if loose:
281 if debug:
282 print "-" * 10, "Multiple matches. Try strict"
283 return self.FindMatching(log, False, debug)
Dennis Kempin17766a62013-06-17 14:09:33 -0700284 print ('multiple matching platforms:', result.name,
285 'and', platform.name)
Dennis Kempin19e972b2013-06-20 13:21:38 -0700286 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700287 result = platform
288 if not result:
Dennis Kempin17766a62013-06-17 14:09:33 -0700289 print 'cannot find matching platform'
Dennis Kempin19e972b2013-06-20 13:21:38 -0700290 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700291 return result
Dennis Kempind0b722a2014-04-15 11:54:48 -0700292
293 @staticmethod
294 def RegisterPlatformFromDevice(ip):
295 # get list of multitouch devices
296 remote = CrOSRemote(ip)
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400297 inputcontrolexist = True
298 try:
299 devices = remote.SafeExecute(
300 "/opt/google/input/inputcontrol -t multitouch --names",
301 verbose=True)
302 except ExecuteException:
303 inputcontrolexist = False
304 if inputcontrolexist:
305 # Each line has the format:
306 # id: Device Name
307 # devices[*][0] will have the id
308 # devices[*][1] will have the name
309 devices = devices.splitlines()
310 devices = [l.split(":", 1) for l in devices]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700311
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400312 # select one device from list
313 idx = AskUser.Select([d[1] for d in devices],
314 "Which device would you like to register?")
315 device_id = devices[idx][0]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700316
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400317 # read hardware properties
318 hwprops = remote.SafeExecute(
319 "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
320 verbose=True)
321 else:
322 devices = remote.SafeExecute(
323 "ls /dev/input/ | grep event",
324 verbose=True)
325 devices = devices.splitlines()
326 touchdevices = []
327 for d in devices:
328 device_desc_evemu = remote.SafeExecute(
329 "evemu-describe /dev/input/%s |grep N:" % d,
330 verbose=False)
331 event_number = (d.split("event", 1))[1]
332 device_desc_evemu = device_desc_evemu.split("N:",1)[1]
333 if "Touchpad" in device_desc_evemu:
334 touchdevices.append([event_number, device_desc_evemu])
335
336 idx = AskUser.Select([t[1] for t in touchdevices],
337 "Which device would you like to register?")
338 device_id = touchdevices[idx][0]
339 hwprops = remote.SafeExecute(
340 "evemu-describe /dev/input/event%s" % touchdevices[idx][0],
341 verbose=True)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700342 if not hwprops:
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400343 print "Please update your device to latest canary or:"
344 print " emerge-${BOARD} inputcontrol"
345 print " cros deploy $DEVICE_IP inputcontrol"
346 return None
347
Dennis Kempind0b722a2014-04-15 11:54:48 -0700348
Dennis Kempincd7caba2014-04-16 13:37:18 -0700349 xorg_files = [
Dennis Kempin58d9a742014-05-08 18:34:59 -0700350 "/etc/X11/xorg.conf.d/60-touchpad-cmt-*.conf",
351 "/etc/X11/xorg.conf.d/50-touchpad-cmt-*.conf",
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400352 "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf",
353 "/etc/gesture/60-touchpad-cmt-*.conf",
354 "/etc/gesture/50-touchpad-cmt-*.conf",
355 "/etc/gesture/40-touchpad-cmt.conf"
Dennis Kempincd7caba2014-04-16 13:37:18 -0700356 ]
357
358 for pattern in xorg_files:
359 # find filename of xorg configuration file
Dennis Kempin58d9a742014-05-08 18:34:59 -0700360 xorg_file = remote.Execute("ls " + pattern, verbose=False)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700361 if not xorg_file:
362 continue
363 xorg_file = xorg_file.strip()
364
365 # extract identifiers
Dennis Kempind0b722a2014-04-15 11:54:48 -0700366 print "Selecting Xorg identifiers from", xorg_file
367 conf = remote.Read(xorg_file)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700368 all_ids = []
Dennis Kempin58d9a742014-05-08 18:34:59 -0700369 for match in re.finditer("Identifier\\s+\"([a-zA-Z0-9-_ ]+)\"", conf):
Dennis Kempincd7caba2014-04-16 13:37:18 -0700370 all_ids.append(match.group(1))
Dennis Kempind0b722a2014-04-15 11:54:48 -0700371
Dennis Kempincd7caba2014-04-16 13:37:18 -0700372 # ask user to select
Dennis Kempin58d9a742014-05-08 18:34:59 -0700373 idxs = AskUser.SelectMulti(
374 all_ids, "Which xorg identifiers apply to this device?",
375 allow_none=True)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700376 ids = [all_ids[i] for i in idxs]
377 if ids:
378 break
Dennis Kempind0b722a2014-04-15 11:54:48 -0700379
Dennis Kempind0b722a2014-04-15 11:54:48 -0700380 if not ids:
Dennis Kempincd7caba2014-04-16 13:37:18 -0700381 print "Please configure the platform properties manually"
382 xorg_file = "todo: add correct xorg conf file"
383 ids = ["todo: add correct xorg identifier"]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700384
Dennis Kempin58d9a742014-05-08 18:34:59 -0700385 ids_string = "[" + ", ".join(["\"%s\"" % i for i in ids]) + "]"
Dennis Kempind0b722a2014-04-15 11:54:48 -0700386 xorg_file = os.path.basename(xorg_file)
387
388 sys.stdout.write("Please name this platform: ")
389 sys.stdout.flush()
390 platform_name = sys.stdin.readline().strip()
391
392 # write platform info to files
393 hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
394 props_file = os.path.join(platforms_dir, platform_name + ".props")
395
396 open(hwprops_file, "w").write(hwprops)
397 open(props_file, "w").write(props_template % (xorg_file, ids_string))
398
399 print "Created files: "
400 print " ", hwprops_file
401 print " ", props_file
402
403 return platform_name