1/*
2 * Copyright © 2015 Red Hat, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24#include "config.h"
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <libudev.h>
30
31#include "libinput-util.h"
32
33#if HAVE_LIBWACOM
34#include <libwacom/libwacom.h>
35
36static void
37wacom_handle_paired(struct udev_device *device,
38		    int *vendor_id,
39		    int *product_id)
40{
41	WacomDeviceDatabase *db = NULL;
42	WacomDevice *tablet = NULL;
43	const WacomMatch *paired;
44
45	db = libwacom_database_new();
46	if (!db)
47		goto out;
48
49	tablet = libwacom_new_from_usbid(db, *vendor_id, *product_id, NULL);
50	if (!tablet)
51		goto out;
52	paired = libwacom_get_paired_device(tablet);
53	if (!paired)
54		goto out;
55
56	*vendor_id = libwacom_match_get_vendor_id(paired);
57	*product_id = libwacom_match_get_product_id(paired);
58
59out:
60	if (tablet)
61		libwacom_destroy(tablet);
62	if (db)
63		libwacom_database_destroy(db);
64}
65
66static int
67find_tree_distance(struct udev_device *a, struct udev_device *b)
68{
69	struct udev_device *ancestor_a = a;
70	int dist_a = 0;
71
72	while (ancestor_a != NULL) {
73		const char *path_a = udev_device_get_syspath(ancestor_a);
74		struct udev_device *ancestor_b = b;
75		int dist_b = 0;
76
77		while (ancestor_b != NULL) {
78			const char *path_b = udev_device_get_syspath(ancestor_b);
79
80			if (streq(path_a, path_b))
81				return dist_a + dist_b;
82
83			dist_b++;
84			ancestor_b = udev_device_get_parent(ancestor_b);
85		}
86
87		dist_a++;
88		ancestor_a = udev_device_get_parent(ancestor_a);
89	}
90	return -1;
91}
92
93static void
94wacom_handle_ekr(struct udev_device *device,
95		 int *vendor_id,
96		 int *product_id,
97		 char **phys_attr)
98{
99	struct udev *udev;
100	struct udev_enumerate *e;
101	struct udev_list_entry *entry = NULL;
102	int best_dist = -1;
103
104	udev = udev_device_get_udev(device);
105	e = udev_enumerate_new(udev);
106	udev_enumerate_add_match_subsystem(e, "input");
107	udev_enumerate_add_match_sysname(e, "input*");
108	udev_enumerate_scan_devices(e);
109
110	udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
111		struct udev_device *d;
112		const char *path, *phys;
113		const char *pidstr, *vidstr;
114		int pid, vid, dist;
115
116		/* Find and use the closest Wacom device on the system,
117		 * relying on wacom_handle_paired() to fix our ID later
118		 * if needed.
119		 */
120		path = udev_list_entry_get_name(entry);
121		d = udev_device_new_from_syspath(udev, path);
122		if (!d)
123			continue;
124
125		vidstr = udev_device_get_property_value(d, "ID_VENDOR_ID");
126		pidstr = udev_device_get_property_value(d, "ID_MODEL_ID");
127		phys = udev_device_get_sysattr_value(d, "phys");
128
129		if (vidstr && pidstr && phys &&
130		    safe_atoi_base(vidstr, &vid, 16) &&
131		    safe_atoi_base(pidstr, &pid, 16) &&
132		    vid == VENDOR_ID_WACOM &&
133		    pid != PRODUCT_ID_WACOM_EKR) {
134			dist = find_tree_distance(device, d);
135			if (dist > 0 && (dist < best_dist || best_dist < 0)) {
136				*vendor_id = vid;
137				*product_id = pid;
138				best_dist = dist;
139
140				free(*phys_attr);
141				*phys_attr = safe_strdup(phys);
142			}
143		}
144
145		udev_device_unref(d);
146	}
147
148	udev_enumerate_unref(e);
149}
150#endif
151
152int main(int argc, char **argv)
153{
154	int rc = 1;
155	struct udev *udev = NULL;
156	struct udev_device *device = NULL;
157	const char *syspath,
158	           *phys = NULL;
159	const char *product;
160	int bustype, vendor_id, product_id, version;
161	char group[1024];
162	char *str;
163
164	if (argc != 2)
165		return 1;
166
167	syspath = argv[1];
168
169	udev = udev_new();
170	if (!udev)
171		goto out;
172
173	device = udev_device_new_from_syspath(udev, syspath);
174	if (!device)
175		goto out;
176
177	/* Find the first parent with ATTRS{phys} set. For tablets that
178	 * value looks like usb-0000:00:14.0-1/input1. Drop the /input1
179	 * bit and use the remainder as device group identifier */
180	while (device != NULL) {
181		struct udev_device *parent;
182
183		phys = udev_device_get_sysattr_value(device, "phys");
184		if (phys)
185			break;
186
187		parent = udev_device_get_parent(device);
188		udev_device_ref(parent);
189		udev_device_unref(device);
190		device = parent;
191	}
192
193	if (!phys)
194		goto out;
195
196	/* udev sets PRODUCT on the same device we find PHYS on, let's rely
197	   on that*/
198	product = udev_device_get_property_value(device, "PRODUCT");
199	if (!product)
200		product = "00/00/00/00";
201
202	if (sscanf(product,
203		   "%x/%x/%x/%x",
204		   &bustype,
205		   &vendor_id,
206		   &product_id,
207		   &version) != 4) {
208		snprintf(group, sizeof(group), "%s:%s", product, phys);
209	} else {
210	    char *physmatch = NULL;
211
212#if HAVE_LIBWACOM
213	    if (vendor_id == VENDOR_ID_WACOM) {
214		    if (product_id == PRODUCT_ID_WACOM_EKR)
215			    wacom_handle_ekr(device,
216					     &vendor_id,
217					     &product_id,
218					     &physmatch);
219		    /* This is called for the EKR as well */
220		    wacom_handle_paired(device,
221					&vendor_id,
222					&product_id);
223	    }
224#endif
225	    snprintf(group,
226		     sizeof(group),
227		     "%x/%x/%x:%s",
228		     bustype,
229		     vendor_id,
230		     product_id,
231		     physmatch ? physmatch : phys);
232
233	    free(physmatch);
234	}
235
236	str = strstr(group, "/input");
237	if (str)
238		*str = '\0';
239
240	/* Cintiq 22HD Touch has
241	   usb-0000:00:14.0-6.3.1/input0 for the touch
242	   usb-0000:00:14.0-6.3.0/input0 for the pen
243	   Check if there's a . after the last -, if so, cut off the string
244	   there.
245	  */
246	str = strrchr(group, '.');
247	if (str && str > strrchr(group, '-'))
248		*str = '\0';
249
250	printf("LIBINPUT_DEVICE_GROUP=%s\n", group);
251
252	rc = 0;
253out:
254	if (device)
255		udev_device_unref(device);
256	if (udev)
257		udev_unref(udev);
258
259	return rc;
260}
261