Button and wheel translation for Chic Technology Corp Browser Mouse via USB This patch implements a USB HID quirk for Chic Technology Corp Browser Mouse devices. These are sold in at least two guises; I've encountered one, the Advent 5-button wireless rechargeable optical mouse. This mouse has the usual inputs common to ExplorerPS/2 mice; additionally, the wheel is tiltable and there's an extra button which the documenation calls a "hot-key button". It identifies itself as Vendor=0x5fe ProdID=0011 Rev= 1.01 Manufacturer=Wireless Mouse Product=Wireless Mouse (but see also http://www.qbik.ch/usb/devices/showdescr.php?id=3244 for an apparently different device with the same vendor and product IDs). Its buttons behave identically whether the mouse is connected via PS/2 or USB. I've tested with a USB snooper on a Windows box (with the mouse's driver installed) and it still reported the buttons as I'd already observed on Linux. The mouse always reports the extra three buttons as follows: * Pushing the wheel to the left => buttons 3 and 4 (middle + side). * Pushing the wheel to the right => buttons 3 and 5 (middle + extra). * Pressing the "hot-key" button => buttons 2 and 4 (right + side). The quirk handles these by making the wheel pushes look like REL_HWHEEL events and the hot-key button look like BTN_FORWARD. (X needs to be told that the mouse has 10 buttons. The hot-key button could be mapped to BTN_TASK, making it a "12-button" mouse as far as X is concerned.) REL_HWHEEL events are repeated using an extra timer which is allocated as part of struct hid_device. Signed-off-by: Darren Salt diff -ur linux-2.6.23.orig/drivers/hid/hid-core.c linux-2.6.23/drivers/hid/hid-core.c --- linux-2.6.23.orig/drivers/hid/hid-core.c 2007-12-06 23:39:40.000000000 +0000 +++ linux-2.6.23/drivers/hid/hid-core.c 2007-12-06 23:41:38.000000000 +0000 @@ -863,6 +863,9 @@ hid_process_event(hid, field, &field->usage[value[n] - min], 1, interrupt); } + if (hid->claimed & HID_CLAIMED_INPUT) + hidinput_hid_quirks(hid, field); + memcpy(field->value, value, count * sizeof(__s32)); exit: kfree(value); diff -ur linux-2.6.23.orig/drivers/hid/hid-input.c linux-2.6.23/drivers/hid/hid-input.c --- linux-2.6.23.orig/drivers/hid/hid-input.c 2007-12-06 23:39:40.000000000 +0000 +++ linux-2.6.23/drivers/hid/hid-input.c 2007-12-06 23:42:54.000000000 +0000 @@ -334,6 +334,86 @@ } +struct hid_pseudobutton_mappings_t { + __u16 mask, type, button; + __s16 value; +}; + +/* Button mappings for the Chic browser mouse (USB ID 05FE:0011). + * This is sold as (at least): + * * Advent 5-button wireless rechargeable mouse + * It has three extra buttons (wheel pushed sideways, task button) which are + * signalled as pairs of standard buttons. + */ +static const struct hid_pseudobutton_mappings_t chic_browser_mouse_buttons[] = { +#if 0 + { 1 << 1 | 1 << 3, EV_KEY, BTN_TASK, 1 }, /* 2 and 4 (hot-key) */ + { 1 << 2 | 1 << 3, EV_KEY, BTN_BACK, 1 }, /* 3 and 4 (wheel left) */ + { 1 << 2 | 1 << 4, EV_KEY, BTN_FORWARD, 1 }, /* 3 and 5 (wheel right) */ +#else + { 1 << 1 | 1 << 3, EV_KEY, BTN_FORWARD, 1 }, /* 2 and 4 (hot-key) */ + { 1 << 2 | 1 << 3, EV_REL, REL_HWHEEL, -1 }, /* 3 and 4 (wheel left) */ + { 1 << 2 | 1 << 4, EV_REL, REL_HWHEEL, 1 }, /* 3 and 5 (wheel right) */ +#endif + {} +}; + +/* Repeat for e.g. pseudo-wheels. We use the input device's own repeat values. */ +static void hidinput_repeat_event(unsigned long data) +{ + struct input_dev *input = (void *) data; + struct hid_device *device = input->private; + + if (!device->repeat.value) + return; + + input_event(input, device->repeat.type, device->repeat.button, device->repeat.value); + input_sync(input); + + if (input->rep[REP_PERIOD]) + mod_timer(&device->repeat.timer, jiffies + msecs_to_jiffies(input->rep[REP_PERIOD])); +} + +static void hidinput_register_pseudobuttons (struct hid_input *hidinput, + struct hid_field *field, + struct hid_usage *usage, + unsigned long *bit, + const struct hid_pseudobutton_mappings_t *buttons, + int allow_repeat) +{ + int i; + int have_wheel = 0; + struct hid_device *device = hidinput->input->private; + + for (i = 0; buttons[i].mask; ++i) + { + if (usage->type != buttons[i].type) + continue; + + switch (buttons[i].type) + { + case EV_KEY: + if (usage->code == BTN_EXTRA) + set_bit(buttons[i].button, bit); + break; + case EV_REL: + if (usage->code == REL_WHEEL) { + have_wheel = allow_repeat; + set_bit(buttons[i].button, bit); + } + break; + } + } + + /* If the quirk has introduced an extra wheel (for example), + * we need to handle our own repeats. */ + if (have_wheel && !device->repeat.timer.function) { + init_timer(&device->repeat.timer); + device->repeat.timer.data = (long) hidinput->input; + device->repeat.timer.function = hidinput_repeat_event; + } +} + static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field, struct hid_usage *usage) { @@ -824,6 +904,9 @@ map_key(BTN_1); } + if (device->quirks & HID_QUIRK_CHIC_PSEUDOBUTTONS_HACK) + hidinput_register_pseudobuttons (hidinput, field, usage, bit, chic_browser_mouse_buttons, 1); + if ((device->quirks & (HID_QUIRK_2WHEEL_MOUSE_HACK_7 | HID_QUIRK_2WHEEL_MOUSE_HACK_5)) && (usage->type == EV_REL) && (usage->code == REL_WHEEL)) set_bit(REL_HWHEEL, bit); @@ -924,6 +1007,12 @@ return; } + if (!(hid->quirks & HID_QUIRK_CHIC_PSEUDOBUTTONS_HACK) && + (hid->quirks & HID_QUIRK_2WHEEL_MOUSE_HACK_ON) && (usage->code == REL_WHEEL)) { + input_event(input, usage->type, REL_HWHEEL, value); + return; + } + if ((hid->quirks & HID_QUIRK_2WHEEL_MOUSE_HACK_ON) && (usage->code == REL_WHEEL)) { input_event(input, usage->type, REL_HWHEEL, value); return; @@ -973,9 +1062,20 @@ return; } + /* For quirks where one physical button looks like (at least) two other physical buttons */ + if ((usage->type == EV_KEY) && (usage->hid > 0x00090000) && (usage->hid <= 0x00090010)) { + if (value) + hid->btn_state |= 1 << (usage->hid - 0x00090001); + else + hid->btn_state &= ~(1 << (usage->hid - 0x00090001)); + } + if ((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UNKNOWN */ return; + if ((usage->type == EV_KEY) && (hid->quirks & HID_QUIRK_CHIC_PSEUDOBUTTONS_HACK)) /* some buttons may have quirks - *don't* generate events just yet */ + return; + if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) && (usage->code == ABS_VOLUME)) { int count = abs(value); @@ -1006,6 +1106,60 @@ } EXPORT_SYMBOL_GPL(hidinput_report_event); +static void hidinput_hid_quirk_process_pseudobuttons (struct hid_device *hid, struct input_dev *input, + const struct hid_pseudobutton_mappings_t *mapping) +{ + __u16 buttons = hid->btn_state; + __u16 previous = hid->btn_prev_state; + int i; + + hid->btn_prev_state = hid->btn_state; + + for (i = 0; mapping[i].mask; ++i) { + /* both clear -> both set => button is now pressed */ + if (!(hid->btn_quirks & (1 << i)) + && ((previous & mapping[i].mask) == 0) + && ((buttons & mapping[i].mask) == mapping[i].mask)) { + buttons &= ~mapping[i].mask; + hid->btn_quirks |= 1 << i; + input_event (input, mapping[i].type, mapping[i].button, mapping[i].value); + /* If it's something like a pseudowheel, start repeating it. */ + if (((mapping[i].button == REL_WHEEL) || (mapping[i].button == REL_HWHEEL)) && + hid->repeat.timer.function && input->rep[REP_PERIOD] && input->rep[REP_DELAY]) { + hid->repeat.type = mapping[i].type; + hid->repeat.button = mapping[i].button; + hid->repeat.value = mapping[i].value; + mod_timer(&hid->repeat.timer, jiffies + msecs_to_jiffies(input->rep[REP_DELAY])); + } + } + /* both set -> either clear => button is now released */ + else + if ((hid->btn_quirks & (1 << i)) + && ((previous & mapping[i].mask) == mapping[i].mask) + && ((buttons & mapping[i].mask) != mapping[i].mask)) { + buttons &= ~mapping[i].mask; + hid->btn_quirks &= ~(1 << i); + input_event (input, mapping[i].type, mapping[i].button, 0); + if ((mapping[i].button == REL_WHEEL) || (mapping[i].button == REL_HWHEEL)) + hid->repeat.value = 0; + } + } + + for (i = 0; i < 16; ++i) + if ((buttons ^ previous) & (1 << i)) + input_event (input, EV_KEY, BTN_MOUSE + i, (buttons >> i) & 1); +} + +void hidinput_hid_quirks(struct hid_device *hid, struct hid_field *field) +{ + if (!field->hidinput) + return; + /* Chic browser mouse (sold as, at least, Advent 5-button) */ + if (hid->quirks & HID_QUIRK_CHIC_PSEUDOBUTTONS_HACK) + hidinput_hid_quirk_process_pseudobuttons (hid, field->hidinput->input, chic_browser_mouse_buttons); +} +EXPORT_SYMBOL_GPL(hidinput_hid_quirks); + int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field) { struct hid_report *report; @@ -1120,6 +1274,10 @@ if (hidinput) input_register_device(hidinput->input); + hid->btn_state = 0; + hid->btn_prev_state = 0; + hid->btn_quirks = 0; + return 0; } EXPORT_SYMBOL_GPL(hidinput_connect); @@ -1129,6 +1287,11 @@ struct hid_input *hidinput, *next; list_for_each_entry_safe(hidinput, next, &hid->inputs, list) { + /* Stop any repeat timer which may have been registered. */ + struct hid_device *device = hidinput->input->private; + if (device->repeat.timer.function) + del_timer_sync(&device->repeat.timer); + /* Unregister the device and clean up. */ list_del(&hidinput->list); input_unregister_device(hidinput->input); kfree(hidinput); diff -ur linux-2.6.23.orig/drivers/hid/usbhid/hid-quirks.c linux-2.6.23/drivers/hid/usbhid/hid-quirks.c --- linux-2.6.23.orig/drivers/hid/usbhid/hid-quirks.c 2007-12-06 23:39:40.000000000 +0000 +++ linux-2.6.23/drivers/hid/usbhid/hid-quirks.c 2007-12-06 23:41:38.000000000 +0000 @@ -82,6 +82,7 @@ #define USB_DEVICE_ID_CHERRY_CYMOTION 0x0023 #define USB_VENDOR_ID_CHIC 0x05fe +#define USB_DEVICE_ID_CHIC_BROWSER_MOUSE 0x0011 #define USB_DEVICE_ID_CHIC_GAMEPAD 0x0014 #define USB_VENDOR_ID_CIDC 0x1677 @@ -355,6 +356,7 @@ { USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU, HID_QUIRK_2WHEEL_MOUSE_HACK_7 }, { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE, HID_QUIRK_2WHEEL_MOUSE_HACK_5 }, + { USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_BROWSER_MOUSE, HID_QUIRK_CHIC_PSEUDOBUTTONS_HACK }, { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER, HID_QUIRK_BAD_RELATIVE_KEYS }, diff -ur linux-2.6.23.orig/include/linux/hid.h linux-2.6.23/include/linux/hid.h --- linux-2.6.23.orig/include/linux/hid.h 2007-12-06 23:39:49.000000000 +0000 +++ linux-2.6.23/include/linux/hid.h 2007-12-06 23:43:15.000000000 +0000 @@ -276,6 +276,7 @@ #define HID_QUIRK_HIDINPUT 0x00200000 #define HID_QUIRK_LOGITECH_IGNORE_DOUBLED_WHEEL 0x00400000 #define HID_QUIRK_LOGITECH_EXPANDED_KEYMAP 0x00800000 +#define HID_QUIRK_CHIC_PSEUDOBUTTONS_HACK 0x01000000 /* * Separate quirks for runtime report descriptor fixup @@ -462,6 +463,16 @@ unsigned long pb_pressed_fn[NBITS(KEY_MAX)]; unsigned long pb_pressed_numlock[NBITS(KEY_MAX)]; #endif + + /* For quirks where one physical button looks like (at least) two other physical buttons */ + __u16 btn_state, btn_prev_state; /* Button map data */ + __u32 btn_quirks; /* Which of the extra buttons are active */ + + struct { + __u16 type, button; + __s16 value; + struct timer_list timer; + } repeat; }; #define HID_GLOBAL_STACK_SIZE 4 @@ -504,6 +515,7 @@ extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct hid_usage *, __s32); extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report); +extern void hidinput_hid_quirks(struct hid_device *, struct hid_field *); extern int hidinput_connect(struct hid_device *); extern void hidinput_disconnect(struct hid_device *);