blob: 0f30bf3be9304666516ef40e7a9633ac39779b54 [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. """
Harry Cutts0edf1572020-01-21 15:42:10 -08006
7from __future__ import print_function
8
Dennis Kempin3cb7b902015-04-06 11:12:40 -07009from collections import namedtuple
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
Dennis Kempin3cb7b902015-04-06 11:12:40 -070015from cros_remote import CrOSRemote
16from util import AskUser, ExecuteException
17from xorg_conf import XorgInputClassParser
18
Dennis Kempin037675e2013-06-14 14:12:39 -070019# path to current script directory
20script_dir = os.path.dirname(os.path.realpath(__file__))
Dennis Kempin17766a62013-06-17 14:09:33 -070021platforms_dir = os.path.realpath(os.path.join(script_dir, '..', 'platforms'))
Chung-yih Wang35613f12014-04-25 13:57:23 +080022xorg_conf_project_path = 'src/platform/xorg-conf'
Dennis Kempin037675e2013-06-14 14:12:39 -070023
Dennis Kempind0b722a2014-04-15 11:54:48 -070024props_template = """\
25{
26 "gestures": {
27 },
28 "xorg": {
29 "file": "%s",
30 "identifiers": %s
31 },
32 "ignore": [
33 ]
34}"""
35
Dennis Kempin3cb7b902015-04-06 11:12:40 -070036AbsInfo = namedtuple("AbsInfo", ("min", "max", "res"))
37
Dennis Kempin037675e2013-06-14 14:12:39 -070038class PlatformProperties(object):
Dennis Kempin17766a62013-06-17 14:09:33 -070039 """ A class containing hardware and xorg properties for a platform.
40
41 The class can be created from an activity log or by providing
42 the name of the platform. Information will then be read from the
43 'platforms_dir' directory.
Dennis Kempin037675e2013-06-14 14:12:39 -070044 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070045 def __init__(self, platform=None, log=None):
Dennis Kempin3cb7b902015-04-06 11:12:40 -070046 self.absinfos = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070047 self.device_class = "touchpad"
Dennis Kempin58d9a742014-05-08 18:34:59 -070048 self.properties = {}
49 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070050 if platform:
51 basename = os.path.join(platforms_dir, platform)
52 self.name = platform
Dennis Kempin17766a62013-06-17 14:09:33 -070053 self.hwprops_file = basename + '.hwprops'
54 self.props_file = basename + '.props'
Dennis Kempin037675e2013-06-14 14:12:39 -070055 self.xorg_parser = XorgInputClassParser()
56 self._ParseHWProperties(open(self.hwprops_file).read())
57 self._ParseProperties(open(self.props_file).read())
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070058 elif log:
Dennis Kempin17766a62013-06-17 14:09:33 -070059 self.name = ''
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070060 if log.evdev:
61 self._ParseEvdevLog(log.evdev)
Dennis Kempin037675e2013-06-14 14:12:39 -070062
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070063 def _ParseEvdevLog(self, evdev_data):
Dennis Kempin143ef162014-04-09 13:46:50 -070064 # Look for embedded hwproperties in header. Format:
65 # absinfo: axis min max 0 0 res
Dennis Kempin3cb7b902015-04-06 11:12:40 -070066 values_regex = 6 * ' ([-0-9]+)'
67 abs_regex = re.compile('# absinfo:' + values_regex)
68 for match in abs_regex.finditer(evdev_data):
69 axis = int(match.group(1))
70 info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
71 res=int(match.group(6)))
72 self.absinfos[axis] = info
Dennis Kempin143ef162014-04-09 13:46:50 -070073
Dennis Kempin037675e2013-06-14 14:12:39 -070074 def _ParseHWProperties(self, data):
Dennis Kempin58d9a742014-05-08 18:34:59 -070075 """Parse x and y dimensions and resolution from hwprops file."""
Dennis Kempin3cb7b902015-04-06 11:12:40 -070076 values_regex = 6 * ' ([0-9\\-a-fA-F]+)'
77 abs_regex = re.compile('A:' + values_regex)
78 for match in abs_regex.finditer(data):
79 axis = int(match.group(1), 16)
80 info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
81 res=int(match.group(6)))
82 self.absinfos[axis] = info
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070083
Dennis Kempin037675e2013-06-14 14:12:39 -070084 def _ParseProperties(self, data):
Dennis Kempin17766a62013-06-17 14:09:33 -070085 """ Parse properties from file and inject xorg properties. """
Dennis Kempin037675e2013-06-14 14:12:39 -070086 self.properties = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070087 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070088 data = json.loads(data)
89
Dennis Kempin17766a62013-06-17 14:09:33 -070090 if 'gestures' in data:
91 self.properties.update(data['gestures'])
Dennis Kempin037675e2013-06-14 14:12:39 -070092
Dennis Kempin17766a62013-06-17 14:09:33 -070093 if 'device_class' in data:
94 self.device_class = data['device_class']
Dennis Kempin037675e2013-06-14 14:12:39 -070095
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070096 if 'ignore' in data:
97 self.ignore_properties.extend(data['ignore'])
98
Chung-yih Wang35613f12014-04-25 13:57:23 +080099 # Sanity check: Make sure it is not used inside ebuild.
100 if os.environ.get('PN') and os.environ.get('S'):
101 raise Exception("Should not be executed inside ebuild")
102
103 # If run on a Chromebook device, access xorg-conf files from their normal
104 # installed location. If run from inside chroot, access xorg-conf files
105 # from the xorg-conf project repository.
106 src_root = os.environ.get('CROS_WORKON_SRCROOT')
107 if src_root:
108 xorg_conf_path = os.path.join(src_root, xorg_conf_project_path)
109 else:
Andrew de los Reyesc0125972015-01-28 10:04:44 -0800110 xorg_conf_path = '/etc/gesture'
111 if not os.path.exists(xorg_conf_path):
112 xorg_conf_path = '/etc/X11/xorg.conf.d'
Chung-yih Wang35613f12014-04-25 13:57:23 +0800113
Dennis Kempin17766a62013-06-17 14:09:33 -0700114 if xorg_conf_path and 'xorg' in data and 'file' in data['xorg']:
115 filename = os.path.join(xorg_conf_path, data['xorg']['file'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700116 input_classes = self.xorg_parser.Parse(file=filename)
Dennis Kempin17766a62013-06-17 14:09:33 -0700117 if 'identifier' in data['xorg']:
118 properties = input_classes[data['xorg']['identifier']]
Dennis Kempin037675e2013-06-14 14:12:39 -0700119 self.properties.update(properties)
Dennis Kempin17766a62013-06-17 14:09:33 -0700120 if 'identifiers' in data['xorg']:
121 for identifier in data['xorg']['identifiers']:
Dennis Kempin037675e2013-06-14 14:12:39 -0700122 properties = input_classes[identifier]
123 self.properties.update(properties)
124
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700125 for prop in self.ignore_properties:
126 if prop in self.properties:
127 del self.properties[prop]
128
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700129 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700130 """ Compare properties and return similarity.
131
132 Compare these properties to another PlatformProperties instance.
133 The return value is a score between 1. 0 meaning there is a big mismatch
134 and 1 meaning the properties match completely.
135 Only a selected range of properties are compared in order to
136 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700137 """
138 scores = []
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700139 def compare_absinfo_prop(a, b, field):
140 a_value = float(getattr(a, field)) if a else None
141 b_value = float(getattr(b, field)) if b else None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700142
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700143 score = 0.0
144 if a_value is not None and b_value is not None:
145 delta = abs(float(a_value) - float(b_value))
146 if delta > 0:
147 score = 1.0 - min(1.0, delta / max(abs(a_value), abs(b_value)))
148 else:
149 score = 1.0
150 scores.append(score)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700151
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700152 a_str = str(a_value) if a_value is not None else "N/A"
153 b_str = str(b_value) if b_value is not None else "N/A"
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700154 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800155 print(" %s: %.2f (%s == %s)" % (field, score, a_str, b_str))
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700156
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700157 for axis in set(self.absinfos.keys() + other.absinfos.keys()):
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700158 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800159 print("axis %d:" % axis)
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700160 self_absinfo = self.absinfos.get(axis, None)
161 other_absinfo = other.absinfos.get(axis, None)
162 compare_absinfo_prop(self_absinfo, other_absinfo, "min")
163 compare_absinfo_prop(self_absinfo, other_absinfo, "max")
164 compare_absinfo_prop(self_absinfo, other_absinfo, "res")
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700165
Dennis Kempin58d9a742014-05-08 18:34:59 -0700166 return reduce(lambda x, y: (x * y), scores)
Dennis Kempin037675e2013-06-14 14:12:39 -0700167
168class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700169 """ Class for managing platforms.
170
171 This class reads all available platforms from the platforms_dir and allows
172 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700173 """
174 def __init__(self):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700175 platform_files = [f for f in os.listdir(platforms_dir)
176 if f.endswith('.hwprops')]
Dennis Kempin037675e2013-06-14 14:12:39 -0700177 self.platforms = {}
Dennis Kempin58d9a742014-05-08 18:34:59 -0700178 for filename in platform_files:
179 name = filename.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700180 self.platforms[name] = PlatformProperties(platform=name)
181
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700182 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700183 """ Find platform matching activity_data.
184
185 Returns the PlatformProperties instance of the platform matching
186 the activity log data. This method might terminate the program in
187 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700188 """
189 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700190 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700191 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700192 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800193 print("-" * 30, name)
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700194 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700195 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800196 print(name, "score =", score)
Andrew de los Reyesf56af272014-07-21 14:32:21 -0700197 if score > 0.96:
Dennis Kempin037675e2013-06-14 14:12:39 -0700198 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700199 if loose:
200 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800201 print("-" * 10, "Multiple matches. Try strict")
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700202 return self.FindMatching(log, False, debug)
Harry Cutts0edf1572020-01-21 15:42:10 -0800203 print(("multiple matching platforms:", result.name,
204 "and", platform.name))
Dennis Kempin19e972b2013-06-20 13:21:38 -0700205 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700206 result = platform
207 if not result:
Harry Cutts0edf1572020-01-21 15:42:10 -0800208 print("cannot find matching platform")
Dennis Kempin19e972b2013-06-20 13:21:38 -0700209 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700210 return result
Dennis Kempind0b722a2014-04-15 11:54:48 -0700211
212 @staticmethod
213 def RegisterPlatformFromDevice(ip):
214 # get list of multitouch devices
215 remote = CrOSRemote(ip)
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400216 inputcontrolexist = True
217 try:
218 devices = remote.SafeExecute(
219 "/opt/google/input/inputcontrol -t multitouch --names",
220 verbose=True)
221 except ExecuteException:
222 inputcontrolexist = False
223 if inputcontrolexist:
224 # Each line has the format:
225 # id: Device Name
226 # devices[*][0] will have the id
227 # devices[*][1] will have the name
228 devices = devices.splitlines()
229 devices = [l.split(":", 1) for l in devices]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700230
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400231 # select one device from list
232 idx = AskUser.Select([d[1] for d in devices],
233 "Which device would you like to register?")
234 device_id = devices[idx][0]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700235
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400236 # read hardware properties
237 hwprops = remote.SafeExecute(
238 "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
239 verbose=True)
240 else:
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700241 event_devices = remote.SafeExecute(
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400242 "ls /dev/input/ | grep event",
243 verbose=True)
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700244 event_devices = event_devices.splitlines()
245 devices = []
246 for d in event_devices:
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400247 device_desc_evemu = remote.SafeExecute(
248 "evemu-describe /dev/input/%s |grep N:" % d,
249 verbose=False)
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700250 event_number = int((d.split("event", 1))[1])
Amirhossein Simjourbcc37012015-07-07 14:59:56 -0400251 device_desc_evemu = device_desc_evemu.split("N:",1)[1]
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700252
253 devices.append([event_number, device_desc_evemu])
254
255 idx = AskUser.Select([t[1] for t in devices],
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400256 "Which device would you like to register?")
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700257 device_id = devices[idx][0]
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400258 hwprops = remote.SafeExecute(
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700259 "evemu-describe /dev/input/event%s" % devices[idx][0],
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400260 verbose=True)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700261 if not hwprops:
Harry Cutts0edf1572020-01-21 15:42:10 -0800262 print("Please update your device to latest canary or:")
263 print(" emerge-${BOARD} inputcontrol")
264 print(" cros deploy $DEVICE_IP inputcontrol")
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400265 return None
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700266
Dennis Kempind0b722a2014-04-15 11:54:48 -0700267
Dennis Kempincd7caba2014-04-16 13:37:18 -0700268 xorg_files = [
Dennis Kempin58d9a742014-05-08 18:34:59 -0700269 "/etc/X11/xorg.conf.d/60-touchpad-cmt-*.conf",
270 "/etc/X11/xorg.conf.d/50-touchpad-cmt-*.conf",
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400271 "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf",
272 "/etc/gesture/60-touchpad-cmt-*.conf",
273 "/etc/gesture/50-touchpad-cmt-*.conf",
274 "/etc/gesture/40-touchpad-cmt.conf"
Dennis Kempincd7caba2014-04-16 13:37:18 -0700275 ]
276
277 for pattern in xorg_files:
278 # find filename of xorg configuration file
Dennis Kempin58d9a742014-05-08 18:34:59 -0700279 xorg_file = remote.Execute("ls " + pattern, verbose=False)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700280 if not xorg_file:
281 continue
282 xorg_file = xorg_file.strip()
283
284 # extract identifiers
Harry Cutts0edf1572020-01-21 15:42:10 -0800285 print("Selecting Xorg identifiers from", xorg_file)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700286 conf = remote.Read(xorg_file)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700287 all_ids = []
Dennis Kempin58d9a742014-05-08 18:34:59 -0700288 for match in re.finditer("Identifier\\s+\"([a-zA-Z0-9-_ ]+)\"", conf):
Dennis Kempincd7caba2014-04-16 13:37:18 -0700289 all_ids.append(match.group(1))
Dennis Kempind0b722a2014-04-15 11:54:48 -0700290
Dennis Kempincd7caba2014-04-16 13:37:18 -0700291 # ask user to select
Dennis Kempin58d9a742014-05-08 18:34:59 -0700292 idxs = AskUser.SelectMulti(
293 all_ids, "Which xorg identifiers apply to this device?",
294 allow_none=True)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700295 ids = [all_ids[i] for i in idxs]
296 if ids:
297 break
Dennis Kempind0b722a2014-04-15 11:54:48 -0700298
Dennis Kempind0b722a2014-04-15 11:54:48 -0700299 if not ids:
Harry Cutts0edf1572020-01-21 15:42:10 -0800300 print("Please configure the platform properties manually")
Dennis Kempincd7caba2014-04-16 13:37:18 -0700301 xorg_file = "todo: add correct xorg conf file"
302 ids = ["todo: add correct xorg identifier"]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700303
Dennis Kempin58d9a742014-05-08 18:34:59 -0700304 ids_string = "[" + ", ".join(["\"%s\"" % i for i in ids]) + "]"
Dennis Kempind0b722a2014-04-15 11:54:48 -0700305 xorg_file = os.path.basename(xorg_file)
306
307 sys.stdout.write("Please name this platform: ")
308 sys.stdout.flush()
309 platform_name = sys.stdin.readline().strip()
310
311 # write platform info to files
312 hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
313 props_file = os.path.join(platforms_dir, platform_name + ".props")
314
315 open(hwprops_file, "w").write(hwprops)
316 open(props_file, "w").write(props_template % (xorg_file, ids_string))
317
Harry Cutts0edf1572020-01-21 15:42:10 -0800318 print("Created files: ")
319 print(" ", hwprops_file)
320 print(" ", props_file)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700321
322 return platform_name