1/*
2 * Copyright © 2009 Julien Cristau
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 * Author: Julien Cristau <jcristau@debian.org>
24 */
25
26#ifdef HAVE_DIX_CONFIG_H
27#include <dix-config.h>
28#endif
29
30#include <libudev.h>
31#include <ctype.h>
32
33#include "input.h"
34#include "inputstr.h"
35#include "hotplug.h"
36#include "config-backends.h"
37#include "os.h"
38
39#define UDEV_XKB_PROP_KEY "xkb"
40
41#define LOG_PROPERTY(path, prop, val)                                   \
42    LogMessageVerb(X_INFO, 10,                                          \
43                   "config/udev: getting property %s on %s "            \
44                   "returned \"%s\"\n",                                 \
45                   (prop), (path), (val) ? (val) : "(null)")
46#define LOG_SYSATTR(path, attr, val)                                    \
47    LogMessageVerb(X_INFO, 10,                                          \
48                   "config/udev: getting attribute %s on %s "           \
49                   "returned \"%s\"\n",                                 \
50                   (attr), (path), (val) ? (val) : "(null)")
51
52static struct udev_monitor *udev_monitor;
53
54static void
55device_added(struct udev_device *udev_device)
56{
57    const char *path, *name = NULL;
58    char *config_info = NULL;
59    const char *syspath;
60    const char *tags_prop;
61    const char *key, *value, *tmp;
62    InputOption *options = NULL, *tmpo;
63    InputAttributes attrs = {};
64    DeviceIntPtr dev = NULL;
65    struct udev_list_entry *set, *entry;
66    struct udev_device *parent;
67    int rc;
68
69    path = udev_device_get_devnode(udev_device);
70
71    syspath = udev_device_get_syspath(udev_device);
72
73    if (!path || !syspath)
74        return;
75
76    if (!udev_device_get_property_value(udev_device, "ID_INPUT")) {
77        LogMessageVerb(X_INFO, 10,
78                       "config/udev: ignoring device %s without "
79                       "property ID_INPUT set\n",
80                       path);
81        return;
82    }
83
84    options = calloc(sizeof(*options), 1);
85    if (!options)
86        return;
87
88    options->key = strdup("_source");
89    options->value = strdup("server/udev");
90    if (!options->key || !options->value)
91        goto unwind;
92
93    parent = udev_device_get_parent(udev_device);
94    if (parent) {
95        const char *ppath = udev_device_get_devnode(parent);
96        const char *product = udev_device_get_property_value(parent, "PRODUCT");
97        const char *pnp_id = udev_device_get_sysattr_value(parent, "id");
98        unsigned int usb_vendor, usb_model;
99
100        name = udev_device_get_sysattr_value(parent, "name");
101        LOG_SYSATTR(ppath, "name", name);
102        if (!name) {
103            name = udev_device_get_property_value(parent, "NAME");
104            LOG_PROPERTY(ppath, "NAME", name);
105        }
106
107        if (pnp_id)
108            attrs.pnp_id = strdup(pnp_id);
109        LOG_SYSATTR(ppath, "id", pnp_id);
110
111        /* construct USB ID in lowercase hex - "0000:ffff" */
112        if (product && sscanf(product, "%*x/%4x/%4x/%*x", &usb_vendor, &usb_model) == 2) {
113            if (asprintf(&attrs.usb_id, "%04x:%04x", usb_vendor, usb_model)
114                == -1)
115                attrs.usb_id = NULL;
116            else
117                LOG_PROPERTY(path, "PRODUCT", product);
118        }
119    }
120    if (!name)
121        name = "(unnamed)";
122    else
123        attrs.product = strdup(name);
124    add_option(&options, "name", name);
125
126    add_option(&options, "path", path);
127    add_option(&options, "device", path);
128    if (path)
129        attrs.device = strdup(path);
130
131    tags_prop = udev_device_get_property_value(udev_device, "ID_INPUT.tags");
132    LOG_PROPERTY(path, "ID_INPUT.tags", tags_prop);
133    attrs.tags = xstrtokenize(tags_prop, ",");
134
135    if (asprintf(&config_info, "udev:%s", syspath) == -1) {
136        config_info = NULL;
137        goto unwind;
138    }
139
140    if (device_is_duplicate(config_info)) {
141        LogMessage(X_WARNING, "config/udev: device %s already added. "
142                              "Ignoring.\n", name);
143        goto unwind;
144    }
145
146    set = udev_device_get_properties_list_entry(udev_device);
147    udev_list_entry_foreach(entry, set) {
148        key = udev_list_entry_get_name(entry);
149        if (!key)
150            continue;
151        value = udev_list_entry_get_value(entry);
152        if (!strncasecmp(key, UDEV_XKB_PROP_KEY,
153                                sizeof(UDEV_XKB_PROP_KEY) - 1)) {
154            LOG_PROPERTY(path, key, value);
155            tmp = key + sizeof(UDEV_XKB_PROP_KEY) - 1;
156            if (!strcasecmp(tmp, "rules"))
157                add_option(&options, "xkb_rules", value);
158            else if (!strcasecmp(tmp, "layout"))
159                add_option(&options, "xkb_layout", value);
160            else if (!strcasecmp(tmp, "variant"))
161                add_option(&options, "xkb_variant", value);
162            else if (!strcasecmp(tmp, "model"))
163                add_option(&options, "xkb_model", value);
164            else if (!strcasecmp(tmp, "options"))
165                add_option(&options, "xkb_options", value);
166        } else if (!strcmp(key, "ID_VENDOR")) {
167            LOG_PROPERTY(path, key, value);
168            attrs.vendor = strdup(value);
169        } else if (!strcmp(key, "ID_INPUT_KEY")) {
170            LOG_PROPERTY(path, key, value);
171            attrs.flags |= ATTR_KEYBOARD;
172        } else if (!strcmp(key, "ID_INPUT_MOUSE")) {
173            LOG_PROPERTY(path, key, value);
174            attrs.flags |= ATTR_POINTER;
175        } else if (!strcmp(key, "ID_INPUT_JOYSTICK")) {
176            LOG_PROPERTY(path, key, value);
177            attrs.flags |= ATTR_JOYSTICK;
178        } else if (!strcmp(key, "ID_INPUT_TABLET")) {
179            LOG_PROPERTY(path, key, value);
180            attrs.flags |= ATTR_TABLET;
181        } else if (!strcmp(key, "ID_INPUT_TOUCHPAD")) {
182            LOG_PROPERTY(path, key, value);
183            attrs.flags |= ATTR_TOUCHPAD;
184        } else if (!strcmp(key, "ID_INPUT_TOUCHSCREEN")) {
185            LOG_PROPERTY(path, key, value);
186            attrs.flags |= ATTR_TOUCHSCREEN;
187        }
188    }
189
190    add_option(&options, "config_info", config_info);
191
192    LogMessage(X_INFO, "config/udev: Adding input device %s (%s)\n",
193               name, path);
194    rc = NewInputDeviceRequest(options, &attrs, &dev);
195    if (rc != Success)
196        goto unwind;
197
198 unwind:
199    free(config_info);
200    while ((tmpo = options)) {
201        options = tmpo->next;
202        free(tmpo->key);        /* NULL if dev != NULL */
203        free(tmpo->value);      /* NULL if dev != NULL */
204        free(tmpo);
205    }
206
207    free(attrs.usb_id);
208    free(attrs.pnp_id);
209    free(attrs.product);
210    free(attrs.device);
211    free(attrs.vendor);
212    if (attrs.tags) {
213        char **tag = attrs.tags;
214        while (*tag) {
215            free(*tag);
216            tag++;
217        }
218        free(attrs.tags);
219    }
220
221    return;
222}
223
224static void
225device_removed(struct udev_device *device)
226{
227    char *value;
228    const char *syspath = udev_device_get_syspath(device);
229
230    if (asprintf(&value, "udev:%s", syspath) == -1)
231        return;
232
233    remove_devices("udev", value);
234
235    free(value);
236}
237
238static void
239wakeup_handler(pointer data, int err, pointer read_mask)
240{
241    int udev_fd = udev_monitor_get_fd(udev_monitor);
242    struct udev_device *udev_device;
243    const char *action;
244
245    if (err < 0)
246        return;
247
248    if (FD_ISSET(udev_fd, (fd_set *)read_mask)) {
249        udev_device = udev_monitor_receive_device(udev_monitor);
250        if (!udev_device)
251            return;
252        action = udev_device_get_action(udev_device);
253        if (action) {
254            if (!strcmp(action, "add"))
255                device_added(udev_device);
256            else if (!strcmp(action, "remove"))
257                device_removed(udev_device);
258            else if (!strcmp(action, "change")) {
259                device_removed(udev_device);
260                device_added(udev_device);
261            }
262        }
263        udev_device_unref(udev_device);
264    }
265}
266
267static void
268block_handler(pointer data, struct timeval **tv, pointer read_mask)
269{
270}
271
272int
273config_udev_init(void)
274{
275    struct udev *udev;
276    struct udev_enumerate *enumerate;
277    struct udev_list_entry *devices, *device;
278
279    udev = udev_new();
280    if (!udev)
281        return 0;
282    udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
283    if (!udev_monitor)
284        return 0;
285
286    if (udev_monitor_enable_receiving(udev_monitor)) {
287        ErrorF("config/udev: failed to bind the udev monitor\n");
288        return 0;
289    }
290
291    enumerate = udev_enumerate_new(udev);
292    if (!enumerate)
293        return 0;
294    udev_enumerate_scan_devices(enumerate);
295    devices = udev_enumerate_get_list_entry(enumerate);
296    udev_list_entry_foreach(device, devices) {
297        const char *syspath = udev_list_entry_get_name(device);
298        struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath);
299        device_added(udev_device);
300        udev_device_unref(udev_device);
301    }
302    udev_enumerate_unref(enumerate);
303
304    RegisterBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL);
305    AddGeneralSocket(udev_monitor_get_fd(udev_monitor));
306
307    return 1;
308}
309
310void
311config_udev_fini(void)
312{
313    struct udev *udev;
314
315    if (!udev_monitor)
316        return;
317
318    udev = udev_monitor_get_udev(udev_monitor);
319
320    RemoveGeneralSocket(udev_monitor_get_fd(udev_monitor));
321    RemoveBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL);
322    udev_monitor_unref(udev_monitor);
323    udev_monitor = NULL;
324    udev_unref(udev);
325}
326