blob: 1a2d990497438c4f769bd1fa0dd97c4c1c05be98 [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):
Harry Cuttsc3c057a2020-04-06 17:39:09 -070088 """ Parse properties from file. """
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
Harry Cuttsc3c057a2020-04-06 17:39:09 -0700106 self._ParseXorgConfig(data)
Dennis Kempin037675e2013-06-14 14:12:39 -0700107
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700108 for prop in self.ignore_properties:
109 if prop in self.properties:
110 del self.properties[prop]
111
Harry Cuttsc3c057a2020-04-06 17:39:09 -0700112 def _ParseXorgConfig(self, data):
113 """ Locate and parse the Xorg configuration. """
114 if 'xorg' not in data or 'file' not in data['xorg']:
115 return
116
117 xorg = data['xorg']
118 src_root = os.environ.get('CROS_WORKON_SRCROOT')
119 if src_root:
120 # Running in the chroot.
121 #if 'conf_directory' in xorg:
122 # # Look wherever the config specifies (probably in an overlay).
123 # project_path = xorg['conf_directory']
124 #else:
125 # # Look in the xorg-conf repository.
126 # project_path = xorg_conf_project_path
127 project_path = (
128 xorg['conf_directory'] if 'conf_directory' in xorg
129 else xorg_conf_project_path)
130
131 xorg_conf_path = os.path.join(src_root, project_path)
132 else:
133 # Running on Chrome OS, so look in the normal installed location.
134 xorg_conf_path = '/etc/gesture'
135 if not os.path.exists(xorg_conf_path):
136 xorg_conf_path = '/etc/X11/xorg.conf.d'
137
138 filename = os.path.join(xorg_conf_path, xorg['file'])
139 input_classes = self.xorg_parser.Parse(file=filename)
140 for identifier in xorg['identifiers']:
141 properties = input_classes[identifier]
142 self.properties.update(properties)
143
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700144 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700145 """ Compare properties and return similarity.
146
147 Compare these properties to another PlatformProperties instance.
148 The return value is a score between 1. 0 meaning there is a big mismatch
149 and 1 meaning the properties match completely.
150 Only a selected range of properties are compared in order to
151 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700152 """
153 scores = []
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700154 def compare_absinfo_prop(a, b, field):
155 a_value = float(getattr(a, field)) if a else None
156 b_value = float(getattr(b, field)) if b else None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700157
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700158 score = 0.0
159 if a_value is not None and b_value is not None:
160 delta = abs(float(a_value) - float(b_value))
161 if delta > 0:
162 score = 1.0 - min(1.0, delta / max(abs(a_value), abs(b_value)))
163 else:
164 score = 1.0
165 scores.append(score)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700166
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700167 a_str = str(a_value) if a_value is not None else "N/A"
168 b_str = str(b_value) if b_value is not None else "N/A"
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700169 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800170 print(" %s: %.2f (%s == %s)" % (field, score, a_str, b_str))
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700171
Harry Cutts87cc6002020-02-04 15:50:20 -0800172 for axis in set().union(self.absinfos.keys(), other.absinfos.keys()):
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700173 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800174 print("axis %d:" % axis)
Dennis Kempin3cb7b902015-04-06 11:12:40 -0700175 self_absinfo = self.absinfos.get(axis, None)
176 other_absinfo = other.absinfos.get(axis, None)
177 compare_absinfo_prop(self_absinfo, other_absinfo, "min")
178 compare_absinfo_prop(self_absinfo, other_absinfo, "max")
179 compare_absinfo_prop(self_absinfo, other_absinfo, "res")
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700180
Dennis Kempin58d9a742014-05-08 18:34:59 -0700181 return reduce(lambda x, y: (x * y), scores)
Dennis Kempin037675e2013-06-14 14:12:39 -0700182
183class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700184 """ Class for managing platforms.
185
186 This class reads all available platforms from the platforms_dir and allows
187 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700188 """
189 def __init__(self):
Dennis Kempin58d9a742014-05-08 18:34:59 -0700190 platform_files = [f for f in os.listdir(platforms_dir)
191 if f.endswith('.hwprops')]
Dennis Kempin037675e2013-06-14 14:12:39 -0700192 self.platforms = {}
Dennis Kempin58d9a742014-05-08 18:34:59 -0700193 for filename in platform_files:
194 name = filename.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700195 self.platforms[name] = PlatformProperties(platform=name)
196
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700197 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700198 """ Find platform matching activity_data.
199
200 Returns the PlatformProperties instance of the platform matching
201 the activity log data. This method might terminate the program in
202 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700203 """
204 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700205 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700206 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700207 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800208 print("-" * 30, name)
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700209 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700210 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800211 print(name, "score =", score)
Andrew de los Reyesf56af272014-07-21 14:32:21 -0700212 if score > 0.96:
Dennis Kempin037675e2013-06-14 14:12:39 -0700213 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700214 if loose:
215 if debug:
Harry Cutts0edf1572020-01-21 15:42:10 -0800216 print("-" * 10, "Multiple matches. Try strict")
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700217 return self.FindMatching(log, False, debug)
Harry Cutts0edf1572020-01-21 15:42:10 -0800218 print(("multiple matching platforms:", result.name,
219 "and", platform.name))
Dennis Kempin19e972b2013-06-20 13:21:38 -0700220 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700221 result = platform
222 if not result:
Harry Cutts0edf1572020-01-21 15:42:10 -0800223 print("cannot find matching platform")
Dennis Kempin19e972b2013-06-20 13:21:38 -0700224 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700225 return result
Dennis Kempind0b722a2014-04-15 11:54:48 -0700226
227 @staticmethod
228 def RegisterPlatformFromDevice(ip):
229 # get list of multitouch devices
230 remote = CrOSRemote(ip)
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400231 inputcontrolexist = True
232 try:
233 devices = remote.SafeExecute(
234 "/opt/google/input/inputcontrol -t multitouch --names",
235 verbose=True)
236 except ExecuteException:
237 inputcontrolexist = False
238 if inputcontrolexist:
239 # Each line has the format:
240 # id: Device Name
241 # devices[*][0] will have the id
242 # devices[*][1] will have the name
243 devices = devices.splitlines()
244 devices = [l.split(":", 1) for l in devices]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700245
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400246 # select one device from list
247 idx = AskUser.Select([d[1] for d in devices],
248 "Which device would you like to register?")
249 device_id = devices[idx][0]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700250
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400251 # read hardware properties
252 hwprops = remote.SafeExecute(
253 "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
254 verbose=True)
255 else:
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700256 event_devices = remote.SafeExecute(
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400257 "ls /dev/input/ | grep event",
258 verbose=True)
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700259 event_devices = event_devices.splitlines()
260 devices = []
261 for d in event_devices:
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400262 device_desc_evemu = remote.SafeExecute(
263 "evemu-describe /dev/input/%s |grep N:" % d,
264 verbose=False)
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700265 event_number = int((d.split("event", 1))[1])
Amirhossein Simjourbcc37012015-07-07 14:59:56 -0400266 device_desc_evemu = device_desc_evemu.split("N:",1)[1]
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700267
268 devices.append([event_number, device_desc_evemu])
269
270 idx = AskUser.Select([t[1] for t in devices],
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400271 "Which device would you like to register?")
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700272 device_id = devices[idx][0]
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400273 hwprops = remote.SafeExecute(
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700274 "evemu-describe /dev/input/event%s" % devices[idx][0],
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400275 verbose=True)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700276 if not hwprops:
Harry Cutts0edf1572020-01-21 15:42:10 -0800277 print("Please update your device to latest canary or:")
278 print(" emerge-${BOARD} inputcontrol")
279 print(" cros deploy $DEVICE_IP inputcontrol")
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400280 return None
Charlie Mooney5fe577f2015-08-20 13:22:17 -0700281
Dennis Kempind0b722a2014-04-15 11:54:48 -0700282
Dennis Kempincd7caba2014-04-16 13:37:18 -0700283 xorg_files = [
Dennis Kempin58d9a742014-05-08 18:34:59 -0700284 "/etc/X11/xorg.conf.d/60-touchpad-cmt-*.conf",
285 "/etc/X11/xorg.conf.d/50-touchpad-cmt-*.conf",
Amirhossein Simjour9127ff12015-03-11 15:47:14 -0400286 "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf",
287 "/etc/gesture/60-touchpad-cmt-*.conf",
288 "/etc/gesture/50-touchpad-cmt-*.conf",
289 "/etc/gesture/40-touchpad-cmt.conf"
Dennis Kempincd7caba2014-04-16 13:37:18 -0700290 ]
291
292 for pattern in xorg_files:
293 # find filename of xorg configuration file
Dennis Kempin58d9a742014-05-08 18:34:59 -0700294 xorg_file = remote.Execute("ls " + pattern, verbose=False)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700295 if not xorg_file:
296 continue
297 xorg_file = xorg_file.strip()
298
299 # extract identifiers
Harry Cutts0edf1572020-01-21 15:42:10 -0800300 print("Selecting Xorg identifiers from", xorg_file)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700301 conf = remote.Read(xorg_file)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700302 all_ids = []
Dennis Kempin58d9a742014-05-08 18:34:59 -0700303 for match in re.finditer("Identifier\\s+\"([a-zA-Z0-9-_ ]+)\"", conf):
Dennis Kempincd7caba2014-04-16 13:37:18 -0700304 all_ids.append(match.group(1))
Dennis Kempind0b722a2014-04-15 11:54:48 -0700305
Dennis Kempincd7caba2014-04-16 13:37:18 -0700306 # ask user to select
Dennis Kempin58d9a742014-05-08 18:34:59 -0700307 idxs = AskUser.SelectMulti(
308 all_ids, "Which xorg identifiers apply to this device?",
309 allow_none=True)
Dennis Kempincd7caba2014-04-16 13:37:18 -0700310 ids = [all_ids[i] for i in idxs]
311 if ids:
312 break
Dennis Kempind0b722a2014-04-15 11:54:48 -0700313
Dennis Kempind0b722a2014-04-15 11:54:48 -0700314 if not ids:
Harry Cutts0edf1572020-01-21 15:42:10 -0800315 print("Please configure the platform properties manually")
Dennis Kempincd7caba2014-04-16 13:37:18 -0700316 xorg_file = "todo: add correct xorg conf file"
317 ids = ["todo: add correct xorg identifier"]
Dennis Kempind0b722a2014-04-15 11:54:48 -0700318
Dennis Kempin58d9a742014-05-08 18:34:59 -0700319 ids_string = "[" + ", ".join(["\"%s\"" % i for i in ids]) + "]"
Dennis Kempind0b722a2014-04-15 11:54:48 -0700320 xorg_file = os.path.basename(xorg_file)
321
322 sys.stdout.write("Please name this platform: ")
323 sys.stdout.flush()
324 platform_name = sys.stdin.readline().strip()
325
326 # write platform info to files
327 hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
328 props_file = os.path.join(platforms_dir, platform_name + ".props")
329
330 open(hwprops_file, "w").write(hwprops)
331 open(props_file, "w").write(props_template % (xorg_file, ids_string))
332
Harry Cutts0edf1572020-01-21 15:42:10 -0800333 print("Created files: ")
334 print(" ", hwprops_file)
335 print(" ", props_file)
Dennis Kempind0b722a2014-04-15 11:54:48 -0700336
337 return platform_name