From 561f0371e40ccc50e7931ff8ca8b113b41aec587 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 9 Nov 2014 13:27:55 +0100 Subject: [PATCH] More... --- Makefile | 2 +- nocturn.c | 370 +++++++++++++++++++++++++++++++++++ pulse-ucw.c | 10 +- ursaryd.h | 9 + ut.c | 543 +++++++++++++++++----------------------------------- 5 files changed, 560 insertions(+), 374 deletions(-) create mode 100644 nocturn.c create mode 100644 ursaryd.h diff --git a/Makefile b/Makefile index aaf7d56..40e97df 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) all: ut -ut: ut.o pulse-ucw.o +ut: ut.o nocturn.o pulse-ucw.o clean: rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name .depend -or -name .#*` diff --git a/nocturn.c b/nocturn.c new file mode 100644 index 0000000..8ed7b18 --- /dev/null +++ b/nocturn.c @@ -0,0 +1,370 @@ +/* + * Interface to Novation Nocturn + * + * (c) 2014 Martin Mares + * + * Protocol reverse-engineered by De Wet van Niekerk , + * see https://github.com/dewert/nocturn-linux-midi for inspiration. + */ + +#define LOCAL_DEBUG + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "ursaryd.h" + +static libusb_context *usb_ctx; +static libusb_device_handle *usb_dev; + +static struct main_file **usb_fds; + +static int usb_fd_ready(struct main_file *f UNUSED) +{ + DBG("USB: Handling events (ready on fd %d)", f->fd); + struct timeval tv = { 0, 0 }; + int comp = 0; + int err = libusb_handle_events_timeout_completed(usb_ctx, &tv, &comp); + if (err < 0) + msg(L_ERROR, "libusb_handle_events: error %d", err); + return HOOK_IDLE; +} + +static void usb_added_fd(int fd, short events, void *user_data UNUSED) +{ + if (fd >= (int) GARY_SIZE(usb_fds)) + GARY_RESIZE(usb_fds, fd + 1); + + struct main_file *f = usb_fds[fd]; + if (!f) + { + f = xmalloc_zero(sizeof(*f)); + usb_fds[fd] = f; + } + else if (file_is_active(f)) + { + DBG("USB: Releasing fd %d", fd); + file_del(f); + } + + DBG("USB: Adding fd %d with event mask %u", fd, events); + f->fd = fd; + f->read_handler = (events & POLLIN) ? usb_fd_ready : NULL; + f->write_handler = (events & POLLOUT) ? usb_fd_ready : NULL; + file_add(f); +} + +static void usb_removed_fd(int fd, void *user_data UNUSED) +{ + DBG("USB: Releasing fd %d", fd); + ASSERT(fd < (int) GARY_SIZE(usb_fds)); + struct main_file *f = usb_fds[fd]; + ASSERT(f); + ASSERT(file_is_active(f)); + file_del(f); +} + +static const char noct_magic[4][9] = { + { 3, 0xb0, 0x00, 0x00 }, + { 8, 0x28, 0x00, 0x2b, 0x4a, 0x2c, 0x00, 0x2e, 0x35 }, + { 6, 0x2a, 0x02, 0x2c, 0x72, 0x2e, 0x30 }, + { 2, 0x7f, 0x00 }, +}; + +static void noct_read_done(struct libusb_transfer *xfer) +{ + byte *pkt = xfer->buffer; + int len = xfer->actual_length; + DBG("USB: Read done: status %d, length %d", xfer->status, len); + + if (xfer->status != LIBUSB_TRANSFER_COMPLETED) + { + msg(L_ERROR, "USB read failed with status %d, not submitting again", xfer->status); + return; + } + +#ifdef LOCAL_DEBUG + char buf[256]; + mem_to_hex(buf, pkt, len, ' '); + DBG("USB: Read <%s>", buf); +#endif + + int i = 0; + while (i < len) + { + if (i + 3 > len) + { + msg(L_ERROR, "Unknown USB packet: length %d not divisible by 3", len); + break; + } + if (pkt[i] != 0xb0) + { + msg(L_ERROR, "Unknown USB packet: expected 0xb0 at position %d", i); + break; + } + int cmd = pkt[i+1]; + int arg = pkt[i+2]; + i += 3; + switch (cmd) + { + case 0x30: + // Unknown packet sent during init + continue; + case 0x40 ... 0x47: + if (arg < 0x80) + { + int r = cmd - 0x40; + int delta = (arg < 0x40 ? arg : arg - 0x80); + DBG("Noct: Rotary %d = %d", r, delta); + continue; + } + break; + case 0x48: + if (arg < 0x80) + { + DBG("Noct: Slider value = %d", arg); + continue; + } + break; + case 0x49: + // Unknown packet, maybe least significant bit of slider + continue; + case 0x4a: + if (arg < 0x80) + { + int delta = (arg < 0x40 ? arg : arg - 0x80); + DBG("Noct: Center = %d", delta); + continue; + } + break; + case 0x52: + if (arg == 0x00 || arg == 0x7f) + { + int state = !!arg; + DBG("Noct: Center touch = %d", state); + continue; + } + break; + case 0x53: + if (arg == 0x00 || arg == 0x7f) + { + int state = !!arg; + DBG("Noct: Slider touch = %d", state); + continue; + } + break; + case 0x60 ... 0x67: + if (arg == 0x00 || arg == 0x7f) + { + int r = cmd - 0x60; + int state = !!arg; + DBG("Noct: Rotary %d touch = %d", r, state); + continue; + } + break; + case 0x70 ... 0x7f: + if (arg == 0x00 || arg == 0x7f) + { + int b = cmd - 0x70; + int state = !!arg; + DBG("Noct: Button %d = %d", b, state); + continue; + } + break; + } + msg(L_ERROR, "Unknown USB packet: unrecognized cmd=%02x arg=%02x", cmd, arg); + } + + int err; + if ((err = libusb_submit_transfer(xfer)) < 0) + die("Cannot submit transfer: error %d", err); +} + +static void noct_read_init(void) +{ + DBG("Noct: Read init"); + + struct libusb_transfer *xfer = libusb_alloc_transfer(0); + libusb_fill_interrupt_transfer(xfer, usb_dev, 0x81, xmalloc(8), 8, noct_read_done, NULL, 0); + + int err; + if ((err = libusb_submit_transfer(xfer)) < 0) + die("Cannot submit transfer: error %d", err); +} + +static byte noct_button_state[16]; +static byte noct_ring_mode[8]; // 0=from-min, 1=from-max, 2=from-mid-right, 3=from-mid-both, 4=single-on, 5=single-off +static byte noct_ring_val[9]; + +static uns noct_dirty_button; +static uns noct_dirty_ring_mode; +static uns noct_dirty_ring_val; + +static struct libusb_transfer *noct_write_xfer; +static uns noct_write_pending; +static void noct_sched_write(void); + +static void noct_write_done(struct libusb_transfer *xfer) +{ + int len = xfer->actual_length; + DBG("USB: Write done: status %d, length %d", xfer->status, len); + + if (xfer->status != LIBUSB_TRANSFER_COMPLETED) + { + msg(L_ERROR, "USB write failed with status %d", xfer->status); + return; + } + + noct_write_pending = 0; + noct_sched_write(); +} + +static void noct_do_write(uns cmd, uns arg) +{ + DBG("USB: Submitting write %02x %02x", cmd, arg); + ASSERT(!noct_write_pending); + noct_write_pending = 1; + + struct libusb_transfer *xfer = noct_write_xfer; + byte *pkt = xfer->buffer; + pkt[0] = cmd; + pkt[1] = arg; + xfer->length = 2; + + int err; + if ((err = libusb_submit_transfer(xfer)) < 0) + die("Cannot submit transfer: error %d", err); +} + +static void noct_sched_write(void) +{ + if (noct_write_pending) + return; + + if (noct_dirty_button) + { + int i = bit_ffs(noct_dirty_button); + noct_dirty_button ^= 1U << i; + noct_do_write(0x70 + i, noct_button_state[i]); + } + else if (noct_dirty_ring_mode) + { + int i = bit_ffs(noct_dirty_ring_mode); + noct_dirty_ring_mode ^= 1U << i; + noct_do_write(0x48 + i, noct_ring_mode[i] << 4); + } + else if (noct_dirty_ring_val) + { + int i = bit_ffs(noct_dirty_ring_val); + noct_dirty_ring_val ^= 1U << i; + if (i == 8) + noct_do_write(0x50 + i, noct_ring_val[i]); + else + noct_do_write(0x40 + i, noct_ring_val[i]); + } +} + +static void noct_write_init(void) +{ + DBG("Noct: Write init"); + + noct_write_xfer = libusb_alloc_transfer(0); + libusb_fill_interrupt_transfer(noct_write_xfer, usb_dev, 0x02, xmalloc(8), 0, noct_write_done, NULL, 1000); + +#if 0 // FIXME + noct_button_state[2] = 1; + noct_ring_mode[0] = 4; + noct_ring_val[0] = 0x40; +#endif + + noct_dirty_button = 0xffff; + noct_dirty_ring_mode = 0xff; + noct_dirty_ring_val = 0x1ff; + noct_sched_write(); +} + +void noct_init(void) +{ + int err; + + if ((err = libusb_init(&usb_ctx)) < 0) + die("libusb_init failed: error %d", err); + libusb_set_debug(usb_ctx, 3); + + libusb_device **dev_list; + libusb_device *found_dev = NULL; + ssize_t len = libusb_get_device_list(usb_ctx, &dev_list); + for (ssize_t i=0; i < len; i++) + { + libusb_device *dev = dev_list[i]; + struct libusb_device_descriptor desc; + if (libusb_get_device_descriptor(dev, &desc) >= 0 && + desc.idVendor == 0x1235 && + desc.idProduct == 0x000a) + { + msg(L_DEBUG, "Found device: bus %d, addr %d", libusb_get_bus_number(dev), libusb_get_device_address(dev)); + if (found_dev) + die("Multiple Nocturn devices found. Please fix me to handle it."); + found_dev = libusb_ref_device(dev); + } + } + libusb_free_device_list(dev_list, 1); + + if (!found_dev) + die("No Nocturn device found"); + + msg(L_DEBUG, "Initializing device"); + + if ((err = libusb_open(found_dev, &usb_dev)) < 0) + die("libusb_open failed: error %d", err); + + // There exist configurations 1 (high brightness) and 2 (power-save) + if ((err = libusb_set_configuration(usb_dev, 1)) < 0) + die("libusb_set_configuration: error %d", err); + + if ((err = libusb_claim_interface(usb_dev, 0)) < 0) + die("libusb_claim_interface: error %d", err); + + for (int i=0; i<4; i++) + { + int done; + if ((err = libusb_interrupt_transfer(usb_dev, 0x02, (byte *) noct_magic[i] + 1, noct_magic[i][0], &done, 5000)) < 0) + die("Cannot send init packets: error %d", err); + if (done != noct_magic[i][0]) + die("Partial send of init packet: %d < %d", done, noct_magic[i][0]); + } + +#if 0 + byte xxx[] = { 0x7f, 0x01 }; + int done; + libusb_interrupt_transfer(usb_dev, 0x02, xxx, 2, &done, 5000); +#endif + + DBG("USB: Connecting libusb to mainloop"); + + if (!libusb_pollfds_handle_timeouts(usb_ctx)) + die("Unsupported version of libusb, please fix me"); + + GARY_INIT_ZERO(usb_fds, 0); + libusb_set_pollfd_notifiers(usb_ctx, usb_added_fd, usb_removed_fd, NULL); + + const struct libusb_pollfd **fds = libusb_get_pollfds(usb_ctx); + ASSERT(fds); + for (int i=0; fds[i]; i++) + usb_added_fd(fds[i]->fd, fds[i]->events, NULL); + free(fds); + + noct_read_init(); + noct_write_init(); +} diff --git a/pulse-ucw.c b/pulse-ucw.c index b0581cb..b94ec8f 100644 --- a/pulse-ucw.c +++ b/pulse-ucw.c @@ -77,7 +77,7 @@ static void pmain_quit(pa_mainloop_api *a, int retval); static struct main_hook pmain_gc_hook; -static void pmain_trigger_gc(void); +static void pmain_schedule_gc(void); struct pa_mainloop_api pmain_api = { .io_new = pmain_io_new, @@ -189,7 +189,7 @@ static void pmain_io_free(pa_io_event *e) DBG("Pulse: Deleting IO %p for fd %d", e, e->io->f.fd); pmain_io_enable(e, 0); clist_add_tail(&pmain_io_gc_list, &e->gc_n); - pmain_trigger_gc(); + pmain_schedule_gc(); } static void pmain_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb) @@ -241,7 +241,7 @@ static void pmain_time_free(pa_time_event *e) DBG("Pulse: Timer %p deleted", e); timer_del(&e->t); clist_add_tail(&pmain_time_gc_list, &e->n); - pmain_trigger_gc(); + pmain_schedule_gc(); } static void pmain_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb) @@ -284,7 +284,7 @@ static void pmain_defer_free(pa_defer_event *e) DBG("Pulse: Deferred event %p deleted", e); hook_del(&e->h); clist_add_tail(&pmain_defer_gc_list, &e->n); - pmain_trigger_gc(); + pmain_schedule_gc(); } static void pmain_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb) @@ -343,7 +343,7 @@ static int pmain_gc_handler(struct main_hook *h) return HOOK_RETRY; } -static void pmain_trigger_gc(void) +static void pmain_schedule_gc(void) { hook_add(&pmain_gc_hook); } diff --git a/ursaryd.h b/ursaryd.h new file mode 100644 index 0000000..39aae3d --- /dev/null +++ b/ursaryd.h @@ -0,0 +1,9 @@ +/* nocturn.c */ + +void noct_init(void); + +/* pulse-ucw.c */ + +extern struct pa_mainloop_api pmain_api; + +void pmain_init(void); diff --git a/ut.c b/ut.c index 8cb2a92..5e3fab4 100644 --- a/ut.c +++ b/ut.c @@ -1,382 +1,27 @@ #define LOCAL_DEBUG #include -#include #include -#include #include -#include +#include #include #include #include -#include -#include -#include #include #include "ursaryd.h" -/* - * Interface to Novation Nocturn - * - * Protocol reverse-engineered by De Wet van Niekerk , - * see https://github.com/dewert/nocturn-linux-midi for inspiration. - */ - -static libusb_context *usb_ctx; -static libusb_device_handle *usb_dev; - -static struct main_file **usb_fds; - -static int usb_fd_ready(struct main_file *f UNUSED) -{ - DBG("USB: Handling events (ready on fd %d)", f->fd); - struct timeval tv = { 0, 0 }; - int comp = 0; - int err = libusb_handle_events_timeout_completed(usb_ctx, &tv, &comp); - if (err < 0) - msg(L_ERROR, "libusb_handle_events: error %d", err); - return HOOK_IDLE; -} - -static void usb_added_fd(int fd, short events, void *user_data UNUSED) -{ - if (fd >= (int) GARY_SIZE(usb_fds)) - GARY_RESIZE(usb_fds, fd + 1); - - struct main_file *f = usb_fds[fd]; - if (!f) - { - f = xmalloc_zero(sizeof(*f)); - usb_fds[fd] = f; - } - else if (file_is_active(f)) - { - DBG("USB: Releasing fd %d", fd); - file_del(f); - } - - DBG("USB: Adding fd %d with event mask %u", fd, events); - f->fd = fd; - f->read_handler = (events & POLLIN) ? usb_fd_ready : NULL; - f->write_handler = (events & POLLOUT) ? usb_fd_ready : NULL; - file_add(f); -} - -static void usb_removed_fd(int fd, void *user_data UNUSED) -{ - DBG("USB: Releasing fd %d", fd); - ASSERT(fd < (int) GARY_SIZE(usb_fds)); - struct main_file *f = usb_fds[fd]; - ASSERT(f); - ASSERT(file_is_active(f)); - file_del(f); -} - -static const char noct_init[4][9] = { - { 3, 0xb0, 0x00, 0x00 }, - { 8, 0x28, 0x00, 0x2b, 0x4a, 0x2c, 0x00, 0x2e, 0x35 }, - { 6, 0x2a, 0x02, 0x2c, 0x72, 0x2e, 0x30 }, - { 2, 0x7f, 0x00 }, -}; - -static void noct_read_done(struct libusb_transfer *xfer) -{ - byte *pkt = xfer->buffer; - int len = xfer->actual_length; - DBG("USB: Read done: status %d, length %d", xfer->status, len); - - if (xfer->status != LIBUSB_TRANSFER_COMPLETED) - { - msg(L_ERROR, "USB read failed with status %d, not submitting again", xfer->status); - return; - } - -#ifdef LOCAL_DEBUG - char buf[256]; - mem_to_hex(buf, pkt, len, ' '); - DBG("USB: Read <%s>", buf); -#endif - - int i = 0; - while (i < len) - { - if (i + 3 > len) - { - msg(L_ERROR, "Unknown USB packet: length %d not divisible by 3", len); - break; - } - if (pkt[i] != 0xb0) - { - msg(L_ERROR, "Unknown USB packet: expected 0xb0 at position %d", i); - break; - } - int cmd = pkt[i+1]; - int arg = pkt[i+2]; - i += 3; - switch (cmd) - { - case 0x30: - // Unknown packet sent during init - continue; - case 0x40 ... 0x47: - if (arg < 0x80) - { - int r = cmd - 0x40; - int delta = (arg < 0x40 ? arg : arg - 0x80); - DBG("Noct: Rotary %d = %d", r, delta); - continue; - } - break; - case 0x48: - if (arg < 0x80) - { - DBG("Noct: Slider value = %d", arg); - continue; - } - break; - case 0x49: - // Unknown packet, maybe least significant bit of slider - continue; - case 0x4a: - if (arg < 0x80) - { - int delta = (arg < 0x40 ? arg : arg - 0x80); - DBG("Noct: Center = %d", delta); - continue; - } - break; - case 0x52: - if (arg == 0x00 || arg == 0x7f) - { - int state = !!arg; - DBG("Noct: Center touch = %d", state); - continue; - } - break; - case 0x53: - if (arg == 0x00 || arg == 0x7f) - { - int state = !!arg; - DBG("Noct: Slider touch = %d", state); - continue; - } - break; - case 0x60 ... 0x67: - if (arg == 0x00 || arg == 0x7f) - { - int r = cmd - 0x60; - int state = !!arg; - DBG("Noct: Rotary %d touch = %d", r, state); - continue; - } - break; - case 0x70 ... 0x7f: - if (arg == 0x00 || arg == 0x7f) - { - int b = cmd - 0x70; - int state = !!arg; - DBG("Noct: Button %d = %d", b, state); - continue; - } - break; - } - msg(L_ERROR, "Unknown USB packet: unrecognized cmd=%02x arg=%02x", cmd, arg); - } - - int err; - if ((err = libusb_submit_transfer(xfer)) < 0) - die("Cannot submit transfer: error %d", err); -} - -static void noct_read_init(void) -{ - DBG("Noct: Read init"); - - struct libusb_transfer *xfer = libusb_alloc_transfer(0); - libusb_fill_interrupt_transfer(xfer, usb_dev, 0x81, xmalloc(8), 8, noct_read_done, NULL, 0); - - int err; - if ((err = libusb_submit_transfer(xfer)) < 0) - die("Cannot submit transfer: error %d", err); -} - -static byte noct_button_state[16]; -static byte noct_ring_mode[8]; // 0=from-min, 1=from-max, 2=from-mid-right, 3=from-mid-both, 4=single-on, 5=single-off -static byte noct_ring_val[9]; - -static uns noct_dirty_button; -static uns noct_dirty_ring_mode; -static uns noct_dirty_ring_val; - -static struct libusb_transfer *noct_write_xfer; -static uns noct_write_pending; -static void noct_sched_write(void); - -static void noct_write_done(struct libusb_transfer *xfer) -{ - int len = xfer->actual_length; - DBG("USB: Write done: status %d, length %d", xfer->status, len); - - if (xfer->status != LIBUSB_TRANSFER_COMPLETED) - { - msg(L_ERROR, "USB write failed with status %d", xfer->status); - return; - } - - noct_write_pending = 0; - noct_sched_write(); -} - -static void noct_do_write(uns cmd, uns arg) -{ - DBG("USB: Submitting write %02x %02x", cmd, arg); - ASSERT(!noct_write_pending); - noct_write_pending = 1; - - struct libusb_transfer *xfer = noct_write_xfer; - byte *pkt = xfer->buffer; - pkt[0] = cmd; - pkt[1] = arg; - xfer->length = 2; - - int err; - if ((err = libusb_submit_transfer(xfer)) < 0) - die("Cannot submit transfer: error %d", err); -} - -static void noct_sched_write(void) -{ - if (noct_write_pending) - return; - - if (noct_dirty_button) - { - int i = bit_ffs(noct_dirty_button); - noct_dirty_button ^= 1U << i; - noct_do_write(0x70 + i, noct_button_state[i]); - } - else if (noct_dirty_ring_mode) - { - int i = bit_ffs(noct_dirty_ring_mode); - noct_dirty_ring_mode ^= 1U << i; - noct_do_write(0x48 + i, noct_ring_mode[i] << 4); - } - else if (noct_dirty_ring_val) - { - int i = bit_ffs(noct_dirty_ring_val); - noct_dirty_ring_val ^= 1U << i; - if (i == 8) - noct_do_write(0x50 + i, noct_ring_val[i]); - else - noct_do_write(0x40 + i, noct_ring_val[i]); - } -} - -static void noct_write_init(void) -{ - DBG("Noct: Write init"); - - noct_write_xfer = libusb_alloc_transfer(0); - libusb_fill_interrupt_transfer(noct_write_xfer, usb_dev, 0x02, xmalloc(8), 0, noct_write_done, NULL, 1000); - -#if 0 // FIXME - noct_button_state[2] = 1; - noct_ring_mode[0] = 4; - noct_ring_val[0] = 0x40; -#endif - - noct_dirty_button = 0xffff; - noct_dirty_ring_mode = 0xff; - noct_dirty_ring_val = 0x1ff; - noct_sched_write(); -} - -static void usb_init(void) -{ - int err; - - if ((err = libusb_init(&usb_ctx)) < 0) - die("libusb_init failed: error %d", err); - libusb_set_debug(usb_ctx, 3); - - libusb_device **dev_list; - libusb_device *found_dev = NULL; - ssize_t len = libusb_get_device_list(usb_ctx, &dev_list); - for (ssize_t i=0; i < len; i++) - { - libusb_device *dev = dev_list[i]; - struct libusb_device_descriptor desc; - if (libusb_get_device_descriptor(dev, &desc) >= 0 && - desc.idVendor == 0x1235 && - desc.idProduct == 0x000a) - { - msg(L_DEBUG, "Found device: bus %d, addr %d", libusb_get_bus_number(dev), libusb_get_device_address(dev)); - if (found_dev) - die("Multiple Nocturn devices found. Please fix me to handle it."); - found_dev = libusb_ref_device(dev); - } - } - libusb_free_device_list(dev_list, 1); - - if (!found_dev) - die("No Nocturn device found"); - - msg(L_DEBUG, "Initializing device"); - - if ((err = libusb_open(found_dev, &usb_dev)) < 0) - die("libusb_open failed: error %d", err); - - // There exist configurations 1 (high brightness) and 2 (power-save) - if ((err = libusb_set_configuration(usb_dev, 1)) < 0) - die("libusb_set_configuration: error %d", err); - - if ((err = libusb_claim_interface(usb_dev, 0)) < 0) - die("libusb_claim_interface: error %d", err); - - for (int i=0; i<4; i++) - { - int done; - if ((err = libusb_interrupt_transfer(usb_dev, 0x02, (byte *) noct_init[i] + 1, noct_init[i][0], &done, 5000)) < 0) - die("Cannot send init packets: error %d", err); - if (done != noct_init[i][0]) - die("Partial send of init packet: %d < %d", done, noct_init[i][0]); - } - -#if 0 - byte xxx[] = { 0x7f, 0x01 }; - int done; - libusb_interrupt_transfer(usb_dev, 0x02, xxx, 2, &done, 5000); -#endif - - DBG("USB: Connecting libusb to mainloop"); - - if (!libusb_pollfds_handle_timeouts(usb_ctx)) - die("Unsupported version of libusb, please fix me"); - - GARY_INIT_ZERO(usb_fds, 0); - libusb_set_pollfd_notifiers(usb_ctx, usb_added_fd, usb_removed_fd, NULL); - - const struct libusb_pollfd **fds = libusb_get_pollfds(usb_ctx); - ASSERT(fds); - for (int i=0; fds[i]; i++) - usb_added_fd(fds[i]->fd, fds[i]->events, NULL); - free(fds); - - noct_read_init(); - noct_write_init(); -} - /* * Interface to PulseAudio - * - * FIXME */ static pa_context *pulse_ctx; +static void pulse_dump(void); +static void pulse_schedule_update(void); + enum pulse_state { PS_OFFLINE, PS_SUBSCRIBE, @@ -427,6 +72,40 @@ static void pulse_op_cancel_all(void) #define PULSE_ASYNC_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->o = name(__VA_ARGS__, _op); } while (0) #define PULSE_ASYNC_INIT_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->is_init = 1; _op->o = name(__VA_ARGS__, _op); } while (0) +static void pulse_dump_proplist(pa_proplist *pl UNUSED) +{ +#ifdef LOCAL_DEBUG + void *iterator = NULL; + const char *key; + + while (key = pa_proplist_iterate(pl, &iterator)) + { + const char *val = pa_proplist_gets(pl, key); + DBG(" %s = %s", key, val); + } +#endif +} + +struct pulse_sink_input { + int idx; + char *name; + int client_idx; + int sink_idx; + uns volume; + uns mute; +}; + +#define HASH_NODE struct pulse_sink_input +#define HASH_PREFIX(x) pulse_sink_input_##x +#define HASH_KEY_ATOMIC idx +// #define HASH_WANT_CLEANUP +#define HASH_WANT_LOOKUP +#define HASH_WANT_REMOVE +#define HASH_ZERO_FILL +#include + +#define SET_STRING(_field, _val) do { if (!_field || strcmp(_field, _val)) { xfree(_field); _field = xstrdup(_val); } } while (0) + static void pulse_sink_input_cb(pa_context *ctx UNUSED, const pa_sink_input_info *i, int eol, void *userdata) { struct pulse_op *op = userdata; @@ -434,14 +113,52 @@ static void pulse_sink_input_cb(pa_context *ctx UNUSED, const pa_sink_input_info if (eol) { if (op->is_init) - PULSE_STATE(PS_ONLINE); + { + PULSE_STATE(PS_ONLINE); + pulse_schedule_update(); + } pulse_op_done(op); return; } - DBG("Pulse: SINK INPUT #%u: %s client=%d sink=%d", i->index, i->name, i->client, i->sink); + DBG("Pulse: SINK INPUT #%u: %s client=%d sink=%d has_vol=%d vol_rw=%d volume=%u mute=%d", + i->index, i->name, i->client, i->sink, i->has_volume, i->volume_writable, i->volume.values[0], i->mute); + pulse_dump_proplist(i->proplist); + + struct pulse_sink_input *s = pulse_sink_input_lookup(i->index); + SET_STRING(s->name, i->name); + s->client_idx = i->client; + s->sink_idx = i->sink; + s->volume = pa_cvolume_avg(&i->volume); + s->mute = i->mute; + pulse_schedule_update(); +} + +static void pulse_sink_input_gone(int idx) +{ + DBG("Pulse: REMOVE SINK INPUT #%d", idx); + struct pulse_sink_input *s = pulse_sink_input_lookup(idx); + pulse_sink_input_remove(s); + pulse_schedule_update(); } +struct pulse_sink { + int idx; + char *name; + uns volume; + uns base_volume; + int mute; +}; + +#define HASH_NODE struct pulse_sink +#define HASH_PREFIX(x) pulse_sink_##x +#define HASH_KEY_ATOMIC idx +// #define HASH_WANT_CLEANUP +#define HASH_WANT_LOOKUP +#define HASH_WANT_REMOVE +#define HASH_ZERO_FILL +#include + static void pulse_sink_cb(pa_context *ctx, const pa_sink_info *i, int eol, void *userdata) { struct pulse_op *op = userdata; @@ -457,9 +174,41 @@ static void pulse_sink_cb(pa_context *ctx, const pa_sink_info *i, int eol, void return; } - DBG("Pulse: SINK #%u: %s (%s)", i->index, i->name, i->description); + DBG("Pulse: SINK #%u: %s (%s) flags=%08x volume=%u mute=%d base_vol=%u state=%u", + i->index, i->name, i->description, i->flags, i->volume.values[0], i->mute, i->base_volume, i->state); + pulse_dump_proplist(i->proplist); + + struct pulse_sink *s = pulse_sink_lookup(i->index); + SET_STRING(s->name, i->name); + s->volume = pa_cvolume_avg(&i->volume); + s->base_volume = i->base_volume; + s->mute = i->mute; + pulse_schedule_update(); } +static void pulse_sink_gone(int idx) +{ + DBG("Pulse: REMOVE SINK #%d", idx); + struct pulse_sink *s = pulse_sink_lookup(idx); + pulse_sink_remove(s); + pulse_schedule_update(); +} + +struct pulse_client { + int idx; + char *name; + char *host; +}; + +#define HASH_NODE struct pulse_client +#define HASH_PREFIX(x) pulse_client_##x +#define HASH_KEY_ATOMIC idx +// #define HASH_WANT_CLEANUP +#define HASH_WANT_LOOKUP +#define HASH_WANT_REMOVE +#define HASH_ZERO_FILL +#include + static void pulse_client_cb(pa_context *ctx, const pa_client_info *i, int eol, void *userdata) { struct pulse_op *op = userdata; @@ -475,7 +224,23 @@ static void pulse_client_cb(pa_context *ctx, const pa_client_info *i, int eol, v return; } - DBG("Pulse: CLIENT #%u: %s mod=%u drv=%s", i->index, i->name, i->owner_module, i->driver); + char *host = stk_strdup(pa_proplist_gets(i->proplist, "application.process.host") ? : "?"); + DBG("Pulse: CLIENT #%u: %s mod=%u drv=%s host=%s", + i->index, i->name, i->owner_module, i->driver, host); + pulse_dump_proplist(i->proplist); + + struct pulse_client *c = pulse_client_lookup(i->index); + SET_STRING(c->name, i->name); + SET_STRING(c->host, host); + pulse_schedule_update(); +} + +static void pulse_client_gone(int idx) +{ + DBG("Pulse: REMOVE CLIENT #%d", idx); + struct pulse_client *c = pulse_client_lookup(idx); + pulse_client_remove(c); + pulse_schedule_update(); } static void pulse_subscribe_done_cb(pa_context *ctx, int success, void *userdata) @@ -501,19 +266,19 @@ static void pulse_event_cb(pa_context *ctx, pa_subscription_event_type_t type, u if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) PULSE_ASYNC_RUN(pa_context_get_client_info, ctx, idx, pulse_client_cb); else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) - DBG("Pulse: REMOVE CLIENT #%u", idx); + pulse_client_gone(idx); break; case PA_SUBSCRIPTION_EVENT_SINK: if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) PULSE_ASYNC_RUN(pa_context_get_sink_info_by_index, ctx, idx, pulse_sink_cb); else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) - DBG("Pulse: REMOVE SINK #%u", idx); + pulse_sink_gone(idx); break; case PA_SUBSCRIPTION_EVENT_SINK_INPUT: if (action == PA_SUBSCRIPTION_EVENT_NEW || action == PA_SUBSCRIPTION_EVENT_CHANGE) PULSE_ASYNC_RUN(pa_context_get_sink_input_info, ctx, idx, pulse_sink_input_cb); else if (action == PA_SUBSCRIPTION_EVENT_REMOVE) - DBG("Pulse: REMOVE SINK INPUT #%u", idx); + pulse_sink_input_gone(idx); break; } } @@ -541,10 +306,52 @@ static void pulse_state_cb(pa_context *ctx, void *userdata UNUSED) } } +static void pulse_dump(void) +{ + HASH_FOR_ALL(pulse_client, c) + { + DBG("## Client #%d: %s host=%s", c->idx, c->name, c->host); + } + HASH_END_FOR; + + HASH_FOR_ALL(pulse_sink, s) + { + DBG("## Sink #%d: %s volume=%u base_vol=%u mute=%u", + s->idx, s->name, s->volume, s->base_volume, s->mute); + } + HASH_END_FOR; + + HASH_FOR_ALL(pulse_sink_input, s) + { + DBG("## Sink input #%d: %s client=%d sink=%d volume=%u mute=%u", + s->idx, s->name, s->client_idx, s->sink_idx, s->volume, s->mute); + } + HASH_END_FOR; +} + +static struct main_timer pulse_update_timer; + +static void pulse_update(struct main_timer *t) +{ + timer_del(t); + DBG("## UPDATE"); + pulse_dump(); +} + +static void pulse_schedule_update(void) +{ + timer_add_rel(&pulse_update_timer, 500); +} + static void pulse_init(void) { pmain_init(); clist_init(&pulse_op_list); + pulse_client_init(); + pulse_sink_init(); + pulse_sink_input_init(); + pulse_update_timer.handler = pulse_update; + pulse_ctx = pa_context_new(&pmain_api, "ursaryd"); pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL); pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); @@ -555,8 +362,8 @@ int main(int argc UNUSED, char **argv) log_init(argv[0]); main_init(); - // msg(L_INFO, "Initializing USB"); - // usb_init(); + // msg(L_INFO, "Initializing Nocturn"); + // noct_init(); msg(L_INFO, "Initializing PulseAudio"); pulse_init(); -- 2.39.2