From e9d5e83e2fcea473c33c141f092d29ba9a378d97 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 26 Nov 2019 14:57:09 +0000 Subject: [PATCH] Fix headset hotplug Kill the old GIOChannel on shutdown (otherwise events were duplicated after a manual rescan because the original one wasn't really torn down). Remove the need for manual rescan anyway, by automatically rescanning whenever a call starts... and by tearing down an existing headset when its fd returns error. --- jabra.c | 57 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/jabra.c b/jabra.c index 1097938..caed49d 100644 --- a/jabra.c +++ b/jabra.c @@ -25,6 +25,7 @@ struct jabra_headset { GIOChannel *ch; + guint watch_src; int fd; gboolean muted; gboolean connected; @@ -248,18 +249,25 @@ static int doListDev(char *path) { if ((fd = open(path, O_RDONLY)) != -1) { if (ioctl(fd, HIDIOCGDEVINFO, &devinfo) == -1) { perror("ioctl HIDIOCGDEVINFO"); + close(fd); return -1; } if (ioctl(fd, HIDIOCGNAME(sizeof(name)), name) == -1) { perror("ioctl HIDIOCGNAME"); + close(fd); return -1; } if (ioctl(fd, HIDIOCGVERSION, &version) == -1) { perror("ioctl HIDIOCGVERSION"); + close(fd); return -1; } - return (devinfo.vendor == JABRA_VID || - devinfo.vendor == PLANTRONICS_VID); + if (devinfo.vendor != JABRA_VID && + devinfo.vendor != PLANTRONICS_VID) { + close(fd); + return -1; + } + return fd; } if (errno == ENOENT) { @@ -413,8 +421,11 @@ static gboolean jabra_in(GIOChannel *gio, GIOCondition condition, gpointer user_ gsize rd; GIOStatus ret = g_io_channel_read_chars(gio, (void *)&ev, sizeof(ev), &rd, NULL); - if (ret != G_IO_STATUS_NORMAL) + if (ret != G_IO_STATUS_NORMAL) { + purple_debug(PURPLE_DEBUG_INFO, "headset", "Unplugged\n"); + shutdown_headset(); return FALSE; + } for (i = 0; i < rd / sizeof(ev[0]); i++) { fprintf(stdout, "Event: %x = %d\n", ev[i].hid, ev[i].value); @@ -477,26 +488,21 @@ gboolean init_headset(void) pthread_t event_thread; __s32 val; + fd = -1; for (i = 0; i < 19; i++) { sprintf(name, "/dev/usb/hiddev%d", i); - if (doListDev(name) == 1) { + fd = doListDev(name); + if (fd != -1) break; - } } - if (i == 19) { + if (fd == -1) { purple_debug(PURPLE_DEBUG_INFO, "headset", "No Jabra device found\n"); return FALSE; } - sprintf(name, "/dev/usb/hiddev%d", i); purple_debug(PURPLE_DEBUG_INFO, "headset", "Using device %s\n", name); - if ((fd = open(name, O_RDONLY)) < 0) { - purple_debug(PURPLE_DEBUG_INFO, "headset", "Failed to open %s\n", name); - return FALSE; - } - ioctl(fd, HIDIOCINITREPORT, 0); ioctl(fd, HIDIOCGNAME(sizeof(name)), name); purple_debug(PURPLE_DEBUG_INFO, "headset", "HID device name: \"%s\"\n", name); @@ -515,18 +521,26 @@ gboolean init_headset(void) fcntl(fd, F_SETFL, O_NONBLOCK); - GIOChannel *ch = g_io_channel_unix_new(fd); - g_io_channel_set_encoding(ch, NULL, NULL); - g_io_channel_set_close_on_unref(ch, TRUE); - g_io_channel_set_buffered (ch, FALSE); - g_io_add_watch(ch, G_IO_IN | G_IO_HUP, jabra_in, NULL); + jabra.ch = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(jabra.ch, NULL, NULL); + g_io_channel_set_close_on_unref(jabra.ch, TRUE); + g_io_channel_set_buffered (jabra.ch, FALSE); + jabra.watch_src = g_io_add_watch(jabra.ch, G_IO_IN | G_IO_HUP | G_IO_ERR, jabra_in, NULL); + return TRUE; } void shutdown_headset(void) { - g_object_unref(jabra.ch); - jabra.ch = NULL; + if (jabra.watch_src) { + g_source_remove(jabra.watch_src); + jabra.watch_src = 0; + } + if (jabra.ch) { + g_io_channel_shutdown(jabra.ch, FALSE, NULL); + g_io_channel_unref(jabra.ch); + jabra.ch = NULL; + } jabra.fd = -1; } @@ -542,6 +556,11 @@ void mute_headset(gpointer user_data, gboolean muted) void connect_headset(gpointer user_data, gboolean connected) { purple_debug(PURPLE_DEBUG_INFO, "headset", "connect %d\n", connected); + + /* Rescan headset whenever a call starts */ + if (connected && jabra.fd == -1) + init_headset(); + if (jabra.connected != connected) { jabra.connected = connected; if (!connected) { -- 2.50.1