Permalink
fixes a bug in cac8479 where crow devices were mapped to the virtual midi device type (which has no corresponding device file to watch for) because the device pattern watch list did not match the order of the `device_t` enum values.
| /* | |
| * device_monitor.c | |
| */ | |
| #include <assert.h> | |
| #include <dirent.h> | |
| #include <errno.h> | |
| #include <fnmatch.h> | |
| #include <libudev.h> | |
| #include <locale.h> | |
| #include <poll.h> | |
| #include <pthread.h> | |
| #include <stdbool.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include "device.h" | |
| #include "device_crow.h" | |
| #include "device_hid.h" | |
| #include "device_list.h" | |
| #include "device_monome.h" | |
| #include "events.h" | |
| #define SUB_NAME_SIZE 32 | |
| #define NODE_NAME_SIZE 128 | |
| #define WATCH_TIMEOUT_MS 100 | |
| struct watch { | |
| // subsystem name to use as a filter on udev_monitor | |
| const char sub_name[NODE_NAME_SIZE]; | |
| // glob pattern for checking the device node | |
| const char node_pattern[NODE_NAME_SIZE]; | |
| // udev monitor | |
| struct udev_monitor *mon; | |
| }; | |
| //------------------------- | |
| //----- static variables | |
| // watchers | |
| // FIXME: these names / paths are really arbitrary. | |
| static struct watch w[DEV_TYPE_COUNT_PHYSICAL] = { | |
| {.sub_name = "tty", .node_pattern = "/dev/ttyUSB*"}, | |
| {.sub_name = "input", .node_pattern = "/dev/input/event*"}, | |
| {.sub_name = "sound", .node_pattern = "/dev/snd/midiC*D*"}, | |
| {.sub_name = "crow", .node_pattern = "/dev/ttyACM*"}}; | |
| // file descriptors to watch/poll | |
| struct pollfd pfds[DEV_TYPE_COUNT_PHYSICAL]; | |
| // thread for polling all the watched file descriptors | |
| pthread_t watch_tid; | |
| //-------------------------------- | |
| //--- static function declarations | |
| static void *watch_loop(void *data); | |
| static void handle_device(struct udev_device *dev); | |
| static device_t check_dev_type(struct udev_device *dev); | |
| static const char *get_alsa_midi_node(struct udev_device *dev); | |
| static const char *get_device_name(struct udev_device *dev); | |
| //-------------------------------- | |
| //---- extern function definitions | |
| void dev_monitor_init(void) { | |
| struct udev *udev = NULL; | |
| pthread_attr_t attr; | |
| int s; | |
| udev = udev_new(); | |
| assert(udev); | |
| for (int i = 0; i < DEV_TYPE_COUNT_PHYSICAL; i++) { | |
| w[i].mon = udev_monitor_new_from_netlink(udev, "udev"); | |
| if (w[i].mon == NULL) { | |
| fprintf(stderr, "failed to start udev_monitor for subsystem %s, pattern %s\n", w[i].sub_name, | |
| w[i].node_pattern); | |
| continue; | |
| } | |
| if (udev_monitor_filter_add_match_subsystem_devtype(w[i].mon, w[i].sub_name, NULL) < 0) { | |
| fprintf(stderr, "failed to add udev monitor filter for subsystem %s, pattern %s\n", w[i].sub_name, | |
| w[i].node_pattern); | |
| continue; | |
| } | |
| if (udev_monitor_enable_receiving(w[i].mon) < 0) { | |
| fprintf(stderr, "failed to enable monitor receiving for for subsystem %s, pattern %s\n", w[i].sub_name, | |
| w[i].node_pattern); | |
| continue; | |
| } | |
| pfds[i].fd = udev_monitor_get_fd(w[i].mon); | |
| pfds[i].events = POLLIN; | |
| } // end dev type loop | |
| s = pthread_attr_init(&attr); | |
| if (s) { | |
| fprintf(stderr, "error initializing thread attributes\n"); | |
| } | |
| s = pthread_create(&watch_tid, &attr, watch_loop, NULL); | |
| if (s) { | |
| fprintf(stderr, "error creating thread\n"); | |
| } | |
| pthread_attr_destroy(&attr); | |
| } | |
| void dev_monitor_deinit(void) { | |
| pthread_cancel(watch_tid); | |
| for (int i = 0; i < DEV_TYPE_COUNT_PHYSICAL; i++) { | |
| free(w[i].mon); | |
| } | |
| } | |
| int dev_monitor_scan(void) { | |
| struct udev *udev; | |
| struct udev_device *dev; | |
| udev = udev_new(); | |
| if (udev == NULL) { | |
| fprintf(stderr, "device_monitor_scan(): failed to create udev\n"); | |
| return 1; | |
| } | |
| for (int i = 0; i < DEV_TYPE_COUNT_PHYSICAL; i++) { | |
| struct udev_enumerate *ue; | |
| struct udev_list_entry *devices, *dev_list_entry; | |
| ue = udev_enumerate_new(udev); | |
| udev_enumerate_add_match_subsystem(ue, w[i].sub_name); | |
| udev_enumerate_scan_devices(ue); | |
| devices = udev_enumerate_get_list_entry(ue); | |
| udev_list_entry_foreach(dev_list_entry, devices) { | |
| const char *path; | |
| path = udev_list_entry_get_name(dev_list_entry); | |
| dev = udev_device_new_from_syspath(udev, path); | |
| if (dev != NULL) { | |
| if (udev_device_get_parent_with_subsystem_devtype(dev, "usb", NULL)) { | |
| handle_device(dev); | |
| } | |
| udev_device_unref(dev); | |
| } | |
| } | |
| udev_enumerate_unref(ue); | |
| } | |
| return 0; | |
| } | |
| //------------------------------- | |
| //--- static function definitions | |
| void *watch_loop(void *p) { | |
| (void)p; | |
| struct udev_device *dev; | |
| while (1) { | |
| if (poll(pfds, DEV_TYPE_COUNT_PHYSICAL, WATCH_TIMEOUT_MS) < 0) { | |
| switch (errno) { | |
| case EINVAL: | |
| perror("error in poll()"); | |
| exit(1); | |
| case EINTR: | |
| case EAGAIN: | |
| continue; | |
| } | |
| } | |
| // see which monitor has data | |
| for (int i = 0; i < DEV_TYPE_COUNT_PHYSICAL; i++) { | |
| if (pfds[i].revents & POLLIN) { | |
| dev = udev_monitor_receive_device(w[i].mon); | |
| if (dev) { | |
| handle_device(dev); | |
| udev_device_unref(dev); | |
| } else { | |
| fprintf(stderr, "no device data from receive_device(). this is an error!\n"); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void handle_device(struct udev_device *dev) { | |
| const char *action = udev_device_get_action(dev); | |
| const char *node = udev_device_get_devnode(dev); | |
| const char *subsys = udev_device_get_subsystem(dev); | |
| if (action == NULL) { | |
| // scan | |
| if (node != NULL) { | |
| device_t t = check_dev_type(dev); | |
| if (t >= 0 && t < DEV_TYPE_COUNT_PHYSICAL) { | |
| dev_list_add(t, node, get_device_name(dev)); | |
| } | |
| } | |
| } else { | |
| // monitor | |
| if (strcmp(subsys, "sound") == 0) { | |
| // try to act according to | |
| // https://github.com/systemd/systemd/blob/master/rules/78-sound-card.rules | |
| if (strcmp(action, "change") == 0) { | |
| const char *alsa_node = get_alsa_midi_node(dev); | |
| if (alsa_node != NULL) { | |
| dev_list_add(DEV_TYPE_MIDI, alsa_node, get_device_name(dev)); | |
| } | |
| } else if (strcmp(action, "remove") == 0) { | |
| if (node != NULL) { | |
| dev_list_remove(DEV_TYPE_MIDI, node); | |
| } | |
| } | |
| } else { | |
| device_t t = check_dev_type(dev); | |
| if (t >= 0 && t < DEV_TYPE_COUNT_PHYSICAL) { | |
| if (strcmp(action, "add") == 0) { | |
| dev_list_add(t, node, get_device_name(dev)); | |
| } else if (strcmp(action, "remove") == 0) { | |
| dev_list_remove(t, node); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| device_t check_dev_type(struct udev_device *dev) { | |
| device_t t = DEV_TYPE_INVALID; | |
| const char *node = udev_device_get_devnode(dev); | |
| if (node) { | |
| // for now, just get USB devices. | |
| // eventually we might want to use this same system for GPIO, &c... | |
| if (udev_device_get_parent_with_subsystem_devtype(dev, "usb", NULL)) { | |
| for (int i = 0; i < DEV_TYPE_COUNT_PHYSICAL; i++) { | |
| const char *node_pattern = w[i].node_pattern; | |
| if (node_pattern[0] && fnmatch(node_pattern, node, 0) == 0) { | |
| t = i; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return t; | |
| } | |
| // try to get midi device node from udev_device | |
| const char *get_alsa_midi_node(struct udev_device *dev) { | |
| const char *subsys; | |
| const char *syspath; | |
| DIR *sysdir; | |
| struct dirent *sysdir_ent; | |
| int alsa_card, alsa_dev; | |
| char *result = NULL; | |
| subsys = udev_device_get_subsystem(dev); | |
| if (strcmp(subsys, "sound") == 0) { | |
| syspath = udev_device_get_syspath(dev); | |
| sysdir = opendir(syspath); | |
| while ((sysdir_ent = readdir(sysdir)) != NULL) { | |
| if (sscanf(sysdir_ent->d_name, "midiC%uD%u", &alsa_card, &alsa_dev) == 2) { | |
| if (asprintf(&result, "/dev/snd/%s", sysdir_ent->d_name) < 0) { | |
| fprintf(stderr, "failed to create alsa device path for %s\n", sysdir_ent->d_name); | |
| return NULL; | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| // try to get product name from udev_device or its parents | |
| const char *get_device_name(struct udev_device *dev) { | |
| char *current_name = NULL; | |
| struct udev_device *current_dev = dev; | |
| while (current_name == NULL) { | |
| current_name = (char *)udev_device_get_sysattr_value(current_dev, "product"); | |
| current_dev = udev_device_get_parent(current_dev); | |
| if (current_dev == NULL) { | |
| break; | |
| } | |
| } | |
| return strdup(current_name); | |
| } |