summaryrefslogtreecommitdiff
path: root/rcm-server.c
diff options
context:
space:
mode:
Diffstat (limited to 'rcm-server.c')
-rw-r--r--rcm-server.c729
1 files changed, 729 insertions, 0 deletions
diff --git a/rcm-server.c b/rcm-server.c
new file mode 100644
index 0000000..fb5eba9
--- /dev/null
+++ b/rcm-server.c
@@ -0,0 +1,729 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <libudev.h>
+#include <unistd.h>
+#include <string.h>
+#include <systemd/sd-bus.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#include "utils.h"
+
+struct keycode {
+ char *name;
+ char *value;
+};
+
+struct keymap {
+ char *name;
+ uint16_t rows;
+ uint16_t cols;
+ struct list_head list;
+ struct keycode keycodes[];
+};
+
+struct device {
+ char *name;
+ char *path;
+ char *driver_name;
+ char *keymap_name;
+ struct list_head list;
+ struct list_head keymaps;
+};
+
+struct manager {
+ sd_bus *bus;
+ sd_event *event;
+ struct udev *udev;
+ struct udev_monitor *udev_mon;
+ sd_event_source *udev_ev;
+ struct list_head devices;
+ unsigned num_devices;
+};
+
+static int
+method_echostring(sd_bus_message *m, void *userdata, sd_bus_error *error)
+{
+ const char *msg;
+ int r;
+
+ r = sd_bus_message_read(m, "s", &msg);
+ if (r < 0) {
+ fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-r));
+ return sd_bus_error_set_errno(error, r);
+ }
+
+ return sd_bus_reply_method_return(m, "s", msg);
+}
+
+static struct device *
+find_device_by_path(struct manager *mgr, const char *path)
+{
+ struct device *dev;
+
+ list_for_each_entry(dev, &mgr->devices, list)
+ if (!strcmp(dev->path, path))
+ return dev;
+
+ return NULL;
+}
+
+static int
+property_get_driver_name(sd_bus *bus, const char *path, const char *interface,
+ const char *property, sd_bus_message *reply, void *userdata,
+ sd_bus_error *error)
+{
+ struct manager *mgr = userdata;
+ struct device *dev;
+
+ dev = find_device_by_path(mgr, path);
+ if (!dev) {
+ sd_bus_error_set_const(error, "org.gnome.RemoteControlManager.InvalidDevice", "Sorry, invalid device");
+ return -EINVAL;
+ }
+
+ if (!strcmp(property, "DriverName"))
+ return sd_bus_message_append(reply, "s", dev->driver_name);
+ else if (!strcmp(property, "KernelKeymapName"))
+ return sd_bus_message_append(reply, "s", dev->keymap_name);
+
+ sd_bus_error_set_const(error, "org.gnome.RemoteControlManager.InvalidProperty", "Sorry, invalid property");
+ return -EINVAL;
+}
+
+
+static int
+method_listkeymaps(sd_bus_message *m, void *userdata, sd_bus_error *error)
+{
+ _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+ struct manager *mgr = userdata;
+ struct device *dev;
+ struct keymap *keymap;
+ int r;
+
+ dev = find_device_by_path(mgr, sd_bus_message_get_path(m));
+ if (!dev) {
+ sd_bus_error_set_const(error, "org.gnome.RemoteControlManager.InvalidDevice", "Sorry, invalid device");
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ goto out;
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ goto out;
+
+ list_for_each_entry(keymap, &dev->keymaps, list) {
+ printf("Listing keymaps for %s: %s\n", dev->name, keymap->name);
+ r = sd_bus_message_append(reply, "s", keymap->name);
+ if (r < 0)
+ goto out;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ goto out;
+
+ r = sd_bus_send(NULL, reply, NULL);
+ if (r < 0)
+ goto out;
+
+ return 1;
+
+out:
+ return sd_bus_error_set_errno(error, r);
+}
+
+static struct keymap *
+find_keymap_by_name(struct device *dev, const char *name)
+{
+ struct keymap *keymap;
+
+ list_for_each_entry(keymap, &dev->keymaps, list)
+ if (!strcmp(keymap->name, name))
+ return keymap;
+
+ return NULL;
+}
+
+static int
+method_getkeymap(sd_bus_message *m, void *userdata, sd_bus_error *error)
+{
+ _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+ struct manager *mgr = userdata;
+ struct device *dev;
+ struct keymap *keymap;
+ const char *name;
+ int r;
+ unsigned i;
+
+ dev = find_device_by_path(mgr, sd_bus_message_get_path(m));
+ if (!dev) {
+ sd_bus_error_set_const(error, "org.gnome.RemoteControlManager.InvalidDevice", "Sorry, invalid device");
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_read(m, "s", &name);
+ if (r < 0) {
+ fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-r));
+ return r;
+ }
+
+ keymap = find_keymap_by_name(dev, name);
+ if (!keymap) {
+ sd_bus_error_set_const(error, "org.gnome.RemoteControlManager.InvalidKeymap", "Sorry, unknown keymap");
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ goto out;
+
+ r = sd_bus_message_append(reply, "q", keymap->cols);
+ if (r < 0)
+ goto out;
+
+ r = sd_bus_message_append(reply, "q", keymap->rows);
+ if (r < 0)
+ goto out;
+
+ r = sd_bus_message_open_container(reply, 'a', "a{sv}");
+ if (r < 0)
+ goto out;
+
+ for (i = 0; i < (keymap->rows * keymap->cols); i++) {
+ r = sd_bus_message_append(reply, "a{sv}", 2,
+ "name", "s", keymap->keycodes[i].name,
+ "keycode", "s", keymap->keycodes[i].value);
+ if (r < 0)
+ goto out;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ goto out;
+
+ r = sd_bus_send(NULL, reply, NULL);
+ if (r < 0)
+ goto out;
+
+ return 1;
+
+out:
+ return sd_bus_error_set_errno(error, r);
+}
+
+static const sd_bus_vtable device_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_PROPERTY("DriverName", "s", property_get_driver_name, 0, 0),
+ SD_BUS_PROPERTY("KernelKeymapName", "s", property_get_driver_name, 0, 0),
+ SD_BUS_METHOD("EchoString", "s", "s", method_echostring, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("ListKeymaps", NULL, "as", method_listkeymaps, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("GetKeymap", "s", "qqaa{sv}", method_getkeymap, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_VTABLE_END
+};
+
+static int
+my_bus_emit_object_changed(sd_bus *bus, const char *path, bool added)
+{
+ int r;
+ _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
+
+ printf("Sending signal %s for path %s\n",
+ added ? "InterfacesAdded" : "InterfacesRemoved",
+ path);
+
+ r = sd_bus_message_new_signal(bus, &m, "/org/gnome/RemoteControlManager",
+ "org.freedesktop.DBus.ObjectManager",
+ added ? "InterfacesAdded" : "InterfacesRemoved");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_basic(m, 'o', path);
+ if (r < 0)
+ return r;
+
+ if (added) {
+ r = sd_bus_message_open_container(m, 'a', "{sa{sv}}");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "{sa{sv}}", "org.gnome.RemoteControlManager.Device", 0);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "s", "org.gnome.RemoteControlManager.Device");
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, m, NULL);
+ return r;
+}
+
+static void
+remove_device(struct manager *mgr, struct udev_device *udev)
+{
+ const char *name;
+ struct device *device;
+
+ name = udev_device_get_sysname(udev);
+ printf("Asked to remove device %s\n", name);
+
+ list_for_each_entry(device, &mgr->devices, list) {
+ if (strcmp(device->name, name))
+ continue;
+ list_del(&device->list);
+ my_bus_emit_object_changed(mgr->bus, device->path, false);
+ mgr->num_devices--;
+ break;
+ }
+}
+
+static struct keymap *
+keymap_read(int dfd, const char *name)
+{
+ int fd;
+ struct keymap *keymap;
+ unsigned i;
+
+ if (dfd < 0 || !name)
+ return NULL;
+
+ fd = openat(dfd, name, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ keymap = zmalloc(sizeof(*keymap) + (8 * 3) * sizeof(struct keycode));
+ if (!keymap) {
+ close(fd);
+ return NULL;
+ }
+
+ keymap->name = strdup(name);
+ if (!keymap->name) {
+ close(fd);
+ free(keymap);
+ return NULL;
+ }
+
+ keymap->cols = 3;
+ keymap->rows = 8;
+
+ for (i = 0; i < (keymap->cols * keymap->rows); i++) {
+ keymap->keycodes[i].name = "key-1-2";
+ keymap->keycodes[i].value = "KEY_COFFEE";
+ }
+ /* FIXME: Actually read the keymap :) */
+
+ close(fd);
+ return keymap;
+}
+
+static int
+keymaps_load_dir(struct device *device, const char *path)
+{
+ DIR *dir;
+ int dfd;
+ struct dirent *dent;
+ struct keymap *keymap;
+
+ dir = opendir(path);
+ if (!dir)
+ return -1;
+
+ dfd = dirfd(dir);
+ if (dfd < 0) {
+ closedir(dir);
+ return -1;
+ }
+
+ while ((dent = readdir(dir))) {
+ switch (dent->d_type) {
+ case DT_REG:
+ case DT_LNK:
+ case DT_UNKNOWN:
+ keymap = keymap_read(dfd, dent->d_name);
+ if (keymap)
+ list_add(&keymap->list, &device->keymaps);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (closedir(dir) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int
+keymaps_load(struct device *device)
+{
+ char pdpath[strlen("./keymaps/per-device/") + strlen(device->name) + 1];
+
+ sprintf(pdpath, "./keymaps/per-device/%s", device->name);
+
+ printf("Loading per-device keymaps...\n");
+ keymaps_load_dir(device, pdpath);
+ printf("Loading default keymaps...\n");
+ keymaps_load_dir(device, "./keymaps/default");
+
+ return 0;
+}
+
+static void
+add_device(struct manager *mgr, struct udev_device *udev)
+{
+ const char *name;
+ const char *str;
+ char *path;
+ struct device *device;
+
+ name = udev_device_get_sysname(udev);
+ if (asprintf(&path, "/org/gnome/RemoteControlManager/%s", name) < 0) {
+ fprintf(stderr, "asprintf failed: %m\n");
+ return;
+ }
+
+ device = malloc(sizeof(*device));
+ if (!device) {
+ fprintf(stderr, "malloc failed: %m\n");
+ free(path);
+ return;
+ }
+
+ list_init(&device->keymaps);
+ device->path = path;
+ device->name = device->path + strlen("/org/gnome/RemoteControlManager/");
+
+ if (keymaps_load(device) < 0) {
+ fprintf(stderr, "failed to load keymaps: %m\n");
+ free(path);
+ free(device);
+ }
+
+ str = udev_device_get_sysattr_value(udev, "uevent");
+ if (str) {
+ char tmp[strlen(str) + 1];
+ char *token;
+
+ strcpy(tmp, str);
+
+ for (token = strtok(tmp, "\n"); token; token = strtok(NULL, "\n")) {
+ if (!strncmp(token, "DRV_NAME=", strlen("DRV_NAME=")))
+ device->driver_name = strdup(token + strlen("DRV_NAME="));
+ else if (!strncmp(token, "NAME=", strlen("NAME=")))
+ device->keymap_name = strdup(token + strlen("NAME="));
+ else
+ printf("Unused uevent: %s\n", token);
+ }
+ }
+
+ printf("Adding Device Object\n");
+ printf(" Path : %s\n", udev_device_get_syspath(udev));
+ printf(" Node : %s\n", udev_device_get_devnode(udev));
+ printf(" Subsystem : %s\n", udev_device_get_subsystem(udev));
+ printf(" Devtype : %s\n", udev_device_get_devtype(udev));
+ printf(" Action : %s\n", udev_device_get_action(udev));
+ printf(" Protocols : %s\n",
+ udev_device_get_sysattr_value(udev, "protocols"));
+ printf(" Uevent : %s\n",
+ udev_device_get_sysattr_value(udev, "uevent"));
+
+ list_add(&device->list, &mgr->devices);
+ mgr->num_devices++;
+
+ my_bus_emit_object_changed(mgr->bus, path, true);
+}
+
+static int
+udev_read(sd_event_source *s, int fd, uint32_t revents, void *userdata)
+{
+ struct manager *mgr = userdata;
+ struct udev_device *dev;
+
+ if (revents & EPOLLHUP) {
+ fprintf(stderr, "udev connection closed!\n");
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ fprintf(stderr, "unexpected udev event: %" PRIu32 "\n", revents);
+ return 0;
+ }
+
+ while ((dev = udev_monitor_receive_device(mgr->udev_mon))) {
+ printf("Read device: %s\n", udev_device_get_syspath(dev));
+ if (!strcmp(udev_device_get_action(dev), "add"))
+ add_device(mgr, dev);
+ else if (!strcmp(udev_device_get_action(dev), "remove"))
+ remove_device(mgr, dev);
+ udev_device_unref(dev);
+ }
+
+ return 1;
+}
+
+static int
+udev_setup(struct manager *mgr)
+{
+ struct udev_enumerate *enumerate;
+ struct udev_list_entry *devices, *dev_list_entry;
+ struct udev_device *dev;
+
+ mgr->udev = udev_new();
+ if (!mgr->udev)
+ return -ENOMEM;
+
+ mgr->udev_mon = udev_monitor_new_from_netlink(mgr->udev, "udev");
+ udev_monitor_filter_add_match_subsystem_devtype(mgr->udev_mon, "rc", NULL);
+ udev_monitor_enable_receiving(mgr->udev_mon);
+
+ enumerate = udev_enumerate_new(mgr->udev);
+ udev_enumerate_add_match_subsystem(enumerate, "rc");
+ udev_enumerate_scan_devices(enumerate);
+ devices = udev_enumerate_get_list_entry(enumerate);
+
+ udev_list_entry_foreach(dev_list_entry, devices) {
+ const char *path;
+
+ path = udev_list_entry_get_name(dev_list_entry);
+ if (!path) {
+ printf("Failed to get udev name\n");
+ continue;
+ }
+
+ dev = udev_device_new_from_syspath(mgr->udev, path);
+ if (!dev) {
+ printf("Failed to create udev device\n");
+ continue;
+ }
+
+ add_device(mgr, dev);
+
+ udev_device_unref(dev);
+ }
+ udev_enumerate_unref(enumerate);
+
+ return sd_event_add_io(mgr->event, &mgr->udev_ev,
+ udev_monitor_get_fd(mgr->udev_mon),
+ EPOLLIN, udev_read, mgr);
+}
+
+static int
+enumerator(sd_bus *bus, const char *path, void *userdata, char ***retnodes, sd_bus_error *error)
+{
+ _cleanup_strv_free_ char **nodes = NULL;
+ struct manager *mgr = userdata;
+ struct device *device;
+ int i = 0;
+
+ if (!path)
+ return 0;
+
+ nodes = zmalloc((mgr->num_devices + 1) * sizeof(char *));
+ if (!nodes)
+ return -ENOMEM;
+
+ list_for_each_entry(device, &mgr->devices, list) {
+ nodes[i] = strdup(device->path);
+ if (!nodes[i])
+ return -ENOMEM;
+ i++;
+ }
+
+ nodes[i++] = NULL;
+ *retnodes = nodes;
+ nodes = NULL;
+
+ return 1;
+}
+
+static void
+free_manager(struct manager *mgr) {
+ if (!mgr)
+ return;
+
+ sd_event_source_unref(mgr->udev_ev);
+ sd_bus_detach_event(mgr->bus);
+ sd_event_unref(mgr->event);
+ udev_monitor_unref(mgr->udev_mon);
+ udev_unref(mgr->udev);
+
+ while (!list_empty(&mgr->devices)) {
+ struct device *dev = list_first_entry(&mgr->devices, typeof(*dev), list);
+ list_del(&dev->list);
+ free(dev->path);
+ free(dev->driver_name);
+ free(dev->keymap_name);
+
+ while (!list_empty(&dev->keymaps)) {
+ struct keymap *keymap = list_first_entry(&dev->keymaps, typeof(*keymap), list);
+ list_del(&keymap->list);
+ free(keymap->name);
+ free(keymap);
+ }
+
+ free(dev);
+ }
+ free(mgr);
+}
+
+static int
+block_signals(void)
+{
+ sigset_t sigset;
+ int r;
+
+ r = sigemptyset(&sigset);
+ if (r < 0)
+ return r;
+
+ r = sigaddset(&sigset, SIGINT);
+ if (r < 0)
+ return r;
+
+ r = sigaddset(&sigset, SIGTERM);
+ if (r < 0)
+ return r;
+
+ return sigprocmask(SIG_BLOCK, &sigset, NULL);
+}
+
+int
+main(int argc, char **argv)
+{
+ int r;
+ struct manager *mgr;
+ _cleanup_bus_close_unref_ sd_bus *bus = NULL;
+ _cleanup_bus_slot_unref_ struct sd_bus_slot *vtable_slot = NULL;
+ _cleanup_bus_slot_unref_ struct sd_bus_slot *enumerator_slot = NULL;
+ _cleanup_bus_slot_unref_ struct sd_bus_slot *objm_slot = NULL;
+ _cleanup_event_source_unref_ sd_event_source *sigint_ev = NULL;
+ _cleanup_event_source_unref_ sd_event_source *sigterm_ev = NULL;
+
+ mgr = zmalloc(sizeof(*mgr));
+ if (!mgr) {
+ fprintf(stderr, "Failed to allocate memory: %m\n");
+ goto finish;
+ }
+ list_init(&mgr->devices);
+
+ r = sd_bus_open_user(&bus);
+ if (r < 0) {
+ fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
+ goto finish;
+ }
+ mgr->bus = bus;
+
+ r = sd_bus_request_name(mgr->bus, "org.gnome.RemoteControlManager", 0);
+ if (r < 0) {
+ fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_bus_add_fallback_vtable(mgr->bus, &vtable_slot, "/org/gnome/RemoteControlManager",
+ "org.gnome.RemoteControlManager.Device",
+ device_vtable, NULL, mgr);
+ if (r < 0) {
+ fprintf(stderr, "Failed to add vtable: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_bus_add_node_enumerator(mgr->bus, &enumerator_slot, "/org/gnome/RemoteControlManager", enumerator, mgr);
+ if (r < 0) {
+ fprintf(stderr, "Failed to create node enumerator: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_bus_add_object_manager(mgr->bus, &objm_slot, "/org/gnome/RemoteControlManager");
+ if (r < 0) {
+ fprintf(stderr, "Failed to create object manager: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_event_default(&mgr->event);
+ if (r < 0) {
+ fprintf(stderr, "Failed to create sd_event: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_bus_attach_event(mgr->bus, mgr->event, 0);
+ if (r < 0) {
+ fprintf(stderr, "Failed to attach bus to event loop: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ r = udev_setup(mgr);
+ if (r < 0) {
+ fprintf(stderr, "Failed to setup udev monitoring: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ r = block_signals();
+ if (r < 0) {
+ fprintf(stderr, "Failed to block signals: %m\n");
+ goto finish;
+ }
+
+ sd_event_add_signal(mgr->event, &sigint_ev, SIGINT, NULL, NULL);
+ sd_event_add_signal(mgr->event, &sigterm_ev, SIGTERM, NULL, NULL);
+
+ r = sd_event_loop(mgr->event);
+ if (r < 0) {
+ fprintf(stderr, "Event loop failed: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ sd_event_get_exit_code(mgr->event, &r);
+
+finish:
+ free_manager(mgr);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+#if 0
+static gboolean
+on_irrx(RCIRDevice *object,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GVariantBuilder builder;
+ GVariant *ret;
+
+ printf("In on_irrx\n");
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(&builder, "{sv}", "name",
+ g_variant_new_string("apan"));
+ g_variant_builder_add(&builder, "{sv}", "foo",
+ g_variant_new_string("bar"));
+
+ ret = g_variant_builder_end (&builder);
+
+ rcirdevice_complete_get_irrxparameters(object,
+ invocation,
+ ret);
+
+ return true;
+}
+
+#endif
+