udev.c revision 6747b715
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        unsigned int usb_vendor, usb_model;
98
99        name = udev_device_get_sysattr_value(parent, "name");
100        LOG_SYSATTR(ppath, "name", name);
101        if (!name) {
102            name = udev_device_get_property_value(parent, "NAME");
103            LOG_PROPERTY(ppath, "NAME", name);
104        }
105
106        attrs.pnp_id = udev_device_get_sysattr_value(parent, "id");
107        LOG_SYSATTR(ppath, "id", attrs.pnp_id);
108
109        /* construct USB ID in lowercase hex - "0000:ffff" */
110        if (product && sscanf(product, "%*x/%4x/%4x/%*x", &usb_vendor, &usb_model) == 2) {
111            attrs.usb_id = Xprintf("%04x:%04x", usb_vendor, usb_model);
112            if (attrs.usb_id)
113                LOG_PROPERTY(path, "PRODUCT", product);
114        }
115    }
116    if (!name)
117        name = "(unnamed)";
118    else
119        attrs.product = name;
120    add_option(&options, "name", name);
121
122    add_option(&options, "path", path);
123    add_option(&options, "device", path);
124    attrs.device = path;
125
126    tags_prop = udev_device_get_property_value(udev_device, "ID_INPUT.tags");
127    LOG_PROPERTY(path, "ID_INPUT.tags", tags_prop);
128    attrs.tags = xstrtokenize(tags_prop, ",");
129
130    config_info = Xprintf("udev:%s", syspath);
131    if (!config_info)
132        goto unwind;
133
134    if (device_is_duplicate(config_info)) {
135        LogMessage(X_WARNING, "config/udev: device %s already added. "
136                              "Ignoring.\n", name);
137        goto unwind;
138    }
139
140    set = udev_device_get_properties_list_entry(udev_device);
141    udev_list_entry_foreach(entry, set) {
142        key = udev_list_entry_get_name(entry);
143        if (!key)
144            continue;
145        value = udev_list_entry_get_value(entry);
146        if (!strncasecmp(key, UDEV_XKB_PROP_KEY,
147                                sizeof(UDEV_XKB_PROP_KEY) - 1)) {
148            LOG_PROPERTY(path, key, value);
149            tmp = key + sizeof(UDEV_XKB_PROP_KEY) - 1;
150            if (!strcasecmp(tmp, "rules"))
151                add_option(&options, "xkb_rules", value);
152            else if (!strcasecmp(tmp, "layout"))
153                add_option(&options, "xkb_layout", value);
154            else if (!strcasecmp(tmp, "variant"))
155                add_option(&options, "xkb_variant", value);
156            else if (!strcasecmp(tmp, "model"))
157                add_option(&options, "xkb_model", value);
158            else if (!strcasecmp(tmp, "options"))
159                add_option(&options, "xkb_options", value);
160        } else if (!strcmp(key, "ID_VENDOR")) {
161            LOG_PROPERTY(path, key, value);
162            attrs.vendor = value;
163        } else if (!strcmp(key, "ID_INPUT_KEY")) {
164            LOG_PROPERTY(path, key, value);
165            attrs.flags |= ATTR_KEYBOARD;
166        } else if (!strcmp(key, "ID_INPUT_MOUSE")) {
167            LOG_PROPERTY(path, key, value);
168            attrs.flags |= ATTR_POINTER;
169        } else if (!strcmp(key, "ID_INPUT_JOYSTICK")) {
170            LOG_PROPERTY(path, key, value);
171            attrs.flags |= ATTR_JOYSTICK;
172        } else if (!strcmp(key, "ID_INPUT_TABLET")) {
173            LOG_PROPERTY(path, key, value);
174            attrs.flags |= ATTR_TABLET;
175        } else if (!strcmp(key, "ID_INPUT_TOUCHPAD")) {
176            LOG_PROPERTY(path, key, value);
177            attrs.flags |= ATTR_TOUCHPAD;
178        } else if (!strcmp(key, "ID_INPUT_TOUCHSCREEN")) {
179            LOG_PROPERTY(path, key, value);
180            attrs.flags |= ATTR_TOUCHSCREEN;
181        }
182    }
183
184    LogMessage(X_INFO, "config/udev: Adding input device %s (%s)\n",
185               name, path);
186    rc = NewInputDeviceRequest(options, &attrs, &dev);
187    if (rc != Success)
188        goto unwind;
189
190    for (; dev; dev = dev->next) {
191        free(dev->config_info);
192        dev->config_info = strdup(config_info);
193    }
194
195 unwind:
196    free(config_info);
197    while (!dev && (tmpo = options)) {
198        options = tmpo->next;
199        free(tmpo->key);
200        free(tmpo->value);
201        free(tmpo);
202    }
203
204    free(attrs.usb_id);
205    if (attrs.tags) {
206        char **tag = attrs.tags;
207        while (*tag) {
208            free(*tag);
209            tag++;
210        }
211        free(attrs.tags);
212    }
213
214    return;
215}
216
217static void
218device_removed(struct udev_device *device)
219{
220    char *value;
221    const char *syspath = udev_device_get_syspath(device);
222
223    value = Xprintf("udev:%s", syspath);
224    if (!value)
225        return;
226
227    remove_devices("udev", value);
228
229    free(value);
230}
231
232static void
233wakeup_handler(pointer data, int err, pointer read_mask)
234{
235    int udev_fd = udev_monitor_get_fd(udev_monitor);
236    struct udev_device *udev_device;
237    const char *action;
238
239    if (err < 0)
240        return;
241
242    if (FD_ISSET(udev_fd, (fd_set *)read_mask)) {
243        udev_device = udev_monitor_receive_device(udev_monitor);
244        if (!udev_device)
245            return;
246        action = udev_device_get_action(udev_device);
247        if (action) {
248            if (!strcmp(action, "add"))
249                device_added(udev_device);
250            else if (!strcmp(action, "remove"))
251                device_removed(udev_device);
252        }
253        udev_device_unref(udev_device);
254    }
255}
256
257static void
258block_handler(pointer data, struct timeval **tv, pointer read_mask)
259{
260}
261
262int
263config_udev_init(void)
264{
265    struct udev *udev;
266    struct udev_enumerate *enumerate;
267    struct udev_list_entry *devices, *device;
268
269    udev = udev_new();
270    if (!udev)
271        return 0;
272    udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
273    if (!udev_monitor)
274        return 0;
275
276    if (udev_monitor_enable_receiving(udev_monitor)) {
277        ErrorF("config/udev: failed to bind the udev monitor\n");
278        return 0;
279    }
280
281    enumerate = udev_enumerate_new(udev);
282    if (!enumerate)
283        return 0;
284    udev_enumerate_scan_devices(enumerate);
285    devices = udev_enumerate_get_list_entry(enumerate);
286    udev_list_entry_foreach(device, devices) {
287        const char *syspath = udev_list_entry_get_name(device);
288        struct udev_device *udev_device = udev_device_new_from_syspath(udev, syspath);
289        device_added(udev_device);
290        udev_device_unref(udev_device);
291    }
292    udev_enumerate_unref(enumerate);
293
294    RegisterBlockAndWakeupHandlers(block_handler, wakeup_handler, NULL);
295    AddGeneralSocket(udev_monitor_get_fd(udev_monitor));
296
297    return 1;
298}
299
300void
301config_udev_fini(void)
302{
303    struct udev *udev;
304
305    if (!udev_monitor)
306        return;
307
308    udev = udev_monitor_get_udev(udev_monitor);
309
310    RemoveGeneralSocket(udev_monitor_get_fd(udev_monitor));
311    RemoveBlockAndWakeupHandlers(block_handler, wakeup_handler, udev_monitor);
312    udev_monitor_unref(udev_monitor);
313    udev_monitor = NULL;
314    udev_unref(udev);
315}
316