blob: f4384672d65687874ff449042b96f80a04784762 [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. """
Dennis Kempin3cb7b902015-04-06 11:12:40 -07006from collections import namedtuple
Dennis Kempin1a8a5be2013-06-18 11:00:02 -07007import json
Dennis Kempin037675e2013-06-14 14:12:39 -07008import os
9import re
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070010import sys
Dennis Kempin037675e2013-06-14 14:12:39 -070011
Dennis Kempin3cb7b902015-04-06 11:12:40 -070012from cros_remote import CrOSRemote
13from util import AskUser, ExecuteException
14from xorg_conf import XorgInputClassParser
15
Dennis Kempin037675e2013-06-14 14:12:39 -070016# path to current script directory
17script_dir = os.path.dirname(os.path.realpath(__file__))
Dennis Kempin17766a62013-06-17 14:09:33 -070018platforms_dir = os.path.realpath(os.path.join(script_dir, '..', 'platforms'))
Chung-yih Wang35613f12014-04-25 13:57:23 +080019xorg_conf_project_path = 'src/platform/xorg-conf'
Dennis Kempin037675e2013-06-14 14:12:39 -070020
Dennis Kempind0b722a2014-04-15 11:54:48 -070021props_template = """\
22{
23 "gestures": {
24 },
25 "xorg": {
26 "file": "%s",
27 "identifiers": %s
28 },
29 "ignore": [
30 ]
31}"""
32
Dennis Kempin3cb7b902015-04-06 11:12:40 -070033AbsInfo = namedtuple("AbsInfo", ("min", "max", "res"))
34
Dennis Kempin037675e2013-06-14 14:12:39 -070035class PlatformProperties(object):
Dennis Kempin17766a62013-06-17 14:09:33 -070036 """ A class containing hardware and xorg properties for a platform.
37
38 The class can be created from an activity log or by providing
39 the name of the platform. Information will then be read from the
40 'platforms_dir' directory.
Dennis Kempin037675e2013-06-14 14:12:39 -070041 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070042 def __init__(self, platform=None, log=None):
Dennis Kempin3cb7b902015-04-06 11:12:40 -070043 self.absinfos = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070044 self.device_class = "touchpad"
Dennis Kempin58d9a742014-05-08 18:34:59 -070045 self.properties = {}
46 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070047 if platform:
48 basename = os.path.join(platforms_dir, platform)
49 self.name = platform
Dennis Kempin17766a62013-06-17 14:09:33 -070050 self.hwprops_file = basename + '.hwprops'
51 self.props_file = basename + '.props'
Dennis Kempin037675e2013-06-14 14:12:39 -070052 self.xorg_parser = XorgInputClassParser()
53 self._ParseHWProperties(open(self.hwprops_file).read())
54 self._ParseProperties(open(self.props_file).read())
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070055 elif log:
Dennis Kempin17766a62013-06-17 14:09:33 -070056 self.name = ''
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070057 if log.evdev:
58 self._ParseEvdevLog(log.evdev)
Dennis Kempin037675e2013-06-14 14:12:39 -070059
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070060 def _ParseEvdevLog(self, evdev_data):
Dennis Kempin143ef162014-04-09 13:46:50 -070061 # Look for embedded hwproperties in header. Format:
62 # absinfo: axis min max 0 0 res
Dennis Kempin3cb7b902015-04-06 11:12:40 -070063 values_regex = 6 * ' ([-0-9]+)'
64 abs_regex = re.compile('# absinfo:' + values_regex)
65 for match in abs_regex.finditer(evdev_data):
66 axis = int(match.group(1))
67 info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
68 res=int(match.group(6)))
69 self.absinfos[axis] = info
Dennis Kempin143ef162014-04-09 13:46:50 -070070
Dennis Kempin037675e2013-06-14 14:12:39 -070071 def _ParseHWProperties(self, data):
Dennis Kempin58d9a742014-05-08 18:34:59 -070072 """Parse x and y dimensions and resolution from hwprops file."""
Dennis Kempin3cb7b902015-04-06 11:12:40 -070073 values_regex = 6 * ' ([0-9\\-a-fA-F]+)'
74 abs_regex = re.compile('A:' + values_regex)
75 for match in abs_regex.finditer(data):
76 axis = int(match.group(1), 16)
77 info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
78 res=int(match.group(6)))
79 self.absinfos[axis] = info
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070080
Dennis Kempin037675e2013-06-14 14:12:39 -070081 def _ParseProperties(self, data):
Dennis Kempin17766a62013-06-17 14:09:33 -070082 """ Parse properties from file and inject xorg properties. """
Dennis Kempin037675e2013-06-14 14:12:39 -070083 self.properties = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070084 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070085 data = json.loads(data)
86
Dennis Kempin17766a62013-06-17 14:09:33 -070087 if 'gestures' in data:
88 self.properties.update(data['gestures'])
Dennis Kempin037675e2013-06-14 14:12:39 -070089
Dennis Kempin17766a62013-06-17 14:09:33 -070090 if 'device_class' in data:
91 self.device_class = data['device_class']
Dennis Kempin037675e2013-06-14 14:12:39 -070092
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070093 if 'ignore' in data:
94 self.ignore_properties.extend(data['ignore'])
95
Chung-yih Wang35613f12014-04-25 13:57:23 +080096 # Sanity check: Make sure it is not used inside ebuild.
97 if os.environ.get('PN') and os.environ.get('S'):
98 raise Exception("Should not be executed inside ebuild")
99
100 # If run on a Chromebook device, access xorg-conf files from their normal
101 # installed location. If run from inside chroot, access xorg-conf files
102 # from the xorg-conf project repository.
103 src_root = os.environ.get('CROS_WORKON_SRCROOT')
104 if src_root:
105 xorg_conf_path = os.path.join(src_root, xorg_conf_project_path)
106 else:
Andrew de los Reyesc0125972015-01-28 10:04:44 -0800107 xorg_conf_path = '/etc/gesture'
108 if not os.path.exists(xorg_conf_path):
109 xorg_conf_path = '/etc/X11/xorg.conf.d'
Chung-yih Wang35613f12014-04-25 13:57:23 +0800110
Dennis Kempin17766a62013-06-17 14:09:33 -0700111 if xorg_conf_path and 'xorg' in data and 'file' in data['xorg']:
112 filename = os.path.join(xorg_conf_path, data['xorg']['file'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700113 input_classes = self.xorg_parser.Parse(file=filename)
Dennis Kempin17766a62013-06-17 14:09:33 -0700114 if 'identifier' in data['xorg']:
115 properties = input_classes[data['xorg']['identifier']]
Dennis Kempin037675e2013-06-14 14:12:39 -0700116 self.properties.update(properties)
Dennis Kempin17766a62013-06-17 14:09:33 -0700117 if 'identifiers' in data['xorg']:
118 for identifier in data['xorg']['identifiers']:
Dennis Kempin037675e2013-06-14 14:12:39 -0700119 properties = input_classes[identifier]
120 self.properties.update(properties)
121
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700122 for prop in self.ignore_properties:
123 if prop in self.properties:
124 del self.properties[prop]
125
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700126 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700127 """ Compare properties and return similarity.
128
129 Compare these properties to another PlatformProperties instance.
130 The return value is a score between 1. 0 meaning there is a big mismatch
131 and 1 meaning the properties match completely.
132 Only a selected range of properties are compared in order to
133 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700134 """
135 scores = []
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700136 def compare_absinfo_prop(a, b, field):
137 a_value = float(getattr(a, field)) if a else None
138 b_value = float(getattr(b, field)) if b else None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700139
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700140 score = 0.0
141 if a_value is not None and b_value is not None:
142 delta = abs(float(a_value) - float(b_value))
143 if delta > 0:
144 score = 1.0 - min(1.0, delta / max(abs(a_value), abs(b_value)))
145 else:
146 score = 1.0
147 scores.append(score)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700148
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700149 a_str = str(a_value) if a_value is not None else "N/A"
150 b_str = str(b_value) if b_value is not None else "N/A"
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700151 if debug:
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700152 print " %s: %.2f (%s == %s)" % (field, score, a_str, b_str)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700153
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700154 for axis in set(self.absinfos.keys() + other.absinfos.keys()):
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700155 if debug:
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700156 print "axis %d:" % axis
157 self_absinfo = self.absinfos.get(axis, None)
158 other_absinfo = other.absinfos.get(axis, None)
159 compare_absinfo_prop(self_absinfo, other_absinfo, "min")
160 compare_absinfo_prop(self_absinfo, other_absinfo, "max")
161 compare_absinfo_prop(self_absinfo, other_absinfo, "res")
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700162
Dennis Kempin58d9a742014-05-08 18:34:59 -0700163 return reduce(lambda x, y: (x * y), scores)
Dennis Kempin037675e2013-06-14 14:12:39 -0700164
165class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700166 """ Class for managing platforms.
167
168 This class reads all available platforms from the platforms_dir and allows
169 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700170 """
171 def __init__(self):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700172 platform_files = [f for f in os.listdir(platforms_dir)
173 if f.endswith('.hwprops')]
Dennis Kempin037675e2013-06-14 14:12:39 -0700174 self.platforms = {}
Dennis Kempin58d9a742014-05-08 18:34:59 -0700175 for filename in platform_files:
176 name = filename.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700177 self.platforms[name] = PlatformProperties(platform=name)
178
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700179 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700180 """ Find platform matching activity_data.
181
182 Returns the PlatformProperties instance of the platform matching
183 the activity log data. This method might terminate the program in
184 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700185 """
186 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700187 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700188 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700189 if debug:
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700190 print "-" * 30, name
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700191 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700192 if debug:
193 print name, "score =", score
Andrew de los Reyesf56af272014-07-21 14:32:21 -0700194 if score > 0.96:
Dennis Kempin037675e2013-06-14 14:12:39 -0700195 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700196 if loose:
197 if debug:
198 print "-" * 10, "Multiple matches. Try strict"
199 return self.FindMatching(log, False, debug)
Dennis Kempin17766a62013-06-17 14:09:33 -0700200 print ('multiple matching platforms:', result.name,
201 'and', platform.name)
Dennis Kempin19e972b2013-06-20 13:21:38 -0700202 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700203 result = platform
204 if not result:
Dennis Kempin17766a62013-06-17 14:09:33 -0700205 print 'cannot find matching platform'
Dennis Kempin19e972b2013-06-20 13:21:38 -0700206 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700207 return result
Dennis Kempind0b722a2014-04-15 11:54:48 -0700208
209 @staticmethod
210 def RegisterPlatformFromDevice(ip):
211 # get list of multitouch devices
212 remote = CrOSRemote(ip)
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400213 inputcontrolexist = True
214 try:
215 devices = remote.SafeExecute(
216 "/opt/google/input/inputcontrol -t multitouch --names",
217 verbose=True)
218 except ExecuteException:
219 inputcontrolexist = False
220 if inputcontrolexist:
221 # Each line has the format:
222 # id: Device Name
223 # devices[*][0] will have the id
224 # devices[*][1] will have the name
225 devices = devices.splitlines()
226 devices = [l.split(":", 1) for l in devices]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700227
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400228 # select one device from list
229 idx = AskUser.Select([d[1] for d in devices],
230 "Which device would you like to register?")
231 device_id = devices[idx][0]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700232
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400233 # read hardware properties
234 hwprops = remote.SafeExecute(
235 "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
236 verbose=True)
237 else:
238 devices = remote.SafeExecute(
239 "ls /dev/input/ | grep event",
240 verbose=True)
241 devices = devices.splitlines()
242 touchdevices = []
243 for d in devices:
244 device_desc_evemu = remote.SafeExecute(
245 "evemu-describe /dev/input/%s |grep N:" % d,
246 verbose=False)
247 event_number = (d.split("event", 1))[1]
Amirhossein Simjourbcc37012015-07-07 14:59:56 -0400248 device_desc_evemu = device_desc_evemu.split("N:",1)[1]
249 if "Touchpad" in device_desc_evemu or "Trackpad" in device_desc_evemu:
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400250 touchdevices.append([event_number, device_desc_evemu])
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400251 idx = AskUser.Select([t[1] for t in touchdevices],
252 "Which device would you like to register?")
253 device_id = touchdevices[idx][0]
254 hwprops = remote.SafeExecute(
255 "evemu-describe /dev/input/event%s" % touchdevices[idx][0],
256 verbose=True)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700257 if not hwprops:
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400258 print "Please update your device to latest canary or:"
259 print " emerge-${BOARD} inputcontrol"
260 print " cros deploy $DEVICE_IP inputcontrol"
261 return None
262
Dennis Kempind0b722a2014-04-15 11:54:48 -0700263
Dennis Kempincd7caba2014-04-16 13:37:18 -0700264 xorg_files = [
Dennis Kempin58d9a742014-05-08 18:34:59 -0700265 "/etc/X11/xorg.conf.d/60-touchpad-cmt-*.conf",
266 "/etc/X11/xorg.conf.d/50-touchpad-cmt-*.conf",
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400267 "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf",
268 "/etc/gesture/60-touchpad-cmt-*.conf",
269 "/etc/gesture/50-touchpad-cmt-*.conf",
270 "/etc/gesture/40-touchpad-cmt.conf"
Dennis Kempincd7caba2014-04-16 13:37:18 -0700271 ]
272
273 for pattern in xorg_files:
274 # find filename of xorg configuration file
Dennis Kempin58d9a742014-05-08 18:34:59 -0700275 xorg_file = remote.Execute("ls " + pattern, verbose=False)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700276 if not xorg_file:
277 continue
278 xorg_file = xorg_file.strip()
279
280 # extract identifiers
Dennis Kempind0b722a2014-04-15 11:54:48 -0700281 print "Selecting Xorg identifiers from", xorg_file
282 conf = remote.Read(xorg_file)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700283 all_ids = []
Dennis Kempin58d9a742014-05-08 18:34:59 -0700284 for match in re.finditer("Identifier\\s+\"([a-zA-Z0-9-_ ]+)\"", conf):
Dennis Kempincd7caba2014-04-16 13:37:18 -0700285 all_ids.append(match.group(1))
Dennis Kempind0b722a2014-04-15 11:54:48 -0700286
Dennis Kempincd7caba2014-04-16 13:37:18 -0700287 # ask user to select
Dennis Kempin58d9a742014-05-08 18:34:59 -0700288 idxs = AskUser.SelectMulti(
289 all_ids, "Which xorg identifiers apply to this device?",
290 allow_none=True)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700291 ids = [all_ids[i] for i in idxs]
292 if ids:
293 break
Dennis Kempind0b722a2014-04-15 11:54:48 -0700294
Dennis Kempind0b722a2014-04-15 11:54:48 -0700295 if not ids:
Dennis Kempincd7caba2014-04-16 13:37:18 -0700296 print "Please configure the platform properties manually"
297 xorg_file = "todo: add correct xorg conf file"
298 ids = ["todo: add correct xorg identifier"]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700299
Dennis Kempin58d9a742014-05-08 18:34:59 -0700300 ids_string = "[" + ", ".join(["\"%s\"" % i for i in ids]) + "]"
Dennis Kempind0b722a2014-04-15 11:54:48 -0700301 xorg_file = os.path.basename(xorg_file)
302
303 sys.stdout.write("Please name this platform: ")
304 sys.stdout.flush()
305 platform_name = sys.stdin.readline().strip()
306
307 # write platform info to files
308 hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
309 props_file = os.path.join(platforms_dir, platform_name + ".props")
310
311 open(hwprops_file, "w").write(hwprops)
312 open(props_file, "w").write(props_template % (xorg_file, ids_string))
313
314 print "Created files: "
315 print " ", hwprops_file
316 print " ", props_file
317
318 return platform_name