/* * Copyright © 2006-2007 Daniel Stone * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Author: Daniel Stone <daniel@fooishbar.org> */ #ifdef HAVE_DIX_CONFIG_H #include <dix-config.h> #endif #include <dbus/dbus.h> #include <string.h> #include <X11/X.h> #include "config-backends.h" #include "opaque.h" /* for 'display': there should be a better way. */ #include "input.h" #include "inputstr.h" #define API_VERSION 2 #define MATCH_RULE "type='method_call',interface='org.x.config.input'" #define MALFORMED_MSG "[config/dbus] malformed message, dropping" #define MALFORMED_MESSAGE() { DebugF(MALFORMED_MSG "\n"); \ ret = BadValue; \ goto unwind; } #define MALFORMED_MESSAGE_ERROR() { DebugF(MALFORMED_MSG ": %s, %s", \ error->name, error->message); \ ret = BadValue; \ goto unwind; } struct connection_info { char busobject[32]; char busname[64]; DBusConnection *connection; }; static void reset_info(struct connection_info *info) { info->connection = NULL; info->busname[0] = '\0'; info->busobject[0] = '\0'; } static int add_device(DBusMessage *message, DBusMessage *reply, DBusError *error) { DBusMessageIter iter, reply_iter, subiter; InputOption *tmpo = NULL, *options = NULL; char *tmp = NULL; int ret, err; DeviceIntPtr dev = NULL; dbus_message_iter_init_append(reply, &reply_iter); if (!dbus_message_iter_init(message, &iter)) { ErrorF("[config/dbus] couldn't initialise iterator\n"); MALFORMED_MESSAGE(); } options = calloc(sizeof(*options), 1); if (!options) { ErrorF("[config/dbus] couldn't allocate option\n"); return BadAlloc; } options->key = strdup("_source"); options->value = strdup("client/dbus"); if (!options->key || !options->value) { ErrorF("[config/dbus] couldn't allocate first key/value pair\n"); ret = BadAlloc; goto unwind; } /* signature should be [ss][ss]... */ while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) { tmpo = calloc(sizeof(*tmpo), 1); if (!tmpo) { ErrorF("[config/dbus] couldn't allocate option\n"); ret = BadAlloc; goto unwind; } tmpo->next = options; options = tmpo; dbus_message_iter_recurse(&iter, &subiter); if (dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_STRING) MALFORMED_MESSAGE(); dbus_message_iter_get_basic(&subiter, &tmp); if (!tmp) MALFORMED_MESSAGE(); /* The _ prefix refers to internal settings, and may not be given by * the client. */ if (tmp[0] == '_') { ErrorF("[config/dbus] attempted subterfuge: option name %s given\n", tmp); MALFORMED_MESSAGE(); } options->key = strdup(tmp); if (!options->key) { ErrorF("[config/dbus] couldn't duplicate key!\n"); ret = BadAlloc; goto unwind; } if (!dbus_message_iter_has_next(&subiter)) MALFORMED_MESSAGE(); dbus_message_iter_next(&subiter); if (dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_STRING) MALFORMED_MESSAGE(); dbus_message_iter_get_basic(&subiter, &tmp); if (!tmp) MALFORMED_MESSAGE(); options->value = strdup(tmp); if (!options->value) { ErrorF("[config/dbus] couldn't duplicate option!\n"); ret = BadAlloc; goto unwind; } dbus_message_iter_next(&iter); } ret = NewInputDeviceRequest(options, NULL, &dev); if (ret != Success) { DebugF("[config/dbus] NewInputDeviceRequest failed\n"); goto unwind; } if (!dev) { DebugF("[config/dbus] NewInputDeviceRequest provided no device\n"); ret = BadImplementation; goto unwind; } /* XXX: If we fail halfway through, we don't seem to have any way to * empty the iterator, so you'll end up with some device IDs, * plus an error. This seems to be a shortcoming in the D-Bus * API. */ for (; dev; dev = dev->next) { if (!dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, &dev->id)) { ErrorF("[config/dbus] couldn't append to iterator\n"); ret = BadAlloc; goto unwind; } } unwind: if (ret != Success) { if (dev) RemoveDevice(dev, TRUE); err = -ret; dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, &err); } while (options) { tmpo = options; options = options->next; free(tmpo->key); free(tmpo->value); free(tmpo); } return ret; } static int remove_device(DBusMessage *message, DBusMessage *reply, DBusError *error) { int deviceid, ret, err; DeviceIntPtr dev; DBusMessageIter iter, reply_iter; dbus_message_iter_init_append(reply, &reply_iter); if (!dbus_message_iter_init(message, &iter)) { ErrorF("[config/dbus] failed to init iterator\n"); MALFORMED_MESSAGE(); } if (!dbus_message_get_args(message, error, DBUS_TYPE_UINT32, &deviceid, DBUS_TYPE_INVALID)) { MALFORMED_MESSAGE_ERROR(); } dixLookupDevice(&dev, deviceid, serverClient, DixDestroyAccess); if (!dev) { DebugF("[config/dbus] bogus device id %d given\n", deviceid); ret = BadMatch; goto unwind; } DebugF("[config/dbus] removing device %s (id %d)\n", dev->name, deviceid); /* Call PIE here so we don't try to dereference a device that's * already been removed. */ OsBlockSignals(); ProcessInputEvents(); DeleteInputDeviceRequest(dev); OsReleaseSignals(); ret = Success; unwind: err = (ret == Success) ? ret : -ret; dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, &err); return ret; } static int list_devices(DBusMessage *message, DBusMessage *reply, DBusError *error) { DeviceIntPtr dev; DBusMessageIter iter, subiter; dbus_message_iter_init_append(reply, &iter); for (dev = inputInfo.devices; dev; dev = dev->next) { if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &subiter)) { ErrorF("[config/dbus] couldn't init container\n"); return BadAlloc; } if (!dbus_message_iter_append_basic(&subiter, DBUS_TYPE_UINT32, &dev->id)) { ErrorF("[config/dbus] couldn't append to iterator\n"); return BadAlloc; } if (!dbus_message_iter_append_basic(&subiter, DBUS_TYPE_STRING, &dev->name)) { ErrorF("[config/dbus] couldn't append to iterator\n"); return BadAlloc; } if (!dbus_message_iter_close_container(&iter, &subiter)) { ErrorF("[config/dbus] couldn't close container\n"); return BadAlloc; } } return Success; } static int get_version(DBusMessage *message, DBusMessage *reply, DBusError *error) { DBusMessageIter iter; unsigned int version = API_VERSION; dbus_message_iter_init_append(reply, &iter); if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &version)) { ErrorF("[config/dbus] couldn't append version\n"); return BadAlloc; } return Success; } static DBusHandlerResult message_handler(DBusConnection *connection, DBusMessage *message, void *data) { DBusError error; DBusMessage *reply; struct connection_info *info = data; /* ret is the overall D-Bus handler result, whereas err is the internal * X error from our individual functions. */ int ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; int err; DebugF("[config/dbus] received a message for %s\n", dbus_message_get_interface(message)); dbus_error_init(&error); reply = dbus_message_new_method_return(message); if (!reply) { ErrorF("[config/dbus] failed to create reply\n"); ret = DBUS_HANDLER_RESULT_NEED_MEMORY; goto err_start; } if (strcmp(dbus_message_get_member(message), "add") == 0) err = add_device(message, reply, &error); else if (strcmp(dbus_message_get_member(message), "remove") == 0) err = remove_device(message, reply, &error); else if (strcmp(dbus_message_get_member(message), "listDevices") == 0) err = list_devices(message, reply, &error); else if (strcmp(dbus_message_get_member(message), "version") == 0) err = get_version(message, reply, &error); else goto err_reply; /* Failure to allocate is a special case. */ if (err == BadAlloc) { ret = DBUS_HANDLER_RESULT_NEED_MEMORY; goto err_reply; } /* While failure here is always an OOM, we don't return that, * since that would result in devices being double-added/removed. */ if (dbus_connection_send(info->connection, reply, NULL)) dbus_connection_flush(info->connection); else ErrorF("[config/dbus] failed to send reply\n"); ret = DBUS_HANDLER_RESULT_HANDLED; err_reply: dbus_message_unref(reply); err_start: dbus_error_free(&error); return ret; } static void connect_hook(DBusConnection *connection, void *data) { DBusError error; DBusObjectPathVTable vtable = { .message_function = message_handler, }; struct connection_info *info = data; info->connection = connection; dbus_error_init(&error); dbus_bus_request_name(info->connection, info->busname, 0, &error); if (dbus_error_is_set(&error)) { ErrorF("[config/dbus] couldn't take over org.x.config: %s (%s)\n", error.name, error.message); goto err_start; } /* blocks until we get a reply. */ dbus_bus_add_match(info->connection, MATCH_RULE, &error); if (dbus_error_is_set(&error)) { ErrorF("[config/dbus] couldn't add match: %s (%s)\n", error.name, error.message); goto err_name; } if (!dbus_connection_register_object_path(info->connection, info->busobject, &vtable, info)) { ErrorF("[config/dbus] couldn't register object path\n"); goto err_match; } DebugF("[dbus] registered %s, %s\n", info->busname, info->busobject); dbus_error_free(&error); return; err_match: dbus_bus_remove_match(info->connection, MATCH_RULE, &error); err_name: dbus_bus_release_name(info->connection, info->busname, &error); err_start: dbus_error_free(&error); reset_info(info); } static void disconnect_hook(void *data) { } #if 0 void pre_disconnect_hook(void) { DBusError error; dbus_error_init(&error); dbus_connection_unregister_object_path(connection_data->connection, connection_data->busobject); dbus_bus_remove_match(connection_data->connection, MATCH_RULE, &error); dbus_bus_release_name(connection_data->connection, connection_data->busname, &error); dbus_error_free(&error); } #endif static struct connection_info connection_data; static struct config_dbus_core_hook core_hook = { .connect = connect_hook, .disconnect = disconnect_hook, .data = &connection_data, }; int config_dbus_init(void) { snprintf(connection_data.busname, sizeof(connection_data.busname), "org.x.config.display%d", atoi(display)); snprintf(connection_data.busobject, sizeof(connection_data.busobject), "/org/x/config/%d", atoi(display)); return config_dbus_core_add_hook(&core_hook); } void config_dbus_fini(void) { config_dbus_core_remove_hook(&core_hook); connection_data.busname[0] = '\0'; connection_data.busobject[0] = '\0'; }