diff options
Diffstat (limited to 'rcm-server-main.c')
-rw-r--r-- | rcm-server-main.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/rcm-server-main.c b/rcm-server-main.c new file mode 100644 index 0000000..02387a1 --- /dev/null +++ b/rcm-server-main.c @@ -0,0 +1,564 @@ +#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 "utils.h" +#include "rcm-server-main.h" +#include "rcm-server-keymap.h" + +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 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}", 3, + "protocol", "s", keymap->layout[i]->protocol, + "scancode", "t", keymap->layout[i]->scancode, + "keycode", "s", keymap->layout[i]->keycode); + 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 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; +} + |