Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
with
345 additions
and 0 deletions.
| @@ -0,0 +1,340 @@ | ||
| /* | ||
| * device_monitor.c | ||
| */ | ||
|
|
||
| #include <assert.h> | ||
| #include <errno.h> | ||
| #include <libudev.h> | ||
| #include <locale.h> | ||
| #include <poll.h> | ||
| #include <pthread.h> | ||
| #include <fnmatch.h> | ||
| #include <stdbool.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #include <dirent.h> | ||
|
|
||
| #include "device.h" | ||
| #include "device_list.h" | ||
| #include "device_hid.h" | ||
| #include "device_monome.h" | ||
| #include "device_crow.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] = { | ||
| { | ||
| .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]; | ||
| // 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; 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; 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; 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, 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; 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) { | ||
| 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) { | ||
| 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); | ||
| const char *subsys = udev_device_get_subsystem(dev); | ||
| //fprintf(stderr, "node: %s\n", node); | ||
| //fprintf(stderr, "subsys: %s\n", subsys); | ||
|
|
||
| const char *device_product = udev_device_get_property_value(dev, "ID_MODEL"); | ||
| fprintf(stderr, "product: %s\n", device_product); | ||
|
|
||
| const char *device_vendor = udev_device_get_property_value(dev, "ID_VENDOR"); | ||
| fprintf(stderr, "vendor: %s\n", device_vendor); | ||
|
|
||
| const char *device_serial = udev_device_get_property_value(dev, "ID_SERIAL_SHORT"); | ||
| fprintf(stderr, "serial: %s\n", device_serial); | ||
|
|
||
|
|
||
| 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)) { | ||
|
|
||
| // if TTY check product info | ||
| if (strcmp(subsys, "tty") == 0) { | ||
| if (strcmp(device_product, "monome") == 0 && strcmp(device_vendor, "monome") == 0) { | ||
| t = DEV_TYPE_MONOME; | ||
| } else if (strcmp(device_product, "crow:_telephone_line") == 0 && strcmp(device_product, "monome___whimsical_raps") == 0){ | ||
| t = DEV_TYPE_CROW; | ||
| } | ||
| } else if (strcmp(subsys, "input") == 0) { // if HID | ||
| t = DEV_TYPE_HID; | ||
| } else if (strcmp(subsys, "sound") == 0) { // if MIDI | ||
| t = DEV_TYPE_MIDI; | ||
| } | ||
| /* | ||
| for (int i = 0; i < DEV_TYPE_COUNT; i++) { | ||
| //if (fnmatch(w[i].node_pattern, node, 0) == 0) { | ||
| if (fnmatch(w[i].sub_name, subsys, 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); | ||
| } |
| @@ -0,0 +1,5 @@ | ||
| cd ~ | ||
| cd /home/we/norns | ||
| sudo cp -f /home/we/fates/install/norns/files/device/device_monitor.c /home/we/norns/matron/src/device/device_monitor.c | ||
| ./waf configure | ||
| ./waf |