1/*
2 * Copyright © 2006-2007 Daniel Stone
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: Daniel Stone <daniel@fooishbar.org>
24 */
25
26#ifdef HAVE_DIX_CONFIG_H
27#include <dix-config.h>
28#endif
29
30#include <dbus/dbus.h>
31#include <string.h>
32
33#include <X11/X.h>
34
35#include "config-backends.h"
36#include "opaque.h" /* for 'display': there should be a better way. */
37#include "input.h"
38#include "inputstr.h"
39
40#define API_VERSION 2
41
42#define MATCH_RULE "type='method_call',interface='org.x.config.input'"
43
44#define MALFORMED_MSG "[config/dbus] malformed message, dropping"
45#define MALFORMED_MESSAGE() { DebugF(MALFORMED_MSG "\n"); \
46                            ret = BadValue; \
47                            goto unwind; }
48#define MALFORMED_MESSAGE_ERROR() { DebugF(MALFORMED_MSG ": %s, %s", \
49                                       error->name, error->message); \
50                                  ret = BadValue; \
51                                  goto unwind; }
52
53struct connection_info {
54    char busobject[32];
55    char busname[64];
56    DBusConnection *connection;
57};
58
59static void
60reset_info(struct connection_info *info)
61{
62    info->connection = NULL;
63    info->busname[0] = '\0';
64    info->busobject[0] = '\0';
65}
66
67static int
68add_device(DBusMessage *message, DBusMessage *reply, DBusError *error)
69{
70    DBusMessageIter iter, reply_iter, subiter;
71    InputOption *tmpo = NULL, *options = NULL;
72    char *tmp = NULL;
73    int ret, err;
74    DeviceIntPtr dev = NULL;
75
76    dbus_message_iter_init_append(reply, &reply_iter);
77
78    if (!dbus_message_iter_init(message, &iter)) {
79        ErrorF("[config/dbus] couldn't initialise iterator\n");
80        MALFORMED_MESSAGE();
81    }
82
83    options = calloc(sizeof(*options), 1);
84    if (!options) {
85        ErrorF("[config/dbus] couldn't allocate option\n");
86        return BadAlloc;
87    }
88
89    options->key = strdup("_source");
90    options->value = strdup("client/dbus");
91    if (!options->key || !options->value) {
92        ErrorF("[config/dbus] couldn't allocate first key/value pair\n");
93        ret = BadAlloc;
94        goto unwind;
95    }
96
97    /* signature should be [ss][ss]... */
98    while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) {
99        tmpo = calloc(sizeof(*tmpo), 1);
100        if (!tmpo) {
101            ErrorF("[config/dbus] couldn't allocate option\n");
102            ret = BadAlloc;
103            goto unwind;
104        }
105        tmpo->next = options;
106        options = tmpo;
107
108        dbus_message_iter_recurse(&iter, &subiter);
109
110        if (dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_STRING)
111            MALFORMED_MESSAGE();
112
113        dbus_message_iter_get_basic(&subiter, &tmp);
114        if (!tmp)
115            MALFORMED_MESSAGE();
116        /* The _ prefix refers to internal settings, and may not be given by
117         * the client. */
118        if (tmp[0] == '_') {
119            ErrorF("[config/dbus] attempted subterfuge: option name %s given\n",
120                   tmp);
121            MALFORMED_MESSAGE();
122        }
123        options->key = strdup(tmp);
124        if (!options->key) {
125            ErrorF("[config/dbus] couldn't duplicate key!\n");
126            ret = BadAlloc;
127            goto unwind;
128        }
129
130        if (!dbus_message_iter_has_next(&subiter))
131            MALFORMED_MESSAGE();
132        dbus_message_iter_next(&subiter);
133        if (dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_STRING)
134            MALFORMED_MESSAGE();
135
136        dbus_message_iter_get_basic(&subiter, &tmp);
137        if (!tmp)
138            MALFORMED_MESSAGE();
139        options->value = strdup(tmp);
140        if (!options->value) {
141            ErrorF("[config/dbus] couldn't duplicate option!\n");
142            ret = BadAlloc;
143            goto unwind;
144        }
145
146        dbus_message_iter_next(&iter);
147    }
148
149    ret = NewInputDeviceRequest(options, NULL, &dev);
150    if (ret != Success) {
151        DebugF("[config/dbus] NewInputDeviceRequest failed\n");
152        goto unwind;
153    }
154
155    if (!dev) {
156        DebugF("[config/dbus] NewInputDeviceRequest provided no device\n");
157        ret = BadImplementation;
158        goto unwind;
159    }
160
161    /* XXX: If we fail halfway through, we don't seem to have any way to
162     *      empty the iterator, so you'll end up with some device IDs,
163     *      plus an error.  This seems to be a shortcoming in the D-Bus
164     *      API. */
165    for (; dev; dev = dev->next) {
166        if (!dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32,
167                                            &dev->id)) {
168            ErrorF("[config/dbus] couldn't append to iterator\n");
169            ret = BadAlloc;
170            goto unwind;
171        }
172    }
173
174unwind:
175    if (ret != Success) {
176        if (dev)
177            RemoveDevice(dev, TRUE);
178
179        err = -ret;
180        dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, &err);
181    }
182
183    while (options) {
184        tmpo = options;
185        options = options->next;
186        free(tmpo->key);
187        free(tmpo->value);
188        free(tmpo);
189    }
190
191    return ret;
192}
193
194static int
195remove_device(DBusMessage *message, DBusMessage *reply, DBusError *error)
196{
197    int deviceid, ret, err;
198    DeviceIntPtr dev;
199    DBusMessageIter iter, reply_iter;
200
201    dbus_message_iter_init_append(reply, &reply_iter);
202
203    if (!dbus_message_iter_init(message, &iter)) {
204        ErrorF("[config/dbus] failed to init iterator\n");
205        MALFORMED_MESSAGE();
206    }
207
208    if (!dbus_message_get_args(message, error, DBUS_TYPE_UINT32,
209                               &deviceid, DBUS_TYPE_INVALID)) {
210        MALFORMED_MESSAGE_ERROR();
211    }
212
213    dixLookupDevice(&dev, deviceid, serverClient, DixDestroyAccess);
214    if (!dev) {
215        DebugF("[config/dbus] bogus device id %d given\n", deviceid);
216        ret = BadMatch;
217        goto unwind;
218    }
219
220    DebugF("[config/dbus] removing device %s (id %d)\n", dev->name, deviceid);
221
222    /* Call PIE here so we don't try to dereference a device that's
223     * already been removed. */
224    OsBlockSignals();
225    ProcessInputEvents();
226    DeleteInputDeviceRequest(dev);
227    OsReleaseSignals();
228
229    ret = Success;
230
231unwind:
232    err = (ret == Success) ? ret : -ret;
233    dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, &err);
234
235    return ret;
236}
237
238static int
239list_devices(DBusMessage *message, DBusMessage *reply, DBusError *error)
240{
241    DeviceIntPtr dev;
242    DBusMessageIter iter, subiter;
243
244    dbus_message_iter_init_append(reply, &iter);
245
246    for (dev = inputInfo.devices; dev; dev = dev->next) {
247        if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL,
248                                              &subiter)) {
249            ErrorF("[config/dbus] couldn't init container\n");
250            return BadAlloc;
251        }
252        if (!dbus_message_iter_append_basic(&subiter, DBUS_TYPE_UINT32,
253                                            &dev->id)) {
254            ErrorF("[config/dbus] couldn't append to iterator\n");
255            return BadAlloc;
256        }
257        if (!dbus_message_iter_append_basic(&subiter, DBUS_TYPE_STRING,
258                                            &dev->name)) {
259            ErrorF("[config/dbus] couldn't append to iterator\n");
260            return BadAlloc;
261        }
262        if (!dbus_message_iter_close_container(&iter, &subiter)) {
263            ErrorF("[config/dbus] couldn't close container\n");
264            return BadAlloc;
265        }
266    }
267
268    return Success;
269}
270
271static int
272get_version(DBusMessage *message, DBusMessage *reply, DBusError *error)
273{
274    DBusMessageIter iter;
275    unsigned int version = API_VERSION;
276
277    dbus_message_iter_init_append(reply, &iter);
278    if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &version)) {
279        ErrorF("[config/dbus] couldn't append version\n");
280        return BadAlloc;
281    }
282
283    return Success;
284}
285
286static DBusHandlerResult
287message_handler(DBusConnection *connection, DBusMessage *message, void *data)
288{
289    DBusError error;
290    DBusMessage *reply;
291    struct connection_info *info = data;
292
293    /* ret is the overall D-Bus handler result, whereas err is the internal
294     * X error from our individual functions. */
295    int ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
296    int err;
297
298    DebugF("[config/dbus] received a message for %s\n",
299           dbus_message_get_interface(message));
300
301    dbus_error_init(&error);
302
303    reply = dbus_message_new_method_return(message);
304    if (!reply) {
305        ErrorF("[config/dbus] failed to create reply\n");
306        ret = DBUS_HANDLER_RESULT_NEED_MEMORY;
307        goto err_start;
308    }
309
310    if (strcmp(dbus_message_get_member(message), "add") == 0)
311        err = add_device(message, reply, &error);
312    else if (strcmp(dbus_message_get_member(message), "remove") == 0)
313        err = remove_device(message, reply, &error);
314    else if (strcmp(dbus_message_get_member(message), "listDevices") == 0)
315        err = list_devices(message, reply, &error);
316    else if (strcmp(dbus_message_get_member(message), "version") == 0)
317        err = get_version(message, reply, &error);
318    else
319        goto err_reply;
320
321    /* Failure to allocate is a special case. */
322    if (err == BadAlloc) {
323        ret = DBUS_HANDLER_RESULT_NEED_MEMORY;
324        goto err_reply;
325    }
326
327    /* While failure here is always an OOM, we don't return that,
328     * since that would result in devices being double-added/removed. */
329    if (dbus_connection_send(info->connection, reply, NULL))
330        dbus_connection_flush(info->connection);
331    else
332        ErrorF("[config/dbus] failed to send reply\n");
333
334    ret = DBUS_HANDLER_RESULT_HANDLED;
335
336err_reply:
337    dbus_message_unref(reply);
338err_start:
339    dbus_error_free(&error);
340
341    return ret;
342}
343
344static void
345connect_hook(DBusConnection *connection, void *data)
346{
347    DBusError error;
348    DBusObjectPathVTable vtable = { .message_function = message_handler, };
349    struct connection_info *info = data;
350
351    info->connection = connection;
352
353    dbus_error_init(&error);
354
355    dbus_bus_request_name(info->connection, info->busname, 0, &error);
356    if (dbus_error_is_set(&error)) {
357        ErrorF("[config/dbus] couldn't take over org.x.config: %s (%s)\n",
358               error.name, error.message);
359        goto err_start;
360    }
361
362    /* blocks until we get a reply. */
363    dbus_bus_add_match(info->connection, MATCH_RULE, &error);
364    if (dbus_error_is_set(&error)) {
365        ErrorF("[config/dbus] couldn't add match: %s (%s)\n", error.name,
366               error.message);
367        goto err_name;
368    }
369
370    if (!dbus_connection_register_object_path(info->connection,
371                                              info->busobject, &vtable,
372                                              info)) {
373        ErrorF("[config/dbus] couldn't register object path\n");
374        goto err_match;
375    }
376
377    DebugF("[dbus] registered %s, %s\n", info->busname, info->busobject);
378
379    dbus_error_free(&error);
380
381    return;
382
383err_match:
384    dbus_bus_remove_match(info->connection, MATCH_RULE, &error);
385err_name:
386    dbus_bus_release_name(info->connection, info->busname, &error);
387err_start:
388    dbus_error_free(&error);
389
390    reset_info(info);
391}
392
393static void
394disconnect_hook(void *data)
395{
396}
397
398#if 0
399void
400pre_disconnect_hook(void)
401{
402    DBusError error;
403
404    dbus_error_init(&error);
405    dbus_connection_unregister_object_path(connection_data->connection,
406                                           connection_data->busobject);
407    dbus_bus_remove_match(connection_data->connection, MATCH_RULE,
408                          &error);
409    dbus_bus_release_name(connection_data->connection,
410                          connection_data->busname, &error);
411    dbus_error_free(&error);
412}
413#endif
414
415static struct connection_info connection_data;
416static struct config_dbus_core_hook core_hook = {
417    .connect = connect_hook,
418    .disconnect = disconnect_hook,
419    .data = &connection_data,
420};
421
422int
423config_dbus_init(void)
424{
425    snprintf(connection_data.busname, sizeof(connection_data.busname),
426             "org.x.config.display%d", atoi(display));
427    snprintf(connection_data.busobject, sizeof(connection_data.busobject),
428             "/org/x/config/%d", atoi(display));
429
430    return config_dbus_core_add_hook(&core_hook);
431}
432
433void
434config_dbus_fini(void)
435{
436    config_dbus_core_remove_hook(&core_hook);
437    connection_data.busname[0] = '\0';
438    connection_data.busobject[0] = '\0';
439}
440