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