Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Copyright (c) 2012 Baptiste Daroussin |
3 |
* Copyright (c) 2013, 2014 Alex Kozlov |
4 |
* Copyright (c) 2014 Robert Millan |
5 |
* Copyright (c) 2014 Jean-Sebastien Pedron |
6 |
* |
7 |
* Permission is hereby granted, free of charge, to any person obtaining a |
8 |
* copy of this software and associated documentation files (the "Software"), |
9 |
* to deal in the Software without restriction, including without limitation |
10 |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
11 |
* and/or sell copies of the Software, and to permit persons to whom the |
12 |
* Software is furnished to do so, subject to the following conditions: |
13 |
* |
14 |
* The above copyright notice and this permission notice (including the next |
15 |
* paragraph) shall be included in all copies or substantial portions of the |
16 |
* Software. |
17 |
* |
18 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
19 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
20 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
21 |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
22 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
23 |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
24 |
* DEALINGS IN THE SOFTWARE. |
25 |
* |
26 |
* Author: Baptiste Daroussin <bapt@FreeBSD.org> |
27 |
*/ |
28 |
|
29 |
#ifdef HAVE_DIX_CONFIG_H |
30 |
#include <dix-config.h> |
31 |
#endif |
32 |
|
33 |
#include <sys/types.h> |
34 |
#include <sys/kbio.h> |
35 |
#include <sys/socket.h> |
36 |
#include <sys/stat.h> |
37 |
#include <sys/sysctl.h> |
38 |
#include <sys/un.h> |
39 |
|
40 |
#include <ctype.h> |
41 |
#include <dirent.h> |
42 |
#include <errno.h> |
43 |
#include <fcntl.h> |
44 |
#include <stdlib.h> |
45 |
#include <stdio.h> |
46 |
#include <stdbool.h> |
47 |
#include <unistd.h> |
48 |
#include <paths.h> |
49 |
|
50 |
#include "input.h" |
51 |
#include "inputstr.h" |
52 |
#include "hotplug.h" |
53 |
#include "config-backends.h" |
54 |
#include "os.h" |
55 |
|
56 |
#define DEVD_SOCK_PATH "/var/run/devd.pipe" |
57 |
|
58 |
#define RECONNECT_DELAY (5 * 1000) |
59 |
|
60 |
static int sock_devd; |
61 |
static bool is_kbdmux = false; |
62 |
OsTimerPtr rtimer; |
63 |
|
64 |
struct hw_type { |
65 |
const char *driver; |
66 |
int flag; |
67 |
const char *xdriver; |
68 |
const char *sysctldesc; |
69 |
}; |
70 |
|
71 |
static const struct hw_type hw_types0[] = { |
72 |
{ _PATH_DEV "sysmouse", ATTR_POINTER, "mouse", NULL }, |
73 |
{ NULL, 0, NULL, NULL }, |
74 |
}; |
75 |
|
76 |
static const struct hw_type hw_types1[] = { |
77 |
{ _PATH_DEV "ukbd%d", ATTR_KEYBOARD, "kbd", "dev.ukbd.%d.%%desc" }, |
78 |
{ _PATH_DEV "atkbd%d", ATTR_KEYBOARD, "kbd", "dev.atkbd.%d.%%desc" }, |
79 |
{ _PATH_DEV "kbdmux%d", ATTR_KEYBOARD, "kbd", NULL }, |
80 |
{ _PATH_DEV "ums%d", ATTR_POINTER, "mouse", "dev.ums.%d.%%desc" }, |
81 |
{ _PATH_DEV "psm%d", ATTR_POINTER, "mouse", "dev.psm.%d.%%desc" }, |
82 |
{ _PATH_DEV "joy%d", ATTR_JOYSTICK, "mouse", "dev.joy.%d.%%desc" }, |
83 |
{ _PATH_DEV "atp%d", ATTR_TOUCHPAD, "mouse", "dev.atp.%d.%%desc" }, |
84 |
{ _PATH_DEV "wsp%d", ATTR_TOUCHPAD, "mouse", "dev.wsp.%d.%%desc" }, |
85 |
{ _PATH_DEV "uep%d", ATTR_TOUCHSCREEN, "mouse", "dev.uep.%d.%%desc" }, |
86 |
{ _PATH_DEV "vboxguest", ATTR_POINTER, "vboxmouse", NULL }, |
87 |
{ _PATH_DEV "input/event%d", ATTR_TOUCHPAD, "evdev", NULL }, |
88 |
{ NULL, 0, NULL, NULL }, |
89 |
}; |
90 |
|
91 |
static void |
92 |
get_usb_id(char **pptr, int fd) |
93 |
{ |
94 |
unsigned short vendor; |
95 |
unsigned short product; |
96 |
unsigned int speed; |
97 |
#define WEBCAMD_IOCTL_GET_USB_VENDOR_ID _IOR('q', 250, unsigned short) |
98 |
#define WEBCAMD_IOCTL_GET_USB_PRODUCT_ID _IOR('q', 251, unsigned short) |
99 |
#define WEBCAMD_IOCTL_GET_USB_SPEED _IOR('q', 252, unsigned int) |
100 |
if (ioctl(fd, WEBCAMD_IOCTL_GET_USB_VENDOR_ID, &vendor) == 0 && |
101 |
ioctl(fd, WEBCAMD_IOCTL_GET_USB_PRODUCT_ID, &product) == 0 && |
102 |
ioctl(fd, WEBCAMD_IOCTL_GET_USB_SPEED, &speed) == 0) { |
103 |
if (asprintf(pptr, "%04x:%04x", vendor, product) == -1) |
104 |
*pptr = NULL; |
105 |
} |
106 |
} |
107 |
|
108 |
static const char * |
109 |
skip_path_dev(const char *ptr) |
110 |
{ |
111 |
if (strstr(ptr, _PATH_DEV) == ptr) |
112 |
ptr += strlen(_PATH_DEV); |
113 |
return (ptr); |
114 |
} |
115 |
|
116 |
static char * |
117 |
sysctl_get_str(const char *sysctlname) |
118 |
{ |
119 |
char *dest = NULL; |
120 |
size_t len; |
121 |
|
122 |
if (sysctlname == NULL) |
123 |
return NULL; |
124 |
|
125 |
if (sysctlbyname(sysctlname, NULL, &len, NULL, 0) == 0) { |
126 |
dest = malloc(len + 1); |
127 |
if (dest) { |
128 |
if (sysctlbyname(sysctlname, dest, &len, NULL, 0) == 0) |
129 |
dest[len] = '\0'; |
130 |
else { |
131 |
free(dest); |
132 |
dest = NULL; |
133 |
} |
134 |
} |
135 |
} |
136 |
|
137 |
return dest; |
138 |
} |
139 |
|
140 |
static void |
141 |
device_added(const char *devicename, bool allow_no_device) |
142 |
{ |
143 |
InputAttributes attrs = { }; |
144 |
InputOption *options = NULL; |
145 |
char *config_info = NULL; |
146 |
DeviceIntPtr dev = NULL; |
147 |
struct hw_type hw_type; |
148 |
char *product = NULL; |
149 |
char sysctlname[64]; |
150 |
char *vendor = NULL; |
151 |
int unit = 0; |
152 |
int fd = -1; |
153 |
char *walk; |
154 |
int i; |
155 |
|
156 |
for (i = 0; hw_types0[i].driver != NULL; i++) { |
157 |
if (strcmp(devicename, hw_types0[i].driver) == 0) { |
158 |
hw_type = hw_types0[i]; |
159 |
goto found; |
160 |
} |
161 |
} |
162 |
for (i = 0; hw_types1[i].driver != NULL; i++) { |
163 |
if (sscanf(devicename, hw_types1[i].driver, &unit) == 1) { |
164 |
hw_type = hw_types1[i]; |
165 |
goto found; |
166 |
} |
167 |
} |
168 |
goto ignore; |
169 |
|
170 |
found: |
171 |
if (hw_type.xdriver == NULL) |
172 |
goto ignore; |
173 |
|
174 |
/* set flags, if any */ |
175 |
attrs.flags |= hw_type.flag; |
176 |
|
177 |
if (strcmp(hw_type.xdriver, "kbd") == 0) { |
178 |
bool match = (strstr(hw_type.driver, |
179 |
_PATH_DEV "kbdmux") == hw_type.driver); |
180 |
|
181 |
if (is_kbdmux) { |
182 |
if (!match) |
183 |
goto ignore; |
184 |
} else { |
185 |
if (match) |
186 |
goto ignore; |
187 |
} |
188 |
} |
189 |
|
190 |
options = input_option_new(NULL, "_source", "server/devd"); |
191 |
if (options == NULL) |
192 |
goto error; |
193 |
|
194 |
if (hw_type.sysctldesc != NULL) { |
195 |
snprintf(sysctlname, sizeof(sysctlname), |
196 |
hw_type.sysctldesc, unit); |
197 |
vendor = sysctl_get_str(sysctlname); |
198 |
} |
199 |
|
200 |
if (vendor == NULL) { |
201 |
options = input_option_new(options, "name", |
202 |
skip_path_dev(devicename)); |
203 |
} else { |
204 |
if ((walk = strchr(vendor, ' ')) != NULL) { |
205 |
walk[0] = '\0'; |
206 |
walk++; |
207 |
product = walk; |
208 |
if ((walk = strchr(product, ',')) != NULL) |
209 |
walk[0] = '\0'; |
210 |
} |
211 |
|
212 |
attrs.vendor = strdup(vendor); |
213 |
if (product != NULL) { |
214 |
attrs.product = strdup(product); |
215 |
options = input_option_new(options, |
216 |
"name", product); |
217 |
} else { |
218 |
options = input_option_new(options, |
219 |
"name", "Unknown"); |
220 |
} |
221 |
} |
222 |
attrs.device = strdup(devicename); |
223 |
|
224 |
fd = open(devicename, O_RDONLY); |
225 |
if (fd > -1) { |
226 |
get_usb_id(&attrs.usb_id, fd); |
227 |
close(fd); |
228 |
options = input_option_new(options, "device", devicename); |
229 |
if (options == NULL) |
230 |
goto error; |
231 |
} else if (allow_no_device) { |
232 |
/* |
233 |
* Don't pass "device" option if the keyboard is |
234 |
* already attached to the console (ie. open() fails). |
235 |
* This would activate a special logic in |
236 |
* xf86-input-keyboard. Prevent any other attached to |
237 |
* console keyboards being processed. There can be |
238 |
* only one such device. |
239 |
*/ |
240 |
} else { |
241 |
goto ignore; |
242 |
} |
243 |
|
244 |
options = input_option_new(options, "driver", hw_type.xdriver); |
245 |
if (options == NULL) |
246 |
goto error; |
247 |
|
248 |
if (asprintf(&config_info, "devd:%s", |
249 |
skip_path_dev(devicename)) == -1) { |
250 |
config_info = NULL; |
251 |
goto error; |
252 |
} |
253 |
|
254 |
if (device_is_duplicate(config_info)) |
255 |
goto duplicate; |
256 |
|
257 |
options = input_option_new(options, "config_info", config_info); |
258 |
if (options == NULL) |
259 |
goto error; |
260 |
|
261 |
LogMessage(X_INFO, "config/devd: adding input device '%s'\n", |
262 |
devicename); |
263 |
|
264 |
NewInputDeviceRequest(options, &attrs, &dev); |
265 |
goto done; |
266 |
|
267 |
duplicate: |
268 |
LogMessage(X_WARNING, "config/devd: device '%s' already " |
269 |
"added. Ignoring\n", devicename); |
270 |
goto done; |
271 |
|
272 |
error: |
273 |
LogMessage(X_INFO, "config/devd: error adding device '%s'\n", |
274 |
devicename); |
275 |
goto done; |
276 |
|
277 |
ignore: |
278 |
LogMessage(X_INFO, "config/devd: ignoring device '%s'\n", |
279 |
devicename); |
280 |
goto done; |
281 |
|
282 |
done: |
283 |
free(config_info); |
284 |
input_option_free_list(&options); |
285 |
free(attrs.usb_id); |
286 |
free(attrs.product); |
287 |
free(attrs.device); |
288 |
free(attrs.vendor); |
289 |
free(vendor); |
290 |
} |
291 |
|
292 |
static void |
293 |
devpath_scan_sub(char *path, int off, int rem) |
294 |
{ |
295 |
struct dirent *entry; |
296 |
DIR *dp; |
297 |
|
298 |
if ((dp = opendir(path)) == NULL) { |
299 |
LogMessage(X_INFO, "Cannot open directory '%s'\n", path); |
300 |
return; |
301 |
} |
302 |
while ((entry = readdir(dp)) != NULL) { |
303 |
int len = strlen(entry->d_name); |
304 |
if (len > rem) |
305 |
continue; |
306 |
strcpy(path + off, entry->d_name); |
307 |
off += len; |
308 |
rem -= len; |
309 |
switch (entry->d_type) { |
310 |
case DT_DIR: |
311 |
if (strcmp(entry->d_name, ".") == 0 || |
312 |
strcmp(entry->d_name, "..") == 0) |
313 |
break; |
314 |
if (rem < 1) |
315 |
break; |
316 |
path[off] = '/'; |
317 |
path[off+1] = '\0'; |
318 |
off++; |
319 |
rem--; |
320 |
/* recurse */ |
321 |
devpath_scan_sub(path, off, rem); |
322 |
off--; |
323 |
rem++; |
324 |
break; |
325 |
case DT_SOCK: |
326 |
case DT_FIFO: |
327 |
case DT_LNK: |
328 |
case DT_CHR: |
329 |
/* add device, if any */ |
330 |
device_added(path, false); |
331 |
break; |
332 |
default: |
333 |
break; |
334 |
} |
335 |
off -= len; |
336 |
rem += len; |
337 |
} |
338 |
closedir(dp); |
339 |
} |
340 |
|
341 |
static void |
342 |
devpath_scan(void) |
343 |
{ |
344 |
char path[PATH_MAX + 1]; |
345 |
|
346 |
strlcpy(path, _PATH_DEV, sizeof(path)); |
347 |
|
348 |
devpath_scan_sub(path, strlen(path), PATH_MAX - strlen(path)); |
349 |
} |
350 |
|
351 |
static void |
352 |
device_removed(char *devicename) |
353 |
{ |
354 |
char *config_info; |
355 |
|
356 |
if (asprintf(&config_info, "devd:%s", |
357 |
skip_path_dev(devicename)) == -1) |
358 |
return; |
359 |
|
360 |
if (device_is_duplicate(config_info)) { |
361 |
LogMessage(X_INFO, "config/devd: removing input device '%s'\n", |
362 |
devicename); |
363 |
} |
364 |
remove_devices("devd", config_info); |
365 |
|
366 |
free(config_info); |
367 |
} |
368 |
|
369 |
static bool is_kbdmux_enabled(void) |
370 |
{ |
371 |
/* Xorg uses /dev/ttyv0 as a console device */ |
372 |
static const char device[]= { _PATH_DEV "ttyv0" }; |
373 |
keyboard_info_t info; |
374 |
int fd; |
375 |
|
376 |
fd = open(device, O_RDONLY); |
377 |
|
378 |
if (fd < 0) |
379 |
return false; |
380 |
|
381 |
if (ioctl(fd, KDGKBINFO, &info) == -1) { |
382 |
close(fd); |
383 |
return false; |
384 |
} |
385 |
|
386 |
close(fd); |
387 |
|
388 |
if (!strncmp(info.kb_name, "kbdmux", 6)) |
389 |
return true; |
390 |
|
391 |
return false; |
392 |
} |
393 |
|
394 |
static void |
395 |
disconnect_devd(int sock) |
396 |
{ |
397 |
if (sock >= 0) { |
398 |
RemoveGeneralSocket(sock); |
399 |
close(sock); |
400 |
} |
401 |
} |
402 |
|
403 |
static int |
404 |
connect_devd(void) |
405 |
{ |
406 |
struct sockaddr_un devd; |
407 |
int sock; |
408 |
|
409 |
sock = socket(AF_UNIX, SOCK_STREAM, 0); |
410 |
if (sock < 0) { |
411 |
LogMessage(X_ERROR, "config/devd: fail opening stream socket\n"); |
412 |
return -1; |
413 |
} |
414 |
|
415 |
devd.sun_family = AF_UNIX; |
416 |
strlcpy(devd.sun_path, DEVD_SOCK_PATH, sizeof(devd.sun_path)); |
417 |
|
418 |
if (connect(sock, (struct sockaddr *) &devd, sizeof(devd)) < 0) { |
419 |
close(sock); |
420 |
LogMessage(X_ERROR, "config/devd: fail to connect to devd\n"); |
421 |
return -1; |
422 |
} |
423 |
|
424 |
AddGeneralSocket(sock); |
425 |
|
426 |
return sock; |
427 |
} |
428 |
|
429 |
static CARD32 |
430 |
reconnect_handler(OsTimerPtr timer, CARD32 time, pointer arg) |
431 |
{ |
432 |
int newsock; |
433 |
|
434 |
if ((newsock = connect_devd()) > 0) { |
435 |
sock_devd = newsock; |
436 |
TimerFree(rtimer); |
437 |
rtimer = NULL; |
438 |
LogMessage(X_INFO, "config/devd: reopening devd socket\n"); |
439 |
return 0; |
440 |
} |
441 |
|
442 |
/* Try again after RECONNECT_DELAY */ |
443 |
return RECONNECT_DELAY; |
444 |
} |
445 |
|
446 |
static ssize_t |
447 |
socket_getline(int fd, char **out) |
448 |
{ |
449 |
char *buf, *newbuf; |
450 |
ssize_t ret, cap, sz = 0; |
451 |
char c; |
452 |
|
453 |
cap = 1024; |
454 |
buf = malloc(cap * sizeof(char)); |
455 |
if (!buf) |
456 |
return -1; |
457 |
|
458 |
for (;;) { |
459 |
ret = read(sock_devd, &c, 1); |
460 |
if (ret < 0) { |
461 |
if (errno == EINTR) |
462 |
continue; |
463 |
free(buf); |
464 |
return -1; |
465 |
/* EOF - devd socket is lost */ |
466 |
} else if (ret == 0) { |
467 |
disconnect_devd(sock_devd); |
468 |
rtimer = TimerSet(NULL, 0, 1, reconnect_handler, NULL); |
469 |
LogMessage(X_WARNING, "config/devd: devd socket is lost\n"); |
470 |
return -1; |
471 |
} |
472 |
if (c == '\n') |
473 |
break; |
474 |
|
475 |
if (sz + 1 >= cap) { |
476 |
cap *= 2; |
477 |
newbuf = realloc(buf, cap * sizeof(char)); |
478 |
if (!newbuf) { |
479 |
free(buf); |
480 |
return -1; |
481 |
} |
482 |
buf = newbuf; |
483 |
} |
484 |
buf[sz] = c; |
485 |
sz++; |
486 |
} |
487 |
|
488 |
buf[sz] = '\0'; |
489 |
if (sz >= 0) |
490 |
*out = buf; |
491 |
else |
492 |
free(buf); |
493 |
|
494 |
/* Number of bytes in the line, not counting the line break */ |
495 |
return sz; |
496 |
} |
497 |
|
498 |
static void |
499 |
wakeup_handler(void *data, int err, void *read_mask) |
500 |
{ |
501 |
static const char cdev_create[] = { "!system=DEVFS subsystem=CDEV type=CREATE cdev=" }; |
502 |
static const char cdev_destroy[] = { "!system=DEVFS subsystem=CDEV type=DESTROY cdev=" }; |
503 |
static const char cdev_path[] = { _PATH_DEV }; |
504 |
char *line = NULL; |
505 |
char *devicename; |
506 |
char *walk; |
507 |
|
508 |
if (err < 0) |
509 |
return; |
510 |
|
511 |
if (FD_ISSET(sock_devd, (fd_set *) read_mask)) { |
512 |
if (socket_getline(sock_devd, &line) < 0) |
513 |
return; |
514 |
if (strstr(line, cdev_create) == line) { |
515 |
devicename = line + strlen(cdev_create) - strlen(cdev_path); |
516 |
memcpy(devicename, cdev_path, strlen(cdev_path)); |
517 |
walk = strchr(devicename, ' '); |
518 |
if (walk != NULL) |
519 |
walk[0] = '\0'; |
520 |
device_added(devicename, false); |
521 |
} else if (strstr(line, cdev_destroy) == line) { |
522 |
devicename = line + strlen(cdev_destroy) - strlen(cdev_path); |
523 |
memcpy(devicename, cdev_path, strlen(cdev_path)); |
524 |
walk = strchr(devicename, ' '); |
525 |
if (walk != NULL) |
526 |
walk[0] = '\0'; |
527 |
device_removed(devicename); |
528 |
} |
529 |
free(line); |
530 |
} |
531 |
} |
532 |
|
533 |
static void |
534 |
block_handler(void *data, struct timeval **tv, void *read_mask) |
535 |
{ |
536 |
} |
537 |
|
538 |
int |
539 |
config_devd_init(void) |
540 |
{ |
541 |
LogMessage(X_INFO, "config/devd: probing input devices...\n"); |
542 |
|
543 |
/* Check if kbdmux is enabled */ |
544 |
is_kbdmux = is_kbdmux_enabled(); |
545 |
|
546 |
/* Try to add kbdmux device first */ |
547 |
if (is_kbdmux) |
548 |
device_added(_PATH_DEV "kbdmux0", true); |
549 |
|
550 |
/* Connect to devd, so that we don't loose any events */ |
551 |
if ((sock_devd = connect_devd()) < 0) |
552 |
return 0; |
553 |
|
554 |
/* Scan what is currently connected */ |
555 |
devpath_scan(); |
556 |
|
557 |
/* Register wakeup handler */ |
558 |
RegisterBlockAndWakeupHandlers(block_handler, |
559 |
wakeup_handler, NULL); |
560 |
|
561 |
return 1; |
562 |
} |
563 |
|
564 |
void |
565 |
config_devd_fini(void) |
566 |
{ |
567 |
LogMessage(X_INFO, "config/devd: terminating backend...\n"); |
568 |
|
569 |
if (rtimer) { |
570 |
TimerFree(rtimer); |
571 |
rtimer = NULL; |
572 |
} |
573 |
|
574 |
disconnect_devd(sock_devd); |
575 |
|
576 |
RemoveBlockAndWakeupHandlers(block_handler, |
577 |
wakeup_handler, NULL); |
578 |
} |