Imported Upstream version 1.15.1
[deb_xorg-server.git] / config / hal.c
CommitLineData
a09e091a
JB
1/*
2 * Copyright © 2007 Daniel Stone
3 * Copyright © 2007 Red Hat, Inc.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *
24 * Author: Daniel Stone <daniel@fooishbar.org>
25 */
26
27#ifdef HAVE_DIX_CONFIG_H
28#include <dix-config.h>
29#endif
30
31#include <dbus/dbus.h>
32#include <hal/libhal.h>
33#include <string.h>
34#include <sys/select.h>
35
36#include "input.h"
37#include "inputstr.h"
38#include "hotplug.h"
39#include "config-backends.h"
40#include "os.h"
41
42#define LIBHAL_PROP_KEY "input.x11_options."
43#define LIBHAL_XKB_PROP_KEY "input.xkb."
44
45struct config_hal_info {
46 DBusConnection *system_bus;
47 LibHalContext *hal_ctx;
48};
49
50/* Used for special handling of xkb options. */
51struct xkb_options {
52 char *layout;
53 char *model;
54 char *rules;
55 char *variant;
56 char *options;
57};
58
59static void
60device_removed(LibHalContext * ctx, const char *udi)
61{
62 char *value;
63
64 if (asprintf(&value, "hal:%s", udi) == -1)
65 return;
66
67 remove_devices("hal", value);
68
69 free(value);
70}
71
72static char *
73get_prop_string(LibHalContext * hal_ctx, const char *udi, const char *name)
74{
75 char *prop, *ret;
76
77 prop = libhal_device_get_property_string(hal_ctx, udi, name, NULL);
78 LogMessageVerb(X_INFO, 10, "config/hal: getting %s on %s returned %s\n",
79 name, udi, prop ? prop : "(null)");
80 if (prop) {
81 ret = strdup(prop);
82 libhal_free_string(prop);
83 }
84 else {
85 return NULL;
86 }
87
88 return ret;
89}
90
91static char *
92get_prop_string_array(LibHalContext * hal_ctx, const char *udi,
93 const char *prop)
94{
95 char **props, *ret, *str;
96 int i, len = 0;
97
98 props = libhal_device_get_property_strlist(hal_ctx, udi, prop, NULL);
99 if (props) {
100 for (i = 0; props[i]; i++)
101 len += strlen(props[i]);
102
103 ret = calloc(sizeof(char), len + i); /* i - 1 commas, 1 NULL */
104 if (!ret) {
105 libhal_free_string_array(props);
106 return NULL;
107 }
108
109 str = ret;
110 for (i = 0; props[i]; i++) {
111 strcpy(str, props[i]);
112 str += strlen(props[i]);
113 *str++ = ',';
114 }
115 *(str - 1) = '\0';
116
117 libhal_free_string_array(props);
118 }
119 else {
120 return NULL;
121 }
122
123 return ret;
124}
125
126static void
127device_added(LibHalContext * hal_ctx, const char *udi)
128{
129 char *path = NULL, *driver = NULL, *name = NULL, *config_info = NULL;
130 char *hal_tags, *parent;
131 InputOption *input_options = NULL;
132 InputAttributes attrs = { 0 };
133 DeviceIntPtr dev = NULL;
134 DBusError error;
135 struct xkb_options xkb_opts = { 0 };
136 int rc;
137
138 LibHalPropertySet *set = NULL;
139 LibHalPropertySetIterator set_iter;
140 char *psi_key = NULL, *tmp_val;
141
142 dbus_error_init(&error);
143
144 driver = get_prop_string(hal_ctx, udi, "input.x11_driver");
145 if (!driver) {
146 /* verbose, don't tell the user unless they _want_ to see it */
147 LogMessageVerb(X_INFO, 7,
148 "config/hal: no driver specified for device %s\n", udi);
149 goto unwind;
150 }
151
152 path = get_prop_string(hal_ctx, udi, "input.device");
153 if (!path) {
154 LogMessage(X_WARNING,
155 "config/hal: no driver or path specified for %s\n", udi);
156 goto unwind;
157 }
158 attrs.device = strdup(path);
159
160 name = get_prop_string(hal_ctx, udi, "info.product");
161 if (!name)
162 name = strdup("(unnamed)");
163 else
164 attrs.product = strdup(name);
165
166 attrs.vendor = get_prop_string(hal_ctx, udi, "info.vendor");
167 hal_tags = get_prop_string(hal_ctx, udi, "input.tags");
168 attrs.tags = xstrtokenize(hal_tags, ",");
169 free(hal_tags);
170
171 if (libhal_device_query_capability(hal_ctx, udi, "input.keys", NULL))
172 attrs.flags |= ATTR_KEYBOARD;
173 if (libhal_device_query_capability(hal_ctx, udi, "input.mouse", NULL))
174 attrs.flags |= ATTR_POINTER;
175 if (libhal_device_query_capability(hal_ctx, udi, "input.joystick", NULL))
176 attrs.flags |= ATTR_JOYSTICK;
177 if (libhal_device_query_capability(hal_ctx, udi, "input.tablet", NULL))
178 attrs.flags |= ATTR_TABLET;
179 if (libhal_device_query_capability(hal_ctx, udi, "input.touchpad", NULL))
180 attrs.flags |= ATTR_TOUCHPAD;
181 if (libhal_device_query_capability(hal_ctx, udi, "input.touchscreen", NULL))
182 attrs.flags |= ATTR_TOUCHSCREEN;
183
184 parent = get_prop_string(hal_ctx, udi, "info.parent");
185 if (parent) {
186 int usb_vendor, usb_product;
187 char *old_parent;
188
189 /* construct USB ID in lowercase - "0000:ffff" */
190 usb_vendor = libhal_device_get_property_int(hal_ctx, parent,
191 "usb.vendor_id", NULL);
192 LogMessageVerb(X_INFO, 10,
193 "config/hal: getting usb.vendor_id on %s "
194 "returned %04x\n", parent, usb_vendor);
195 usb_product = libhal_device_get_property_int(hal_ctx, parent,
196 "usb.product_id", NULL);
197 LogMessageVerb(X_INFO, 10,
198 "config/hal: getting usb.product_id on %s "
199 "returned %04x\n", parent, usb_product);
200 if (usb_vendor && usb_product)
201 if (asprintf(&attrs.usb_id, "%04x:%04x", usb_vendor, usb_product)
202 == -1)
203 attrs.usb_id = NULL;
204
205 attrs.pnp_id = get_prop_string(hal_ctx, parent, "pnp.id");
206 old_parent = parent;
207
208 while (!attrs.pnp_id &&
209 (parent = get_prop_string(hal_ctx, parent, "info.parent"))) {
210 attrs.pnp_id = get_prop_string(hal_ctx, parent, "pnp.id");
211
212 free(old_parent);
213 old_parent = parent;
214 }
215
216 free(old_parent);
217 }
218
219 input_options = input_option_new(NULL, "_source", "server/hal");
220 if (!input_options) {
221 LogMessage(X_ERROR,
222 "config/hal: couldn't allocate first key/value pair\n");
223 goto unwind;
224 }
225
226 /* most drivers use device.. not path. evdev uses both however, but the
227 * path version isn't documented apparently. support both for now. */
228 input_options = input_option_new(input_options, "path", path);
229 input_options = input_option_new(input_options, "device", path);
230
231 input_options = input_option_new(input_options, "driver", driver);
232 input_options = input_option_new(input_options, "name", name);
233
234 if (asprintf(&config_info, "hal:%s", udi) == -1) {
235 config_info = NULL;
236 LogMessage(X_ERROR, "config/hal: couldn't allocate name\n");
237 goto unwind;
238 }
239
240 /* Check for duplicate devices */
241 if (device_is_duplicate(config_info)) {
242 LogMessage(X_WARNING,
243 "config/hal: device %s already added. Ignoring.\n", name);
244 goto unwind;
245 }
246
247 /* ok, grab options from hal.. iterate through all properties
248 * and lets see if any of them are options that we can add */
249 set = libhal_device_get_all_properties(hal_ctx, udi, &error);
250
251 if (!set) {
252 LogMessage(X_ERROR,
253 "config/hal: couldn't get property list for %s: %s (%s)\n",
254 udi, error.name, error.message);
255 goto unwind;
256 }
257
258 libhal_psi_init(&set_iter, set);
259 while (libhal_psi_has_more(&set_iter)) {
260 /* we are looking for supported keys.. extract and add to options */
261 psi_key = libhal_psi_get_key(&set_iter);
262
263 if (psi_key) {
264
265 /* normal options first (input.x11_options.<propname>) */
266 if (!strncasecmp
267 (psi_key, LIBHAL_PROP_KEY, sizeof(LIBHAL_PROP_KEY) - 1)) {
268 char *tmp;
269
270 /* only support strings for all values */
271 tmp_val = get_prop_string(hal_ctx, udi, psi_key);
272
273 if (tmp_val) {
274
275 /* xkb needs special handling. HAL specs include
276 * input.xkb.xyz options, but the x11-input.fdi specifies
277 * input.x11_options.Xkbxyz options. By default, we use
278 * the former, unless the specific X11 ones are specified.
279 * Since we can't predict the order in which the keys
280 * arrive, we need to store them.
281 */
282 if ((tmp = strcasestr(psi_key, "xkb")) && strlen(tmp) >= 4) {
283 if (!strcasecmp(&tmp[3], "layout")) {
284 free(xkb_opts.layout);
285 xkb_opts.layout = strdup(tmp_val);
286 }
287 else if (!strcasecmp(&tmp[3], "model")) {
288 free(xkb_opts.model);
289 xkb_opts.model = strdup(tmp_val);
290 }
291 else if (!strcasecmp(&tmp[3], "rules")) {
292 free(xkb_opts.rules);
293 xkb_opts.rules = strdup(tmp_val);
294 }
295 else if (!strcasecmp(&tmp[3], "variant")) {
296 free(xkb_opts.variant);
297 xkb_opts.variant = strdup(tmp_val);
298 }
299 else if (!strcasecmp(&tmp[3], "options")) {
300 free(xkb_opts.options);
301 xkb_opts.options = strdup(tmp_val);
302 }
303 }
304 else {
305 /* all others */
306 input_options =
307 input_option_new(input_options,
308 psi_key + sizeof(LIBHAL_PROP_KEY) -
309 1, tmp_val);
310 free(tmp_val);
311 }
312 }
313 else {
314 /* server 1.4 had xkb_options as strlist. */
315 if ((tmp = strcasestr(psi_key, "xkb")) &&
316 (strlen(tmp) >= 4) &&
317 (!strcasecmp(&tmp[3], "options")) &&
318 (tmp_val =
319 get_prop_string_array(hal_ctx, udi, psi_key))) {
320 free(xkb_opts.options);
321 xkb_opts.options = strdup(tmp_val);
322 }
323 }
324 }
325 else if (!strncasecmp
326 (psi_key, LIBHAL_XKB_PROP_KEY,
327 sizeof(LIBHAL_XKB_PROP_KEY) - 1)) {
328 char *tmp;
329
330 /* only support strings for all values */
331 tmp_val = get_prop_string(hal_ctx, udi, psi_key);
332
333 if (tmp_val && strlen(psi_key) >= sizeof(LIBHAL_XKB_PROP_KEY)) {
334
335 tmp = &psi_key[sizeof(LIBHAL_XKB_PROP_KEY) - 1];
336
337 if (!strcasecmp(tmp, "layout")) {
338 if (!xkb_opts.layout)
339 xkb_opts.layout = strdup(tmp_val);
340 }
341 else if (!strcasecmp(tmp, "rules")) {
342 if (!xkb_opts.rules)
343 xkb_opts.rules = strdup(tmp_val);
344 }
345 else if (!strcasecmp(tmp, "variant")) {
346 if (!xkb_opts.variant)
347 xkb_opts.variant = strdup(tmp_val);
348 }
349 else if (!strcasecmp(tmp, "model")) {
350 if (!xkb_opts.model)
351 xkb_opts.model = strdup(tmp_val);
352 }
353 else if (!strcasecmp(tmp, "options")) {
354 if (!xkb_opts.options)
355 xkb_opts.options = strdup(tmp_val);
356 }
357 free(tmp_val);
358 }
359 else {
360 /* server 1.4 had xkb options as strlist */
361 tmp_val = get_prop_string_array(hal_ctx, udi, psi_key);
362 if (tmp_val &&
363 strlen(psi_key) >= sizeof(LIBHAL_XKB_PROP_KEY)) {
364 tmp = &psi_key[sizeof(LIBHAL_XKB_PROP_KEY) - 1];
365 if (!strcasecmp(tmp, ".options") && (!xkb_opts.options))
366 xkb_opts.options = strdup(tmp_val);
367 }
368 free(tmp_val);
369 }
370 }
371 }
372
373 /* psi_key doesn't need to be freed */
374 libhal_psi_next(&set_iter);
375 }
376
377 /* Now add xkb options */
378 if (xkb_opts.layout)
379 input_options =
380 input_option_new(input_options, "xkb_layout", xkb_opts.layout);
381 if (xkb_opts.rules)
382 input_options =
383 input_option_new(input_options, "xkb_rules", xkb_opts.rules);
384 if (xkb_opts.variant)
385 input_options =
386 input_option_new(input_options, "xkb_variant", xkb_opts.variant);
387 if (xkb_opts.model)
388 input_options =
389 input_option_new(input_options, "xkb_model", xkb_opts.model);
390 if (xkb_opts.options)
391 input_options =
392 input_option_new(input_options, "xkb_options", xkb_opts.options);
393 input_options = input_option_new(input_options, "config_info", config_info);
394
395 /* this isn't an error, but how else do you output something that the user can see? */
396 LogMessage(X_INFO, "config/hal: Adding input device %s\n", name);
397 if ((rc = NewInputDeviceRequest(input_options, &attrs, &dev)) != Success) {
398 LogMessage(X_ERROR, "config/hal: NewInputDeviceRequest failed (%d)\n",
399 rc);
400 dev = NULL;
401 goto unwind;
402 }
403
404 unwind:
405 if (set)
406 libhal_free_property_set(set);
407 free(path);
408 free(driver);
409 free(name);
410 free(config_info);
411 input_option_free_list(&input_options);
412
413 free(attrs.product);
414 free(attrs.vendor);
415 free(attrs.device);
416 free(attrs.pnp_id);
417 free(attrs.usb_id);
418 if (attrs.tags) {
419 char **tag = attrs.tags;
420
421 while (*tag) {
422 free(*tag);
423 tag++;
424 }
425 free(attrs.tags);
426 }
427
428 free(xkb_opts.layout);
429 free(xkb_opts.rules);
430 free(xkb_opts.model);
431 free(xkb_opts.variant);
432 free(xkb_opts.options);
433
434 dbus_error_free(&error);
435
436 return;
437}
438
439static void
440disconnect_hook(void *data)
441{
442 DBusError error;
443 struct config_hal_info *info = data;
444
445 if (info->hal_ctx) {
446 if (dbus_connection_get_is_connected(info->system_bus)) {
447 dbus_error_init(&error);
448 if (!libhal_ctx_shutdown(info->hal_ctx, &error))
449 LogMessage(X_WARNING,
450 "config/hal: disconnect_hook couldn't shut down context: %s (%s)\n",
451 error.name, error.message);
452 dbus_error_free(&error);
453 }
454 libhal_ctx_free(info->hal_ctx);
455 }
456
457 info->hal_ctx = NULL;
458 info->system_bus = NULL;
459}
460
461static BOOL
462connect_and_register(DBusConnection * connection, struct config_hal_info *info)
463{
464 DBusError error;
465 char **devices;
466 int num_devices, i;
467
468 if (info->hal_ctx)
469 return TRUE; /* already registered, pretend we did something */
470
471 info->system_bus = connection;
472
473 dbus_error_init(&error);
474
475 info->hal_ctx = libhal_ctx_new();
476 if (!info->hal_ctx) {
477 LogMessage(X_ERROR, "config/hal: couldn't create HAL context\n");
478 goto out_err;
479 }
480
481 if (!libhal_ctx_set_dbus_connection(info->hal_ctx, info->system_bus)) {
482 LogMessage(X_ERROR,
483 "config/hal: couldn't associate HAL context with bus\n");
484 goto out_err;
485 }
486 if (!libhal_ctx_init(info->hal_ctx, &error)) {
487 LogMessage(X_ERROR,
488 "config/hal: couldn't initialise context: %s (%s)\n",
489 error.name ? error.name : "unknown error",
490 error.message ? error.message : "null");
491 goto out_err;
492 }
493 if (!libhal_device_property_watch_all(info->hal_ctx, &error)) {
494 LogMessage(X_ERROR,
495 "config/hal: couldn't watch all properties: %s (%s)\n",
496 error.name ? error.name : "unknown error",
497 error.message ? error.message : "null");
498 goto out_ctx;
499 }
500 libhal_ctx_set_device_added(info->hal_ctx, device_added);
501 libhal_ctx_set_device_removed(info->hal_ctx, device_removed);
502
503 devices = libhal_find_device_by_capability(info->hal_ctx, "input",
504 &num_devices, &error);
505 /* FIXME: Get default devices if error is set. */
506 if (dbus_error_is_set(&error)) {
507 LogMessage(X_ERROR, "config/hal: couldn't find input device: %s (%s)\n",
508 error.name ? error.name : "unknown error",
509 error.message ? error.message : "null");
510 goto out_ctx;
511 }
512 for (i = 0; i < num_devices; i++)
513 device_added(info->hal_ctx, devices[i]);
514 libhal_free_string_array(devices);
515
516 dbus_error_free(&error);
517
518 return TRUE;
519
520 out_ctx:
521 dbus_error_free(&error);
522
523 if (!libhal_ctx_shutdown(info->hal_ctx, &error)) {
524 LogMessage(X_WARNING,
525 "config/hal: couldn't shut down context: %s (%s)\n",
526 error.name ? error.name : "unknown error",
527 error.message ? error.message : "null");
528 dbus_error_free(&error);
529 }
530
531 out_err:
532 dbus_error_free(&error);
533
534 if (info->hal_ctx) {
535 libhal_ctx_free(info->hal_ctx);
536 }
537
538 info->hal_ctx = NULL;
539 info->system_bus = NULL;
540
541 return FALSE;
542}
543
544/**
545 * Handle NewOwnerChanged signals to deal with HAL startup at X server runtime.
546 *
547 * NewOwnerChanged is send once when HAL shuts down, and once again when it
548 * comes back up. Message has three arguments, first is the name
549 * (org.freedesktop.Hal), the second one is the old owner, third one is new
550 * owner.
551 */
552static DBusHandlerResult
553ownerchanged_handler(DBusConnection * connection, DBusMessage * message,
554 void *data)
555{
556 int ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
557
558 if (dbus_message_is_signal(message,
559 "org.freedesktop.DBus", "NameOwnerChanged")) {
560 DBusError error;
561 char *name, *old_owner, *new_owner;
562
563 dbus_error_init(&error);
564 dbus_message_get_args(message, &error,
565 DBUS_TYPE_STRING, &name,
566 DBUS_TYPE_STRING, &old_owner,
567 DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID);
568
569 if (dbus_error_is_set(&error)) {
570 ErrorF
571 ("[config/hal] failed to get NameOwnerChanged args: %s (%s)\n",
572 error.name, error.message);
573 }
574 else if (name && strcmp(name, "org.freedesktop.Hal") == 0) {
575
576 if (!old_owner || !strlen(old_owner)) {
577 DebugF("[config/hal] HAL startup detected.\n");
578 if (connect_and_register
579 (connection, (struct config_hal_info *) data))
580 dbus_connection_unregister_object_path(connection,
581 "/org/freedesktop/DBus");
582 else
583 ErrorF("[config/hal] Failed to connect to HAL bus.\n");
584 }
585
586 ret = DBUS_HANDLER_RESULT_HANDLED;
587 }
588 dbus_error_free(&error);
589 }
590
591 return ret;
592}
593
594/**
595 * Register a handler for the NameOwnerChanged signal.
596 */
597static BOOL
598listen_for_startup(DBusConnection * connection, void *data)
599{
600 DBusObjectPathVTable vtable = {.message_function = ownerchanged_handler, };
601 DBusError error;
602 const char MATCH_RULE[] = "sender='org.freedesktop.DBus',"
603 "interface='org.freedesktop.DBus',"
604 "type='signal',"
605 "path='/org/freedesktop/DBus'," "member='NameOwnerChanged'";
606 int rc = FALSE;
607
608 dbus_error_init(&error);
609 dbus_bus_add_match(connection, MATCH_RULE, &error);
610 if (!dbus_error_is_set(&error)) {
611 if (dbus_connection_register_object_path(connection,
612 "/org/freedesktop/DBus",
613 &vtable, data))
614 rc = TRUE;
615 else
616 ErrorF("[config/hal] cannot register object path.\n");
617 }
618 else {
619 ErrorF("[config/hal] couldn't add match rule: %s (%s)\n", error.name,
620 error.message);
621 ErrorF("[config/hal] cannot detect a HAL startup.\n");
622 }
623
624 dbus_error_free(&error);
625
626 return rc;
627}
628
629static void
630connect_hook(DBusConnection * connection, void *data)
631{
632 struct config_hal_info *info = data;
633
634 if (listen_for_startup(connection, data) &&
635 connect_and_register(connection, info))
636 dbus_connection_unregister_object_path(connection,
637 "/org/freedesktop/DBus");
638
639 return;
640}
641
642static struct config_hal_info hal_info;
643
644static struct config_dbus_core_hook hook = {
645 .connect = connect_hook,
646 .disconnect = disconnect_hook,
647 .data = &hal_info,
648};
649
650int
651config_hal_init(void)
652{
653 memset(&hal_info, 0, sizeof(hal_info));
654 hal_info.system_bus = NULL;
655 hal_info.hal_ctx = NULL;
656
657 if (!config_dbus_core_add_hook(&hook)) {
658 LogMessage(X_ERROR, "config/hal: failed to add D-Bus hook\n");
659 return 0;
660 }
661
662 /* verbose message */
663 LogMessageVerb(X_INFO, 7, "config/hal: initialized\n");
664
665 return 1;
666}
667
668void
669config_hal_fini(void)
670{
671 config_dbus_core_remove_hook(&hook);
672}