]> www.infradead.org Git - users/dwmw2/pidgin-headset.git/commitdiff
Wire up Jabra headset
authorDavid Woodhouse <dwmw2@infradead.org>
Wed, 21 Mar 2018 22:17:34 +0000 (22:17 +0000)
committerDavid Woodhouse <dwmw2@infradead.org>
Wed, 21 Mar 2018 22:17:34 +0000 (22:17 +0000)
Makefile.am
configure.ac
headset.c
headset.h
jabra.c

index 85e5eb4b9e019b439cb9e27039725738f42f7138..597081e45b593204410e1e2475d73f2b831411b5 100644 (file)
@@ -2,11 +2,11 @@
 
 pidgin_plugin_LTLIBRARIES = headset.la
 
-headset_la_CFLAGS = $(PIDGIN_CFLAGS)
+headset_la_CFLAGS = $(PIDGIN_CFLAGS) $(GLIB_CFLAGS)
 
 headset_la_SOURCES = headset.c jabra.c headset.h
 
 headset_la_LDFLAGS = -module -avoid-version -no-undefined
 
-headset_la_LIBADD = $(PIDGIN_LIBS)
+headset_la_LIBADD = $(PIDGIN_LIBS) $(GLIB_LIBS)
 
index 7716c595056cc0f0a70793aea789d0cc4782ec76..79452ba11c8625187c6bc7e2ca8088edefbb996e 100644 (file)
@@ -33,6 +33,7 @@ AC_SUBST(WFLAGS, [$WFLAGS])
 
 pidgin_plugindir=
 PKG_CHECK_MODULES(PIDGIN, [pidgin])
+PKG_CHECK_MODULES(GLIB, [glib-2.0])
 pidgin_plugindir="$($PKG_CONFIG --variable=plugindir pidgin)"
 AC_SUBST(pidgin_plugindir, ${pidgin_plugindir})
 
index 7ef813988e7ac73beb9dd9fa41c888ce71950c39..000c1f1f2736cc4bab755c78c7dc5d92b14d4ac4 100644 (file)
--- a/headset.c
+++ b/headset.c
@@ -192,6 +192,8 @@ headset_plugin_load(PurplePlugin *plugin)
        g_signal_connect(mgr, "init-media", G_CALLBACK(headset_init_media_cb), NULL);
 
        purple_debug(PURPLE_DEBUG_INFO, "headset", "headset plugin loaded.\n");
+
+       init_headset();
        return TRUE;
 }
 
@@ -203,17 +205,15 @@ headset_plugin_unload(PurplePlugin *plugin)
        g_signal_handlers_disconnect_matched(mgr, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(headset_init_media_cb), NULL);
 
        GList *medias = purple_media_manager_get_media(mgr);
-       gboolean ret = FALSE;
 
        for (; medias; medias = medias->next) {
                PurpleMedia *media = medias->data;
 
                g_signal_handlers_disconnect_matched(media, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(headset_media_state_changed_cb), NULL);
                g_signal_handlers_disconnect_matched(media, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(headset_media_stream_info_cb), NULL);
-
        }
-       return ret;
 
+       shutdown_headset();
        purple_debug(PURPLE_DEBUG_INFO, "headset", "headset plugin unloaded.\n");
        return TRUE;
 }
index 8e9803000087767e72b920f7ea2bafcec15f8583..e54be78c62ca70bcd560d35eabcaac058b59a23f 100644 (file)
--- a/headset.h
+++ b/headset.h
@@ -26,5 +26,7 @@ void headset_connected(gpointer user_data, gboolean connected);
 /* jabra.c */
 void mute_headset(gpointer user_data, gboolean muted);
 void connect_headset(gpointer user_data, gboolean connected);
+gboolean init_headset(void);
+void shutdown_headset(void);
 
 #endif /* __HEADSET_H__ */
diff --git a/jabra.c b/jabra.c
index c9d54eae1b370bc10ce83c6c5266e4d8edc14000..d9de5f796c97e4c9ecce3d93224cde520e8b26f9 100644 (file)
--- a/jabra.c
+++ b/jabra.c
 
 #include "headset.h"
 
+#include <glib.h>
+#include <glib-object.h>
+
+struct jabra_headset {
+       GIOChannel *ch;
+       int fd;
+       gboolean muted;
+       gboolean connected;
+       gboolean ringing;
+};
+
+static struct jabra_headset jabra = {
+       .fd = -1,
+};
+
+/* MIT License
+ *
+ * Copyright (c) 2017 GN Audio A/S (Jabra)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * @file   jabra_hiddev_demo.c
+ *
+ * @brief  Demonstration program for basic call control functionality:
+ *         mute/offhook/ringer using the Linux hiddev device interface.
+ *
+ *         This program will work with most Jabra devices.
+ *
+ *         The program must have priviledges to read and write the
+ *         /dev/usb/hiddev[0-19] device.
+ *
+ *         To compile:
+ *         gcc jabra_hiddev_demo.c -o jabra_hiddev_demo -lpthread
+ *
+ * @author Flemming Mortensen
+ */
+
+/****************************************************************************/
+/*                              INCLUDE FILES                               */
+/****************************************************************************/
+#include <asm/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/hiddev.h>
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/****************************************************************************/
+/*                      PRIVATE TYPES and DEFINITIONS                       */
+/****************************************************************************/
+#define HIDDEBUG 1
+
+/* Jabra Vendor Id */
+#define JABRA_VID            ((__u16) 0x0B0E)
+
+/* HID Usage Page definitions */
+#define TelephonyUsagePage   ((__u16) 0x000B)
+#define ConsumerUsagePage    ((__u16) 0x000C)
+#define LEDUsagePage         ((__u16) 0x0008)
+#define ButtonUsagePage      ((__u16) 0x0009)
+
+/* HID Usage Id definitions: LED usage page (0x08) */
+#define Led_Mute             ((__u16) 0x0009)
+#define Led_Off_Hook         ((__u16) 0x0017)
+#define Led_Ring             ((__u16) 0x0018)
+#define Led_Hold             ((__u16) 0x0020)
+#define Led_Microphone       ((__u16) 0x0021)
+#define Led_On_Line          ((__u16) 0x002A)
+#define Led_Off_Line         ((__u16) 0x002B)
+
+/* HID Usage Id definitions:  Telephony usage page (0x0B) */
+#define Tel_Hook_Switch      ((__u16) 0x0020)
+#define Tel_Flash            ((__u16) 0x0021)
+#define Tel_Feature          ((__u16) 0x0022)
+#define Tel_Hold             ((__u16) 0x0023)
+#define Tel_Redial           ((__u16) 0x0024)
+#define Tel_Transfer         ((__u16) 0x0025)
+#define Tel_Drop             ((__u16) 0x0026)
+#define Tel_Park             ((__u16) 0x0027)
+#define Tel_Forward          ((__u16) 0x0028)
+#define Tel_Alternate        ((__u16) 0x0029)
+#define Tel_Line             ((__u16) 0x002A)
+#define Tel_Speaker          ((__u16) 0x002B)
+#define Tel_Conference       ((__u16) 0x002C)
+#define Tel_Ring_Enable      ((__u16) 0x002D)
+#define Tel_Ring_Select      ((__u16) 0x002E)
+#define Tel_Phone_Mute       ((__u16) 0x002F)
+#define Tel_Caller           ((__u16) 0x0030)
+#define Tel_Send             ((__u16) 0x0031)
+#define Tel_VoiceMail        ((__u16) 0x0070)
+#define Tel_Ringer           ((__u16) 0x009E)
+#define Tel_Phone_Key_0      ((__u16) 0x00B0)
+#define Tel_Phone_Key_1      ((__u16) 0x00B1)
+#define Tel_Phone_Key_2      ((__u16) 0x00B2)
+#define Tel_Phone_Key_3      ((__u16) 0x00B3)
+#define Tel_Phone_Key_4      ((__u16) 0x00B4)
+#define Tel_Phone_Key_5      ((__u16) 0x00B5)
+#define Tel_Phone_Key_6      ((__u16) 0x00B6)
+#define Tel_Phone_Key_7      ((__u16) 0x00B7)
+#define Tel_Phone_Key_8      ((__u16) 0x00B8)
+#define Tel_Phone_Key_9      ((__u16) 0x00B9)
+#define Tel_Phone_Key_Star   ((__u16) 0x00BA)
+#define Tel_Phone_Key_Pound  ((__u16) 0x00BB)
+#define Tel_Phone_Key_A      ((__u16) 0x00BC)
+#define Tel_Phone_Key_B      ((__u16) 0x00BD)
+#define Tel_Phone_Key_C      ((__u16) 0x00BE)
+#define Tel_Phone_Key_D      ((__u16) 0x00BF)
+#define Tel_Control          ((__u16) 0xFFFF)
+
+/* HID Usage Id definitions: Consumer usage page (0x0C) */
+#define Con_Volume_Incr      ((__u16) 0x00E9)
+#define Con_Volume_Decr      ((__u16) 0x00EA)
+
+/****************************************************************************/
+/*                              PRIVATE DATA                                */
+/****************************************************************************/
+
+/****************************************************************************/
+/*                              EXPORTED DATA                               */
+/****************************************************************************/
+
+/* empty */
+
+/****************************************************************************/
+/*                            PRIVATE FUNCTIONS                             */
+/****************************************************************************/
+static const char *usagePageName(__u32 usage_code) {
+  __u16 hi = (usage_code >> 16) & 0xFFFF;
+
+  switch (hi) {
+    case TelephonyUsagePage:   return "TelephonyUsagePage";
+    case ConsumerUsagePage:    return "ConsumerUsagePage";
+    case LEDUsagePage:         return "LEDUsagePage";
+    case ButtonUsagePage:      return "ButtonUsagePage";
+    default:                   return "not translated";
+  }
+}
+
+#if (HIDDEBUG == 1)
+static void showReports(int fd, __u16 report_type) {
+  struct hiddev_report_info rinfo;
+  struct hiddev_field_info finfo;
+  struct hiddev_usage_ref uref;
+  int ret;
+
+  rinfo.report_type = report_type;
+  rinfo.report_id = HID_REPORT_ID_FIRST;
+  ret = ioctl(fd, HIDIOCGREPORTINFO, &rinfo);
+
+  while (ret >= 0) {
+    printf("HIDIOCGREPORTINFO: report_id=0x%X (%u fields)\n", rinfo.report_id, rinfo.num_fields);
+    for (int i = 0; i < rinfo.num_fields; i++) {
+      finfo.report_type = rinfo.report_type;
+      finfo.report_id   = rinfo.report_id;
+      finfo.field_index = i;
+      ioctl(fd, HIDIOCGFIELDINFO, &finfo);
+
+      fprintf(stdout, "HIDIOCGFIELDINFO: field_index=%u maxusage=%u flags=0x%X\n"
+          "\tphysical=0x%X logical=0x%X application=0x%X reportid=0x%X\n"
+          "\tlogical_minimum=%d,maximum=%d physical_minimum=%d,maximum=%d\n",
+        finfo.field_index,
+        finfo.maxusage,
+        finfo.flags,
+        finfo.physical,
+        finfo.logical,
+        finfo.application,
+        finfo.report_id,
+        finfo.logical_minimum,
+        finfo.logical_maximum,
+        finfo.physical_minimum,
+        finfo.physical_maximum);
+
+      for (int j = 0; j < finfo.maxusage; j++) {
+        uref.report_type = finfo.report_type;
+        uref.report_id   = finfo.report_id;
+        uref.field_index = i;
+        uref.usage_index = j;
+        ioctl(fd, HIDIOCGUCODE, &uref);
+        ioctl(fd, HIDIOCGUSAGE, &uref);
+
+        fprintf(stdout, " >> usage_index=%u usage_code=0x%X (%s) value=%d\n",
+          uref.usage_index,
+          uref.usage_code,
+          usagePageName(uref.usage_code),
+          uref.value);
+
+      }
+    }
+    fprintf(stdout, "\n");
+
+    rinfo.report_id |= HID_REPORT_ID_NEXT;
+    ret = ioctl(fd, HIDIOCGREPORTINFO, &rinfo);
+  }
+}
+#endif
+
+static int doListDev(char *path) {
+  int fd;
+  struct hiddev_devinfo devinfo;
+  char name[128];
+  int version;
+
+  if ((fd = open(path, O_RDONLY)) != -1) {
+    if (ioctl(fd, HIDIOCGDEVINFO, &devinfo) == -1) {
+      perror("ioctl HIDIOCGDEVINFO");
+      return -1;
+    }
+    if (ioctl(fd, HIDIOCGNAME(sizeof(name)), name) == -1) {
+      perror("ioctl HIDIOCGNAME");
+      return -1;
+    }
+    if (ioctl(fd, HIDIOCGVERSION, &version) == -1) {
+      perror("ioctl HIDIOCGVERSION");
+      return -1;
+    }
+    return (devinfo.vendor == JABRA_VID);
+  }
+
+  if (errno == ENOENT) {
+    return 0;
+  }
+
+  perror("ioctl HIDIOCGVERSION");
+  return -1;
+}
+
+static void writeUsage(int fd, unsigned report_type, unsigned page, unsigned code, __s32 value) {
+  struct hiddev_report_info rinfo;
+  struct hiddev_field_info finfo;
+  struct hiddev_usage_ref uref;
+
+  /* find the requested usage code */
+  uref.report_type = report_type;
+  uref.report_id   = HID_REPORT_ID_UNKNOWN;
+  uref.usage_code  = (page << 16) | code;
+  if (ioctl(fd, HIDIOCGUSAGE, &uref) < 0) {
+    perror("HIDIOCGUSAGE");
+    return;
+  }
+#if (HIDDEBUG == 1)
+  fprintf(stdout, " >> usage_index=%u usage_code=0x%X (%s) value=%d\n",
+    uref.usage_index,
+    uref.usage_code,
+    usagePageName(uref.usage_code),
+    uref.value);
+#endif
+  /* retrieve field info */
+  finfo.report_type = uref.report_type;
+  finfo.report_id   = uref.report_id;
+  finfo.field_index = uref.field_index;
+  if (ioctl(fd, HIDIOCGFIELDINFO, &finfo) < 0) {
+    perror("HIDIOCGFIELDINFO");
+    return;
+  }
+#if (HIDDEBUG == 1)
+  fprintf(stdout, "HIDIOCGFIELDINFO: field_index=%u maxusage=%u flags=0x%X\n"
+   "\tphysical=0x%X logical=0x%X application=0x%X reportid=0x%X\n"
+   "\tlogical_minimum=%d,maximum=%d physical_minimum=%d,maximum=%d\n",
+    finfo.field_index,
+    finfo.maxusage,
+    finfo.flags,
+    finfo.physical,
+    finfo.logical,
+    finfo.application,
+    finfo.report_id,
+    finfo.logical_minimum,
+    finfo.logical_maximum,
+    finfo.physical_minimum,
+    finfo.physical_maximum);
+#endif
+  if ((value < finfo.logical_minimum) || (value > finfo.logical_maximum)) {
+    fprintf(stdout, "%s: value %d outside of allowed range (%d-%d)\n",
+      usagePageName(uref.usage_code),
+      value,
+      finfo.logical_minimum,
+      finfo.logical_maximum);
+    return;
+  }
+
+  /* set value */
+  uref.value = value;
+  if (ioctl(fd, HIDIOCSUSAGE, &uref) < 0) {
+    perror("HIDIOCSUSAGE");
+    return;
+  }
+
+  rinfo.report_type = uref.report_type;
+  rinfo.report_id   = uref.report_id;
+  if (ioctl(fd, HIDIOCSREPORT, &rinfo) < 0) {
+    perror("HIDIOCSREPORT");
+  }
+}
+
+static void readUsage(int fd, unsigned report_type, unsigned page, unsigned code, __s32* value) {
+  struct hiddev_report_info rinfo;
+  struct hiddev_field_info finfo;
+  struct hiddev_usage_ref uref;
+
+  /* find the requested usage code */
+  uref.report_type = report_type;
+  uref.report_id   = HID_REPORT_ID_UNKNOWN;
+  uref.usage_code  = (page << 16) | code;
+  if (ioctl(fd, HIDIOCGUSAGE, &uref) < 0) {
+    perror("HIDIOCGUSAGE");
+    return;
+  }
+#if (HIDDEBUG == 1)
+  fprintf(stdout, " >> usage_index=%u usage_code=0x%X (%s) value=%d\n",
+    uref.usage_index,
+    uref.usage_code,
+    usagePageName(uref.usage_code),
+    uref.value);
+#endif
+  /* retrieve field info */
+  finfo.report_type = uref.report_type;
+  finfo.report_id   = uref.report_id;
+  finfo.field_index = uref.field_index;
+  if (ioctl(fd, HIDIOCGFIELDINFO, &finfo) < 0) {
+    perror("HIDIOCGFIELDINFO");
+    return;
+  }
+#if (HIDDEBUG == 1)
+  fprintf(stdout, "HIDIOCGFIELDINFO: field_index=%u maxusage=%u flags=0x%X\n"
+   "\tphysical=0x%X logical=0x%X application=0x%X reportid=0x%X\n"
+   "\tlogical_minimum=%d,maximum=%d physical_minimum=%d,maximum=%d\n",
+    finfo.field_index,
+    finfo.maxusage,
+    finfo.flags,
+    finfo.physical,
+    finfo.logical,
+    finfo.application,
+    finfo.report_id,
+    finfo.logical_minimum,
+    finfo.logical_maximum,
+    finfo.physical_minimum,
+    finfo.physical_maximum);
+#endif
+#if 0
+  if ((value < finfo.logical_minimum) || (value > finfo.logical_maximum)) {
+    fprintf(stdout, "%s: value %d outside of allowed range (%d-%d)\n",
+      usagePageName(uref.usage_code),
+      value,
+      finfo.logical_minimum,
+      finfo.logical_maximum);
+    return;
+  }
+#endif
+  /* get value */
+  //  uref.value = value;
+  if (ioctl(fd, HIDIOCGUSAGE, &uref) < 0) {
+    perror("HIDIOCGUSAGE");
+    return;
+  }
+  *value = uref.value;
+
+  rinfo.report_type = uref.report_type;
+  rinfo.report_id   = uref.report_id;
+  if (ioctl(fd, HIDIOCSREPORT, &rinfo) < 0) {
+    perror("HIDIOCSREPORT");
+  }
+}
+
+static gboolean jabra_in(GIOChannel *gio, GIOCondition condition, gpointer user_data)
+{
+       int i;
+       struct hiddev_event ev[64];
+       gsize rd;
+
+       GIOStatus ret = g_io_channel_read_chars(gio, (void *)&ev, sizeof(ev), &rd, NULL);
+       if (ret != G_IO_STATUS_NORMAL)
+               return FALSE;
+
+       for (i = 0; i < rd / sizeof(ev[0]); i++) {
+               fprintf(stdout, "Event: %x = %d\n", ev[i].hid, ev[i].value);
+
+               switch (ev[i].hid >> 16) {
+               case TelephonyUsagePage:
+                       //fprintf(stdout, "Event: %x = %d\n", ev[i].hid, ev[i].value);
+                       switch (ev[i].hid & 0xFFFF) {
+                       case Tel_Hook_Switch:
+                               if (jabra.connected != ev[i].value) {
+                                       if (!jabra.connected) {
+                                               writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Ring, 0);
+                                               writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, TelephonyUsagePage, Tel_Ringer, 0);
+                                       }
+                                       writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Off_Hook, ev[i].value);
+                                       headset_connected(NULL, ev[i].value);
+                               }
+                               break;
+                       case Tel_Phone_Mute:
+                               //fprintf(stdout, "Event: %x = %d\n", ev[i].hid, ev[i].value);
+                               if (ev[i].value == 1) {
+                                       jabra.muted = !jabra.muted;
+                                       writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Mute, jabra.muted);
+                                       headset_muted(NULL, jabra.muted);
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               case ConsumerUsagePage:
+                       //fprintf(stdout, "Event: %x = %d\n", ev[i].hid, ev[i].value);
+                       switch (ev[i].hid & 0xFFFF) {
+                       case Con_Volume_Decr:
+                               if (ev[i].value) fprintf(stdout, "Volume decrement = 0x%x\n", ev[i].value);
+                               break;
+                       case Con_Volume_Incr:
+                               if (ev[i].value) fprintf(stdout, "Volume increment = 0x%x\n", ev[i].value);
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+       return TRUE;
+
+}
+
+/****************************************************************************/
+/*                           EXPORTED FUNCTIONS                             */
+/****************************************************************************/
+gboolean init_headset(void)
+{
+       int fd, i;
+       char name[128];
+       int retval = 0;
+       pthread_t event_thread;
+       __s32 val;
+
+       for (i = 0; i < 19; i++) {
+               sprintf(name, "/dev/usb/hiddev%d", i);
+               if (doListDev(name) == 1) {
+                       break;
+               }
+       }
+
+       if (i == 19) {
+               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);
+
+       /* set initial values */
+       jabra.fd = fd;
+       jabra.muted = jabra.connected = jabra.ringing = FALSE;
+       readUsage(fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Mute, &val);
+       jabra.muted = val;
+
+       readUsage(fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Off_Hook, &val);
+       jabra.connected = val;
+
+       readUsage(fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Ring, &val);
+       jabra.ringing = val;
+
+       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);
+       return TRUE;
+}
+
+void shutdown_headset(void)
+{
+       g_object_unref(jabra.ch);
+       jabra.ch = NULL;
+       jabra.fd = -1;
+}
+
 void mute_headset(gpointer user_data, gboolean muted)
 {
        purple_debug(PURPLE_DEBUG_INFO, "headset", "mute %d\n", muted);
+       if (jabra.muted != muted) {
+               jabra.muted = muted;
+               writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Mute, muted);
+       }
 }
 
-void connect_headset(gpointer user_data, gboolean muted)
+void connect_headset(gpointer user_data, gboolean connected)
 {
-       purple_debug(PURPLE_DEBUG_INFO, "headset", "connect %d\n", muted);
+       purple_debug(PURPLE_DEBUG_INFO, "headset", "connect %d\n", connected);
+       if (jabra.connected != connected) {
+               jabra.connected = connected;
+               if (!connected) {
+                       writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Ring, 0);
+                       writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, TelephonyUsagePage, Tel_Ringer, 0);
+               }
+               writeUsage(jabra.fd, HID_REPORT_TYPE_OUTPUT, LEDUsagePage, Led_Off_Hook, connected);
+       }
 }
 
+