#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);
+ }
}
+