2 * Copyright © 2007 Daniel Stone
3 * Copyright © 2007 Red Hat, Inc.
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:
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
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.
24 * Author: Daniel Stone <daniel@fooishbar.org>
27 #ifdef HAVE_DIX_CONFIG_H
28 #include <dix-config.h>
31 #include <dbus/dbus.h>
32 #include <hal/libhal.h>
34 #include <sys/select.h>
39 #include "config-backends.h"
42 #define LIBHAL_PROP_KEY "input.x11_options."
43 #define LIBHAL_XKB_PROP_KEY "input.xkb."
45 struct config_hal_info
{
46 DBusConnection
*system_bus
;
47 LibHalContext
*hal_ctx
;
50 /* Used for special handling of xkb options. */
60 device_removed(LibHalContext
* ctx
, const char *udi
)
64 if (asprintf(&value
, "hal:%s", udi
) == -1)
67 remove_devices("hal", value
);
73 get_prop_string(LibHalContext
* hal_ctx
, const char *udi
, const char *name
)
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)");
82 libhal_free_string(prop
);
92 get_prop_string_array(LibHalContext
* hal_ctx
, const char *udi
,
95 char **props
, *ret
, *str
;
98 props
= libhal_device_get_property_strlist(hal_ctx
, udi
, prop
, NULL
);
100 for (i
= 0; props
[i
]; i
++)
101 len
+= strlen(props
[i
]);
103 ret
= calloc(sizeof(char), len
+ i
); /* i - 1 commas, 1 NULL */
105 libhal_free_string_array(props
);
110 for (i
= 0; props
[i
]; i
++) {
111 strcpy(str
, props
[i
]);
112 str
+= strlen(props
[i
]);
117 libhal_free_string_array(props
);
127 device_added(LibHalContext
* hal_ctx
, const char *udi
)
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
;
135 struct xkb_options xkb_opts
= { 0 };
138 LibHalPropertySet
*set
= NULL
;
139 LibHalPropertySetIterator set_iter
;
140 char *psi_key
= NULL
, *tmp_val
;
142 dbus_error_init(&error
);
144 driver
= get_prop_string(hal_ctx
, udi
, "input.x11_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
);
152 path
= get_prop_string(hal_ctx
, udi
, "input.device");
154 LogMessage(X_WARNING
,
155 "config/hal: no driver or path specified for %s\n", udi
);
158 attrs
.device
= strdup(path
);
160 name
= get_prop_string(hal_ctx
, udi
, "info.product");
162 name
= strdup("(unnamed)");
164 attrs
.product
= strdup(name
);
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
, ",");
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
;
184 parent
= get_prop_string(hal_ctx
, udi
, "info.parent");
186 int usb_vendor
, usb_product
;
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
)
205 attrs
.pnp_id
= get_prop_string(hal_ctx
, parent
, "pnp.id");
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");
219 input_options
= input_option_new(NULL
, "_source", "server/hal");
220 if (!input_options
) {
222 "config/hal: couldn't allocate first key/value pair\n");
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
);
231 input_options
= input_option_new(input_options
, "driver", driver
);
232 input_options
= input_option_new(input_options
, "name", name
);
234 if (asprintf(&config_info
, "hal:%s", udi
) == -1) {
236 LogMessage(X_ERROR
, "config/hal: couldn't allocate name\n");
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
);
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
);
253 "config/hal: couldn't get property list for %s: %s (%s)\n",
254 udi
, error
.name
, error
.message
);
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
);
265 /* normal options first (input.x11_options.<propname>) */
267 (psi_key
, LIBHAL_PROP_KEY
, sizeof(LIBHAL_PROP_KEY
) - 1)) {
270 /* only support strings for all values */
271 tmp_val
= get_prop_string(hal_ctx
, udi
, psi_key
);
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.
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
);
287 else if (!strcasecmp(&tmp
[3], "model")) {
288 free(xkb_opts
.model
);
289 xkb_opts
.model
= strdup(tmp_val
);
291 else if (!strcasecmp(&tmp
[3], "rules")) {
292 free(xkb_opts
.rules
);
293 xkb_opts
.rules
= strdup(tmp_val
);
295 else if (!strcasecmp(&tmp
[3], "variant")) {
296 free(xkb_opts
.variant
);
297 xkb_opts
.variant
= strdup(tmp_val
);
299 else if (!strcasecmp(&tmp
[3], "options")) {
300 free(xkb_opts
.options
);
301 xkb_opts
.options
= strdup(tmp_val
);
307 input_option_new(input_options
,
308 psi_key
+ sizeof(LIBHAL_PROP_KEY
) -
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")) &&
319 get_prop_string_array(hal_ctx
, udi
, psi_key
))) {
320 free(xkb_opts
.options
);
321 xkb_opts
.options
= strdup(tmp_val
);
325 else if (!strncasecmp
326 (psi_key
, LIBHAL_XKB_PROP_KEY
,
327 sizeof(LIBHAL_XKB_PROP_KEY
) - 1)) {
330 /* only support strings for all values */
331 tmp_val
= get_prop_string(hal_ctx
, udi
, psi_key
);
333 if (tmp_val
&& strlen(psi_key
) >= sizeof(LIBHAL_XKB_PROP_KEY
)) {
335 tmp
= &psi_key
[sizeof(LIBHAL_XKB_PROP_KEY
) - 1];
337 if (!strcasecmp(tmp
, "layout")) {
338 if (!xkb_opts
.layout
)
339 xkb_opts
.layout
= strdup(tmp_val
);
341 else if (!strcasecmp(tmp
, "rules")) {
343 xkb_opts
.rules
= strdup(tmp_val
);
345 else if (!strcasecmp(tmp
, "variant")) {
346 if (!xkb_opts
.variant
)
347 xkb_opts
.variant
= strdup(tmp_val
);
349 else if (!strcasecmp(tmp
, "model")) {
351 xkb_opts
.model
= strdup(tmp_val
);
353 else if (!strcasecmp(tmp
, "options")) {
354 if (!xkb_opts
.options
)
355 xkb_opts
.options
= strdup(tmp_val
);
360 /* server 1.4 had xkb options as strlist */
361 tmp_val
= get_prop_string_array(hal_ctx
, udi
, psi_key
);
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
);
373 /* psi_key doesn't need to be freed */
374 libhal_psi_next(&set_iter
);
377 /* Now add xkb options */
380 input_option_new(input_options
, "xkb_layout", xkb_opts
.layout
);
383 input_option_new(input_options
, "xkb_rules", xkb_opts
.rules
);
384 if (xkb_opts
.variant
)
386 input_option_new(input_options
, "xkb_variant", xkb_opts
.variant
);
389 input_option_new(input_options
, "xkb_model", xkb_opts
.model
);
390 if (xkb_opts
.options
)
392 input_option_new(input_options
, "xkb_options", xkb_opts
.options
);
393 input_options
= input_option_new(input_options
, "config_info", config_info
);
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",
406 libhal_free_property_set(set
);
411 input_option_free_list(&input_options
);
419 char **tag
= attrs
.tags
;
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
);
434 dbus_error_free(&error
);
440 disconnect_hook(void *data
)
443 struct config_hal_info
*info
= data
;
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
);
454 libhal_ctx_free(info
->hal_ctx
);
457 info
->hal_ctx
= NULL
;
458 info
->system_bus
= NULL
;
462 connect_and_register(DBusConnection
* connection
, struct config_hal_info
*info
)
469 return TRUE
; /* already registered, pretend we did something */
471 info
->system_bus
= connection
;
473 dbus_error_init(&error
);
475 info
->hal_ctx
= libhal_ctx_new();
476 if (!info
->hal_ctx
) {
477 LogMessage(X_ERROR
, "config/hal: couldn't create HAL context\n");
481 if (!libhal_ctx_set_dbus_connection(info
->hal_ctx
, info
->system_bus
)) {
483 "config/hal: couldn't associate HAL context with bus\n");
486 if (!libhal_ctx_init(info
->hal_ctx
, &error
)) {
488 "config/hal: couldn't initialise context: %s (%s)\n",
489 error
.name
? error
.name
: "unknown error",
490 error
.message
? error
.message
: "null");
493 if (!libhal_device_property_watch_all(info
->hal_ctx
, &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");
500 libhal_ctx_set_device_added(info
->hal_ctx
, device_added
);
501 libhal_ctx_set_device_removed(info
->hal_ctx
, device_removed
);
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");
512 for (i
= 0; i
< num_devices
; i
++)
513 device_added(info
->hal_ctx
, devices
[i
]);
514 libhal_free_string_array(devices
);
516 dbus_error_free(&error
);
521 dbus_error_free(&error
);
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
);
532 dbus_error_free(&error
);
535 libhal_ctx_free(info
->hal_ctx
);
538 info
->hal_ctx
= NULL
;
539 info
->system_bus
= NULL
;
545 * Handle NewOwnerChanged signals to deal with HAL startup at X server runtime.
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
552 static DBusHandlerResult
553 ownerchanged_handler(DBusConnection
* connection
, DBusMessage
* message
,
556 int ret
= DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
558 if (dbus_message_is_signal(message
,
559 "org.freedesktop.DBus", "NameOwnerChanged")) {
561 char *name
, *old_owner
, *new_owner
;
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
);
569 if (dbus_error_is_set(&error
)) {
571 ("[config/hal] failed to get NameOwnerChanged args: %s (%s)\n",
572 error
.name
, error
.message
);
574 else if (name
&& strcmp(name
, "org.freedesktop.Hal") == 0) {
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");
583 ErrorF("[config/hal] Failed to connect to HAL bus.\n");
586 ret
= DBUS_HANDLER_RESULT_HANDLED
;
588 dbus_error_free(&error
);
595 * Register a handler for the NameOwnerChanged signal.
598 listen_for_startup(DBusConnection
* connection
, void *data
)
600 DBusObjectPathVTable vtable
= {.message_function
= ownerchanged_handler
, };
602 const char MATCH_RULE
[] = "sender='org.freedesktop.DBus',"
603 "interface='org.freedesktop.DBus',"
605 "path='/org/freedesktop/DBus'," "member='NameOwnerChanged'";
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",
616 ErrorF("[config/hal] cannot register object path.\n");
619 ErrorF("[config/hal] couldn't add match rule: %s (%s)\n", error
.name
,
621 ErrorF("[config/hal] cannot detect a HAL startup.\n");
624 dbus_error_free(&error
);
630 connect_hook(DBusConnection
* connection
, void *data
)
632 struct config_hal_info
*info
= data
;
634 if (listen_for_startup(connection
, data
) &&
635 connect_and_register(connection
, info
))
636 dbus_connection_unregister_object_path(connection
,
637 "/org/freedesktop/DBus");
642 static struct config_hal_info hal_info
;
644 static struct config_dbus_core_hook hook
= {
645 .connect
= connect_hook
,
646 .disconnect
= disconnect_hook
,
651 config_hal_init(void)
653 memset(&hal_info
, 0, sizeof(hal_info
));
654 hal_info
.system_bus
= NULL
;
655 hal_info
.hal_ctx
= NULL
;
657 if (!config_dbus_core_add_hook(&hook
)) {
658 LogMessage(X_ERROR
, "config/hal: failed to add D-Bus hook\n");
662 /* verbose message */
663 LogMessageVerb(X_INFO
, 7, "config/hal: initialized\n");
669 config_hal_fini(void)
671 config_dbus_core_remove_hook(&hook
);