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
43#define LIBHAL_PROP_KEY "input.x11_options."
44#define LIBHAL_XKB_PROP_KEY "input.xkb."
45
46
47struct config_hal_info {
48    DBusConnection *system_bus;
49    LibHalContext *hal_ctx;
50};
51
52/* Used for special handling of xkb options. */
53struct xkb_options {
54    char* layout;
55    char* model;
56    char* rules;
57    char* variant;
58    char* options;
59};
60
61static void
62device_removed(LibHalContext *ctx, const char *udi)
63{
64    char *value;
65
66    if (asprintf (&value, "hal:%s", udi) == -1)
67        return;
68
69    remove_devices("hal", value);
70
71    free(value);
72}
73
74static char *
75get_prop_string(LibHalContext *hal_ctx, const char *udi, const char *name)
76{
77    char *prop, *ret;
78
79    prop = libhal_device_get_property_string(hal_ctx, udi, name, NULL);
80    LogMessageVerb(X_INFO, 10, "config/hal: getting %s on %s returned %s\n", name, udi, prop ? prop : "(null)");
81    if (prop) {
82        ret = strdup(prop);
83        libhal_free_string(prop);
84    }
85    else {
86        return NULL;
87    }
88
89    return ret;
90}
91
92static char *
93get_prop_string_array(LibHalContext *hal_ctx, const char *udi, 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 *options = NULL, *tmpo = 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
143    dbus_error_init(&error);
144
145    driver = get_prop_string(hal_ctx, udi, "input.x11_driver");
146    if (!driver){
147        /* verbose, don't tell the user unless they _want_ to see it */
148        LogMessageVerb(X_INFO,7,"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,"config/hal: no driver or path specified for %s\n", udi);
155        goto unwind;
156    }
157    attrs.device = strdup(path);
158
159    name = get_prop_string(hal_ctx, udi, "info.product");
160    if (!name)
161        name = strdup("(unnamed)");
162    else
163        attrs.product = strdup(name);
164
165    attrs.vendor = get_prop_string(hal_ctx, udi, "info.vendor");
166    hal_tags = get_prop_string(hal_ctx, udi, "input.tags");
167    attrs.tags = xstrtokenize(hal_tags, ",");
168    free(hal_tags);
169
170    if (libhal_device_query_capability(hal_ctx, udi, "input.keys", NULL))
171        attrs.flags |= ATTR_KEYBOARD;
172    if (libhal_device_query_capability(hal_ctx, udi, "input.mouse", NULL))
173        attrs.flags |= ATTR_POINTER;
174    if (libhal_device_query_capability(hal_ctx, udi, "input.joystick", NULL))
175        attrs.flags |= ATTR_JOYSTICK;
176    if (libhal_device_query_capability(hal_ctx, udi, "input.tablet", NULL))
177        attrs.flags |= ATTR_TABLET;
178    if (libhal_device_query_capability(hal_ctx, udi, "input.touchpad", NULL))
179        attrs.flags |= ATTR_TOUCHPAD;
180    if (libhal_device_query_capability(hal_ctx, udi, "input.touchscreen", NULL))
181        attrs.flags |= ATTR_TOUCHSCREEN;
182
183    parent = get_prop_string(hal_ctx, udi, "info.parent");
184    if (parent) {
185        int usb_vendor, usb_product;
186
187        attrs.pnp_id = get_prop_string(hal_ctx, parent, "pnp.id");
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        free(parent);
206    }
207
208    options = calloc(sizeof(*options), 1);
209    if (!options){
210        LogMessage(X_ERROR, "config/hal: couldn't allocate space for input options!\n");
211        goto unwind;
212    }
213
214    options->key = strdup("_source");
215    options->value = strdup("server/hal");
216    if (!options->key || !options->value) {
217        LogMessage(X_ERROR, "config/hal: couldn't allocate first key/value pair\n");
218        goto unwind;
219    }
220
221    /* most drivers use device.. not path. evdev uses both however, but the
222     * path version isn't documented apparently. support both for now. */
223    add_option(&options, "path", path);
224    add_option(&options, "device", path);
225
226    add_option(&options, "driver", driver);
227    add_option(&options, "name", name);
228
229    if (asprintf (&config_info, "hal:%s", udi) == -1) {
230        config_info = NULL;
231        LogMessage(X_ERROR, "config/hal: couldn't allocate name\n");
232        goto unwind;
233    }
234
235    /* Check for duplicate devices */
236    if (device_is_duplicate(config_info))
237    {
238        LogMessage(X_WARNING, "config/hal: device %s already added. Ignoring.\n", name);
239        goto unwind;
240    }
241
242    /* ok, grab options from hal.. iterate through all properties
243    * and lets see if any of them are options that we can add */
244    set = libhal_device_get_all_properties(hal_ctx, udi, &error);
245
246    if (!set) {
247        LogMessage(X_ERROR, "config/hal: couldn't get property list for %s: %s (%s)\n",
248               udi, error.name, error.message);
249        goto unwind;
250    }
251
252    libhal_psi_init(&set_iter,set);
253    while (libhal_psi_has_more(&set_iter)) {
254        /* we are looking for supported keys.. extract and add to options */
255        psi_key = libhal_psi_get_key(&set_iter);
256
257        if (psi_key){
258
259            /* normal options first (input.x11_options.<propname>) */
260            if (!strncasecmp(psi_key, LIBHAL_PROP_KEY, sizeof(LIBHAL_PROP_KEY)-1)){
261                char* tmp;
262
263                /* only support strings for all values */
264                tmp_val = get_prop_string(hal_ctx, udi, psi_key);
265
266                if (tmp_val){
267
268                    /* xkb needs special handling. HAL specs include
269                     * input.xkb.xyz options, but the x11-input.fdi specifies
270                     * input.x11_options.Xkbxyz options. By default, we use
271                     * the former, unless the specific X11 ones are specified.
272                     * Since we can't predict the order in which the keys
273                     * arrive, we need to store them.
274                     */
275                    if ((tmp = strcasestr(psi_key, "xkb")) && strlen(tmp) >= 4)
276                    {
277                        if (!strcasecmp(&tmp[3], "layout"))
278                        {
279                            free(xkb_opts.layout);
280                            xkb_opts.layout = strdup(tmp_val);
281                        } else if (!strcasecmp(&tmp[3], "model"))
282                        {
283                            free(xkb_opts.model);
284                            xkb_opts.model = strdup(tmp_val);
285                        } else if (!strcasecmp(&tmp[3], "rules"))
286                        {
287                            free(xkb_opts.rules);
288                            xkb_opts.rules = strdup(tmp_val);
289                        } else if (!strcasecmp(&tmp[3], "variant"))
290                        {
291                            free(xkb_opts.variant);
292                            xkb_opts.variant = strdup(tmp_val);
293                        } else if (!strcasecmp(&tmp[3], "options"))
294                        {
295                            free(xkb_opts.options);
296                            xkb_opts.options = strdup(tmp_val);
297                        }
298                    } else
299                    {
300                        /* all others */
301                        add_option(&options, psi_key + sizeof(LIBHAL_PROP_KEY)-1, tmp_val);
302                        free(tmp_val);
303                    }
304                } else
305                {
306                    /* server 1.4 had xkb_options as strlist. */
307                    if ((tmp = strcasestr(psi_key, "xkb")) &&
308                        (strlen(tmp) >= 4) &&
309                        (!strcasecmp(&tmp[3], "options")) &&
310                        (tmp_val = get_prop_string_array(hal_ctx, udi, psi_key)))
311                    {
312                        free(xkb_opts.options);
313                        xkb_opts.options = strdup(tmp_val);
314                    }
315                }
316            } else if (!strncasecmp(psi_key, LIBHAL_XKB_PROP_KEY, sizeof(LIBHAL_XKB_PROP_KEY)-1)){
317                char* tmp;
318
319                /* only support strings for all values */
320                tmp_val = get_prop_string(hal_ctx, udi, psi_key);
321
322                if (tmp_val && strlen(psi_key) >= sizeof(LIBHAL_XKB_PROP_KEY)) {
323
324                    tmp = &psi_key[sizeof(LIBHAL_XKB_PROP_KEY) - 1];
325
326                    if (!strcasecmp(tmp, "layout"))
327                    {
328                        if (!xkb_opts.layout)
329                            xkb_opts.layout = strdup(tmp_val);
330                    } else if (!strcasecmp(tmp, "rules"))
331                    {
332                        if (!xkb_opts.rules)
333                            xkb_opts.rules = strdup(tmp_val);
334                    } else if (!strcasecmp(tmp, "variant"))
335                    {
336                        if (!xkb_opts.variant)
337                            xkb_opts.variant = strdup(tmp_val);
338                    } else if (!strcasecmp(tmp, "model"))
339                    {
340                        if (!xkb_opts.model)
341                            xkb_opts.model = strdup(tmp_val);
342                    } else if (!strcasecmp(tmp, "options"))
343                    {
344                        if (!xkb_opts.options)
345                            xkb_opts.options = strdup(tmp_val);
346                    }
347                    free(tmp_val);
348                } else
349                {
350                    /* server 1.4 had xkb options as strlist */
351                    tmp_val = get_prop_string_array(hal_ctx, udi, psi_key);
352                    if (tmp_val && strlen(psi_key) >= sizeof(LIBHAL_XKB_PROP_KEY))
353                    {
354                        tmp = &psi_key[sizeof(LIBHAL_XKB_PROP_KEY) - 1];
355                        if (!strcasecmp(tmp, ".options") && (!xkb_opts.options))
356                            xkb_opts.options = strdup(tmp_val);
357                    }
358                    free(tmp_val);
359                }
360            }
361        }
362
363        /* psi_key doesn't need to be freed */
364        libhal_psi_next(&set_iter);
365    }
366
367
368    /* Now add xkb options */
369    if (xkb_opts.layout)
370        add_option(&options, "xkb_layout", xkb_opts.layout);
371    if (xkb_opts.rules)
372        add_option(&options, "xkb_rules", xkb_opts.rules);
373    if (xkb_opts.variant)
374        add_option(&options, "xkb_variant", xkb_opts.variant);
375    if (xkb_opts.model)
376        add_option(&options, "xkb_model", xkb_opts.model);
377    if (xkb_opts.options)
378        add_option(&options, "xkb_options", xkb_opts.options);
379    add_option(&options, "config_info", config_info);
380
381    /* this isn't an error, but how else do you output something that the user can see? */
382    LogMessage(X_INFO, "config/hal: Adding input device %s\n", name);
383    if ((rc = NewInputDeviceRequest(options, &attrs, &dev)) != Success) {
384        LogMessage(X_ERROR, "config/hal: NewInputDeviceRequest failed (%d)\n", rc);
385        dev = NULL;
386        goto unwind;
387    }
388
389unwind:
390    if (set)
391        libhal_free_property_set(set);
392    free(path);
393    free(driver);
394    free(name);
395    free(config_info);
396    while ((tmpo = options)) {
397        options = tmpo->next;
398        free(tmpo->key);        /* NULL if dev != NULL */
399        free(tmpo->value);      /* NULL if dev != NULL */
400        free(tmpo);
401    }
402
403    free(attrs.product);
404    free(attrs.vendor);
405    free(attrs.device);
406    free(attrs.pnp_id);
407    free(attrs.usb_id);
408    if (attrs.tags) {
409        char **tag = attrs.tags;
410        while (*tag) {
411            free(*tag);
412            tag++;
413        }
414        free(attrs.tags);
415    }
416
417    free(xkb_opts.layout);
418    free(xkb_opts.rules);
419    free(xkb_opts.model);
420    free(xkb_opts.variant);
421    free(xkb_opts.options);
422
423    dbus_error_free(&error);
424
425    return;
426}
427
428static void
429disconnect_hook(void *data)
430{
431    DBusError error;
432    struct config_hal_info *info = data;
433
434    if (info->hal_ctx) {
435        if (dbus_connection_get_is_connected(info->system_bus)) {
436            dbus_error_init(&error);
437            if (!libhal_ctx_shutdown(info->hal_ctx, &error))
438                LogMessage(X_WARNING, "config/hal: disconnect_hook couldn't shut down context: %s (%s)\n",
439                        error.name, error.message);
440            dbus_error_free(&error);
441        }
442        libhal_ctx_free(info->hal_ctx);
443    }
444
445    info->hal_ctx = NULL;
446    info->system_bus = NULL;
447}
448
449static BOOL
450connect_and_register(DBusConnection *connection, struct config_hal_info *info)
451{
452    DBusError error;
453    char **devices;
454    int num_devices, i;
455
456    if (info->hal_ctx)
457        return TRUE; /* already registered, pretend we did something */
458
459    info->system_bus = connection;
460
461    dbus_error_init(&error);
462
463    info->hal_ctx = libhal_ctx_new();
464    if (!info->hal_ctx) {
465        LogMessage(X_ERROR, "config/hal: couldn't create HAL context\n");
466        goto out_err;
467    }
468
469    if (!libhal_ctx_set_dbus_connection(info->hal_ctx, info->system_bus)) {
470        LogMessage(X_ERROR, "config/hal: couldn't associate HAL context with bus\n");
471        goto out_err;
472    }
473    if (!libhal_ctx_init(info->hal_ctx, &error)) {
474        LogMessage(X_ERROR, "config/hal: couldn't initialise context: %s (%s)\n",
475		   error.name ? error.name : "unknown error",
476		   error.message ? error.message : "null");
477        goto out_err;
478    }
479    if (!libhal_device_property_watch_all(info->hal_ctx, &error)) {
480        LogMessage(X_ERROR, "config/hal: couldn't watch all properties: %s (%s)\n",
481		   error.name ? error.name : "unknown error",
482		   error.message ? error.message : "null");
483        goto out_ctx;
484    }
485    libhal_ctx_set_device_added(info->hal_ctx, device_added);
486    libhal_ctx_set_device_removed(info->hal_ctx, device_removed);
487
488    devices = libhal_find_device_by_capability(info->hal_ctx, "input",
489                                               &num_devices, &error);
490    /* FIXME: Get default devices if error is set. */
491    if (dbus_error_is_set(&error)) {
492        LogMessage(X_ERROR, "config/hal: couldn't find input device: %s (%s)\n",
493		   error.name ? error.name : "unknown error",
494		   error.message ? error.message : "null");
495        goto out_ctx;
496    }
497    for (i = 0; i < num_devices; i++)
498        device_added(info->hal_ctx, devices[i]);
499    libhal_free_string_array(devices);
500
501    dbus_error_free(&error);
502
503    return TRUE;
504
505out_ctx:
506    dbus_error_free(&error);
507
508    if (!libhal_ctx_shutdown(info->hal_ctx, &error)) {
509        LogMessage(X_WARNING, "config/hal: couldn't shut down context: %s (%s)\n",
510                error.name ? error.name : "unknown error",
511                error.message ? error.message : "null");
512        dbus_error_free(&error);
513    }
514
515out_err:
516    dbus_error_free(&error);
517
518    if (info->hal_ctx) {
519        libhal_ctx_free(info->hal_ctx);
520    }
521
522    info->hal_ctx = NULL;
523    info->system_bus = NULL;
524
525    return FALSE;
526}
527
528
529/**
530 * Handle NewOwnerChanged signals to deal with HAL startup at X server runtime.
531 *
532 * NewOwnerChanged is send once when HAL shuts down, and once again when it
533 * comes back up. Message has three arguments, first is the name
534 * (org.freedesktop.Hal), the second one is the old owner, third one is new
535 * owner.
536 */
537static DBusHandlerResult
538ownerchanged_handler(DBusConnection *connection, DBusMessage *message, void *data)
539{
540    int ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
541
542    if (dbus_message_is_signal(message,
543                               "org.freedesktop.DBus",
544                               "NameOwnerChanged")) {
545        DBusError error;
546        char *name, *old_owner, *new_owner;
547
548        dbus_error_init(&error);
549        dbus_message_get_args(message, &error,
550                              DBUS_TYPE_STRING, &name,
551                              DBUS_TYPE_STRING, &old_owner,
552                              DBUS_TYPE_STRING, &new_owner,
553                              DBUS_TYPE_INVALID);
554
555        if (dbus_error_is_set(&error)) {
556            ErrorF("[config/hal] failed to get NameOwnerChanged args: %s (%s)\n",
557                   error.name, error.message);
558        } else if (name && strcmp(name, "org.freedesktop.Hal") == 0) {
559
560            if (!old_owner || !strlen(old_owner)) {
561                DebugF("[config/hal] HAL startup detected.\n");
562                if (connect_and_register(connection, (struct config_hal_info*)data))
563                    dbus_connection_unregister_object_path(connection,
564                                                     "/org/freedesktop/DBus");
565                else
566                    ErrorF("[config/hal] Failed to connect to HAL bus.\n");
567            }
568
569            ret = DBUS_HANDLER_RESULT_HANDLED;
570        }
571        dbus_error_free(&error);
572    }
573
574    return ret;
575}
576
577/**
578 * Register a handler for the NameOwnerChanged signal.
579 */
580static BOOL
581listen_for_startup(DBusConnection *connection, void *data)
582{
583    DBusObjectPathVTable vtable = { .message_function = ownerchanged_handler, };
584    DBusError error;
585    const char MATCH_RULE[] = "sender='org.freedesktop.DBus',"
586                              "interface='org.freedesktop.DBus',"
587                              "type='signal',"
588                              "path='/org/freedesktop/DBus',"
589                              "member='NameOwnerChanged'";
590    int rc = FALSE;
591
592    dbus_error_init(&error);
593    dbus_bus_add_match(connection, MATCH_RULE, &error);
594    if (!dbus_error_is_set(&error)) {
595        if (dbus_connection_register_object_path(connection,
596                                                  "/org/freedesktop/DBus",
597                                                  &vtable,
598                                                  data))
599            rc = TRUE;
600        else
601            ErrorF("[config/hal] cannot register object path.\n");
602    } else {
603        ErrorF("[config/hal] couldn't add match rule: %s (%s)\n", error.name,
604                error.message);
605        ErrorF("[config/hal] cannot detect a HAL startup.\n");
606    }
607
608    dbus_error_free(&error);
609
610    return rc;
611}
612
613static void
614connect_hook(DBusConnection *connection, void *data)
615{
616    struct config_hal_info *info = data;
617
618    if (listen_for_startup(connection, data) &&
619        connect_and_register(connection, info))
620        dbus_connection_unregister_object_path(connection,
621                                               "/org/freedesktop/DBus");
622
623    return;
624}
625
626static struct config_hal_info hal_info;
627static struct config_dbus_core_hook hook = {
628    .connect = connect_hook,
629    .disconnect = disconnect_hook,
630    .data = &hal_info,
631};
632
633int
634config_hal_init(void)
635{
636    memset(&hal_info, 0, sizeof(hal_info));
637    hal_info.system_bus = NULL;
638    hal_info.hal_ctx = NULL;
639
640    if (!config_dbus_core_add_hook(&hook)) {
641        LogMessage(X_ERROR, "config/hal: failed to add D-Bus hook\n");
642        return 0;
643    }
644
645    /* verbose message */
646    LogMessageVerb(X_INFO,7,"config/hal: initialized\n");
647
648    return 1;
649}
650
651void
652config_hal_fini(void)
653{
654    config_dbus_core_remove_hook(&hook);
655}
656