blob: 38597ba5b62ea8017d0577e2b30f2aa222f638a6 [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
Harry Cutts69fc2be2020-01-22 18:03:21 -08007from __future__ import absolute_import
8from __future__ import division
Harry Cutts0edf1572020-01-21 15:42:10 -08009from __future__ import print_function
10
Dennis Kempin3cb7b902015-04-06 11:12:40 -070011from collections import namedtuple
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070012import json
Dennis Kempin037675e2013-06-14 14:12:39 -070013import os
14import re
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070015import sys
Dennis Kempin037675e2013-06-14 14:12:39 -070016
Harry Cutts69fc2be2020-01-22 18:03:21 -080017from .cros_remote import CrOSRemote
18from .util import AskUser, ExecuteException
19from .xorg_conf import XorgInputClassParser
20from functools import reduce
Dennis Kempin3cb7b902015-04-06 11:12:40 -070021
Dennis Kempin037675e2013-06-14 14:12:39 -070022# path to current script directory
23script_dir = os.path.dirname(os.path.realpath(__file__))
Dennis Kempin17766a62013-06-17 14:09:33 -070024platforms_dir = os.path.realpath(os.path.join(script_dir, '..', 'platforms'))
Chung-yih Wang35613f12014-04-25 13:57:23 +080025xorg_conf_project_path = 'src/platform/xorg-conf'
Dennis Kempin037675e2013-06-14 14:12:39 -070026
Dennis Kempind0b722a2014-04-15 11:54:48 -070027props_template = """\
28{
29 "gestures": {
30 },
31 "xorg": {
32 "file": "%s",
33 "identifiers": %s
34 },
35 "ignore": [
36 ]
37}"""
38
Dennis Kempin3cb7b902015-04-06 11:12:40 -070039AbsInfo = namedtuple("AbsInfo", ("min", "max", "res"))
40
Dennis Kempin037675e2013-06-14 14:12:39 -070041class PlatformProperties(object):
Dennis Kempin17766a62013-06-17 14:09:33 -070042 """ A class containing hardware and xorg properties for a platform.
43
44 The class can be created from an activity log or by providing
45 the name of the platform. Information will then be read from the
46 'platforms_dir' directory.
Dennis Kempin037675e2013-06-14 14:12:39 -070047 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070048 def __init__(self, platform=None, log=None):
Dennis Kempin3cb7b902015-04-06 11:12:40 -070049 self.absinfos = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070050 self.device_class = "touchpad"
Dennis Kempin58d9a742014-05-08 18:34:59 -070051 self.properties = {}
52 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070053 if platform:
54 basename = os.path.join(platforms_dir, platform)
55 self.name = platform
Dennis Kempin17766a62013-06-17 14:09:33 -070056 self.hwprops_file = basename + '.hwprops'
57 self.props_file = basename + '.props'
Dennis Kempin037675e2013-06-14 14:12:39 -070058 self.xorg_parser = XorgInputClassParser()
59 self._ParseHWProperties(open(self.hwprops_file).read())
60 self._ParseProperties(open(self.props_file).read())
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070061 elif log:
Dennis Kempin17766a62013-06-17 14:09:33 -070062 self.name = ''
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070063 if log.evdev:
64 self._ParseEvdevLog(log.evdev)
Dennis Kempin037675e2013-06-14 14:12:39 -070065
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070066 def _ParseEvdevLog(self, evdev_data):
Dennis Kempin143ef162014-04-09 13:46:50 -070067 # Look for embedded hwproperties in header. Format:
68 # absinfo: axis min max 0 0 res
Dennis Kempin3cb7b902015-04-06 11:12:40 -070069 values_regex = 6 * ' ([-0-9]+)'
70 abs_regex = re.compile('# absinfo:' + values_regex)
71 for match in abs_regex.finditer(evdev_data):
72 axis = int(match.group(1))
73 info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
74 res=int(match.group(6)))
75 self.absinfos[axis] = info
Dennis Kempin143ef162014-04-09 13:46:50 -070076
Dennis Kempin037675e2013-06-14 14:12:39 -070077 def _ParseHWProperties(self, data):
Dennis Kempin58d9a742014-05-08 18:34:59 -070078 """Parse x and y dimensions and resolution from hwprops file."""
Dennis Kempin3cb7b902015-04-06 11:12:40 -070079 values_regex = 6 * ' ([0-9\\-a-fA-F]+)'
80 abs_regex = re.compile('A:' + values_regex)
81 for match in abs_regex.finditer(data):
82 axis = int(match.group(1), 16)
83 info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
84 res=int(match.group(6)))
85 self.absinfos[axis] = info
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070086
Dennis Kempin037675e2013-06-14 14:12:39 -070087 def _ParseProperties(self, data):
Dennis Kempin17766a62013-06-17 14:09:33 -070088 """ Parse properties from file and inject xorg properties. """
Dennis Kempin037675e2013-06-14 14:12:39 -070089 self.properties = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070090 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070091 data = json.loads(data)
92
Dennis Kempin17766a62013-06-17 14:09:33 -070093 if 'gestures' in data:
94 self.properties.update(data['gestures'])
Dennis Kempin037675e2013-06-14 14:12:39 -070095
Dennis Kempin17766a62013-06-17 14:09:33 -070096 if 'device_class' in data:
97 self.device_class = data['device_class']
Dennis Kempin037675e2013-06-14 14:12:39 -070098
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070099 if 'ignore' in data:
100 self.ignore_properties.extend(data['ignore'])
101
Chung-yih Wang35613f12014-04-25 13:57:23 +0800102 # Sanity check: Make sure it is not used inside ebuild.
103 if os.environ.get('PN') and os.environ.get('S'):
104 raise Exception("Should not be executed inside ebuild")
105
106 # If run on a Chromebook device, access xorg-conf files from their normal
107 # installed location. If run from inside chroot, access xorg-conf files
108 # from the xorg-conf project repository.
109 src_root = os.environ.get('CROS_WORKON_SRCROOT')
110 if src_root:
111 xorg_conf_path = os.path.join(src_root, xorg_conf_project_path)
112 else:
Andrew de los Reyesc0125972015-01-28 10:04:44 -0800113 xorg_conf_path = '/etc/gesture'
114 if not os.path.exists(xorg_conf_path):
115 xorg_conf_path = '/etc/X11/xorg.conf.d'
Chung-yih Wang35613f12014-04-25 13:57:23 +0800116
Dennis Kempin17766a62013-06-17 14:09:33 -0700117 if xorg_conf_path and 'xorg' in data and 'file' in data['xorg']:
118 filename = os.path.join(xorg_conf_path, data['xorg']['file'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700119 input_classes = self.xorg_parser.Parse(file=filename)
Dennis Kempin17766a62013-06-17 14:09:33 -0700120 if 'identifier' in data['xorg']:
121 properties = input_classes[data['xorg']['identifier']]
Dennis Kempin037675e2013-06-14 14:12:39 -0700122 self.properties.update(properties)
Dennis Kempin17766a62013-06-17 14:09:33 -0700123 if 'identifiers' in data['xorg']:
124 for identifier in data['xorg']['identifiers']:
Dennis Kempin037675e2013-06-14 14:12:39 -0700125 properties = input_classes[identifier]
126 self.properties.update(properties)
127
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700128 for prop in self.ignore_properties:
129 if prop in self.properties:
130 del self.properties[prop]
131
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700132 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700133 """ Compare properties and return similarity.
134
135 Compare these properties to another PlatformProperties instance.
136 The return value is a score between 1. 0 meaning there is a big mismatch
137 and 1 meaning the properties match completely.
138 Only a selected range of properties are compared in order to
139 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700140 """
141 scores = []
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700142 def compare_absinfo_prop(a, b, field):
143 a_value = float(getattr(a, field)) if a else None
144 b_value = float(getattr(b, field)) if b else None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700145
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700146 score = 0.0
147 if a_value is not None and b_value is not None:
148 delta = abs(float(a_value) - float(b_value))
149 if delta > 0:
150 score = 1.0 - min(1.0, delta / max(abs(a_value), abs(b_value)))
151 else:
152 score = 1.0
153 scores.append(score)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700154
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700155 a_str = str(a_value) if a_value is not None else "N/A"
156 b_str = str(b_value) if b_value is not None else "N/A"
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700157 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800158 print(" %s: %.2f (%s == %s)" % (field, score, a_str, b_str))
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700159
Harry Cutts87cc6002020-02-04 15:50:20 -0800160 for axis in set().union(self.absinfos.keys(), other.absinfos.keys()):
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700161 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800162 print("axis %d:" % axis)
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700163 self_absinfo = self.absinfos.get(axis, None)
164 other_absinfo = other.absinfos.get(axis, None)
165 compare_absinfo_prop(self_absinfo, other_absinfo, "min")
166 compare_absinfo_prop(self_absinfo, other_absinfo, "max")
167 compare_absinfo_prop(self_absinfo, other_absinfo, "res")
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700168
Dennis Kempin58d9a742014-05-08 18:34:59 -0700169 return reduce(lambda x, y: (x * y), scores)
Dennis Kempin037675e2013-06-14 14:12:39 -0700170
171class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700172 """ Class for managing platforms.
173
174 This class reads all available platforms from the platforms_dir and allows
175 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700176 """
177 def __init__(self):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700178 platform_files = [f for f in os.listdir(platforms_dir)
179 if f.endswith('.hwprops')]
Dennis Kempin037675e2013-06-14 14:12:39 -0700180 self.platforms = {}
Dennis Kempin58d9a742014-05-08 18:34:59 -0700181 for filename in platform_files:
182 name = filename.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700183 self.platforms[name] = PlatformProperties(platform=name)
184
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700185 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700186 """ Find platform matching activity_data.
187
188 Returns the PlatformProperties instance of the platform matching
189 the activity log data. This method might terminate the program in
190 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700191 """
192 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700193 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700194 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700195 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800196 print("-" * 30, name)
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700197 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700198 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800199 print(name, "score =", score)
Andrew de los Reyesf56af272014-07-21 14:32:21 -0700200 if score > 0.96:
Dennis Kempin037675e2013-06-14 14:12:39 -0700201 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700202 if loose:
203 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800204 print("-" * 10, "Multiple matches. Try strict")
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700205 return self.FindMatching(log, False, debug)
Harry Cutts0edf1572020-01-21 15:42:10 -0800206 print(("multiple matching platforms:", result.name,
207 "and", platform.name))
Dennis Kempin19e972b2013-06-20 13:21:38 -0700208 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700209 result = platform
210 if not result:
Harry Cutts0edf1572020-01-21 15:42:10 -0800211 print("cannot find matching platform")
Dennis Kempin19e972b2013-06-20 13:21:38 -0700212 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700213 return result
Dennis Kempind0b722a2014-04-15 11:54:48 -0700214
215 @staticmethod
216 def RegisterPlatformFromDevice(ip):
217 # get list of multitouch devices
218 remote = CrOSRemote(ip)
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400219 inputcontrolexist = True
220 try:
221 devices = remote.SafeExecute(
222 "/opt/google/input/inputcontrol -t multitouch --names",
223 verbose=True)
224 except ExecuteException:
225 inputcontrolexist = False
226 if inputcontrolexist:
227 # Each line has the format:
228 # id: Device Name
229 # devices[*][0] will have the id
230 # devices[*][1] will have the name
231 devices = devices.splitlines()
232 devices = [l.split(":", 1) for l in devices]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700233
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400234 # select one device from list
235 idx = AskUser.Select([d[1] for d in devices],
236 "Which device would you like to register?")
237 device_id = devices[idx][0]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700238
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400239 # read hardware properties
240 hwprops = remote.SafeExecute(
241 "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
242 verbose=True)
243 else:
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700244 event_devices = remote.SafeExecute(
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400245 "ls /dev/input/ | grep event",
246 verbose=True)
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700247 event_devices = event_devices.splitlines()
248 devices = []
249 for d in event_devices:
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400250 device_desc_evemu = remote.SafeExecute(
251 "evemu-describe /dev/input/%s |grep N:" % d,
252 verbose=False)
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700253 event_number = int((d.split("event", 1))[1])
Amirhossein Simjourbcc37012015-07-07 14:59:56 -0400254 device_desc_evemu = device_desc_evemu.split("N:",1)[1]
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700255
256 devices.append([event_number, device_desc_evemu])
257
258 idx = AskUser.Select([t[1] for t in devices],
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400259 "Which device would you like to register?")
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700260 device_id = devices[idx][0]
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400261 hwprops = remote.SafeExecute(
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700262 "evemu-describe /dev/input/event%s" % devices[idx][0],
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400263 verbose=True)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700264 if not hwprops:
Harry Cutts0edf1572020-01-21 15:42:10 -0800265 print("Please update your device to latest canary or:")
266 print(" emerge-${BOARD} inputcontrol")
267 print(" cros deploy $DEVICE_IP inputcontrol")
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400268 return None
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700269
Dennis Kempind0b722a2014-04-15 11:54:48 -0700270
Dennis Kempincd7caba2014-04-16 13:37:18 -0700271 xorg_files = [
Dennis Kempin58d9a742014-05-08 18:34:59 -0700272 "/etc/X11/xorg.conf.d/60-touchpad-cmt-*.conf",
273 "/etc/X11/xorg.conf.d/50-touchpad-cmt-*.conf",
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400274 "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf",
275 "/etc/gesture/60-touchpad-cmt-*.conf",
276 "/etc/gesture/50-touchpad-cmt-*.conf",
277 "/etc/gesture/40-touchpad-cmt.conf"
Dennis Kempincd7caba2014-04-16 13:37:18 -0700278 ]
279
280 for pattern in xorg_files:
281 # find filename of xorg configuration file
Dennis Kempin58d9a742014-05-08 18:34:59 -0700282 xorg_file = remote.Execute("ls " + pattern, verbose=False)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700283 if not xorg_file:
284 continue
285 xorg_file = xorg_file.strip()
286
287 # extract identifiers
Harry Cutts0edf1572020-01-21 15:42:10 -0800288 print("Selecting Xorg identifiers from", xorg_file)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700289 conf = remote.Read(xorg_file)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700290 all_ids = []
Dennis Kempin58d9a742014-05-08 18:34:59 -0700291 for match in re.finditer("Identifier\\s+\"([a-zA-Z0-9-_ ]+)\"", conf):
Dennis Kempincd7caba2014-04-16 13:37:18 -0700292 all_ids.append(match.group(1))
Dennis Kempind0b722a2014-04-15 11:54:48 -0700293
Dennis Kempincd7caba2014-04-16 13:37:18 -0700294 # ask user to select
Dennis Kempin58d9a742014-05-08 18:34:59 -0700295 idxs = AskUser.SelectMulti(
296 all_ids, "Which xorg identifiers apply to this device?",
297 allow_none=True)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700298 ids = [all_ids[i] for i in idxs]
299 if ids:
300 break
Dennis Kempind0b722a2014-04-15 11:54:48 -0700301
Dennis Kempind0b722a2014-04-15 11:54:48 -0700302 if not ids:
Harry Cutts0edf1572020-01-21 15:42:10 -0800303 print("Please configure the platform properties manually")
Dennis Kempincd7caba2014-04-16 13:37:18 -0700304 xorg_file = "todo: add correct xorg conf file"
305 ids = ["todo: add correct xorg identifier"]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700306
Dennis Kempin58d9a742014-05-08 18:34:59 -0700307 ids_string = "[" + ", ".join(["\"%s\"" % i for i in ids]) + "]"
Dennis Kempind0b722a2014-04-15 11:54:48 -0700308 xorg_file = os.path.basename(xorg_file)
309
310 sys.stdout.write("Please name this platform: ")
311 sys.stdout.flush()
312 platform_name = sys.stdin.readline().strip()
313
314 # write platform info to files
315 hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
316 props_file = os.path.join(platforms_dir, platform_name + ".props")
317
318 open(hwprops_file, "w").write(hwprops)
319 open(props_file, "w").write(props_template % (xorg_file, ids_string))
320
Harry Cutts0edf1572020-01-21 15:42:10 -0800321 print("Created files: ")
322 print(" ", hwprops_file)
323 print(" ", props_file)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700324
325 return platform_name