Skip to contentSkip to conversation

usbhid: Driver for microsoft natural ergonomic keyboard 4000

PATCH In review liyu 4 participants 8 messages Jul 26, 2006 – Jul 31, 2006 USB

lore
Share & tools
lore

liyu

author
#0 lore raw

PATCH 1/3 usbhid: Driver for microsoft natural ergonomic keyboard 4000

This driver use "HID device simple driver interface", you must
install that patch first. This new version get some improvements.

The patch is core of driver.

I am sorry for sendding patches in the attachment, beacause of my mail client always break TAB into some spaces.

Good luck

-Liyu

First, this driver use "HID device simple driver interface", you must
apply that patch before use me.

As Greg KH words, I split this driver into some more littler patches, and
break some long line in source.

This new version get some improvements:

1. These five "My Favorites" keys map FN_F{1,2,3,4,5}_KEY directly,
instead of map them to KEY_VENDOR hacking means.

2. Support left paren key "(", right paren key ")", equal key "=" on
right-top keypad. In fact, this keyboard generate KEYPAD_XXX usage code for them, but I find many applications can not handle them on default configuration, especially X.org. To get the most best usability, I use a bit magic here: map them to "Shift+9" and "Shift+0".

This patch is main part of driver.

First, this driver use "HID device simple driver interface", you must apply that patch before use me.

drivers/usb/input/usbnek4k.c renamed from drivers/usb/input.orig/usbnek4k.c +420 -0
diff -Naurp linux-2.6.17.6/drivers/usb/input.orig/usbnek4k.c linux-2.6.17.6/drivers/usb/input/usbnek4k.c
@@ -0,0 +1,420 @@
+/*
+ *  Microsoft Natural Ergonomic Keyboard 4000 Driver
+ *
+ *  Version:	0.2.0
+ *
+ *  TODO:	
+ *	Refactoring to escape from switch {case ...} hell.
+ *
+ *  Copyright (c) 2006 Liyu <[email protected]> 	
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include "hid.h"
+#include "hid-simple.h"
+
+#define map_key(c) \
+	do { \
+		usage->code = c; \
+		usage->type = EV_KEY; \
+		set_bit(c,input->keybit); \
+	} while (0)
+
+#define clear_key(c) \
+	do { \
+		usage->code = 0; \
+		usage->type = 0; \
+		clear_bit(c,input->keybit); \
+	} while (0)
+
+#define clear_usage(c)	\
+	do { \
+		usage->code = 0; \
+		usage->type = 0; \
+	} while (0)
+
+#define LEFTP_PRESSED	0x1
+#define RIGHTP_PRESSED	0x2
+#define KPP_PRESSED_MASK	(LEFTP_PRESSED|RIGHTP_PRESSED)
+
+#define set_leftp_pressed(hiddev) \
+	set_bit(LEFTP_PRESSED, (unsigned long*)(&hiddev->simple->private))
+#define set_rightp_pressed(hiddev) \
+	set_bit(RIGHTP_PRESSED, (unsigned long*)(&hiddev->simple->private))
+#define clear_leftp_pressed(hiddev) \
+	clear_bit(LEFTP_PRESSED, (unsigned long*)(&hiddev->simple->private))
+#define clear_rightp_pressed(hiddev) \
+	clear_bit(RIGHTP_PRESSED, (unsigned long*)(&hiddev->simple->private))
+
+#define is_kpp_pressed(hiddev) \
+	((unsigned long)(hiddev->simple->private) & KPP_PRESSED_MASK)
+#define is_leftp_pressed(hiddev) \
+	((unsigned long)(hiddev->simple->private) & LEFTP_PRESSED)
+#define is_rightp_pressed(hiddev) \
+	((unsigned long)(hiddev->simple->private) & RIGHTP_PRESSED)
+
+
+
+#define USAGE_ZOOM_IN 0x22d
+#define USAGE_ZOOM_OUT 0x22e
+#define USAGE_HOME	0x223
+#define USAGE_SEARCH	0x221
+#define USAGE_EMAIL	0x18a
+#define USAGE_FAVORITES	0x182
+#define USAGE_MUTE	0xe2
+#define USAGE_VOLUME_DOWN	0xea
+#define USAGE_VOLUME_UP	0xe9
+#define USAGE_PLAY_PAUSE	0xcd
+#define USAGE_CALCULATOR	0x192
+#define USAGE_BACK	0x224
+#define USAGE_FORWARD	0x225
+#define USAGE_CUSTOM	0xff05
+
+#define USAGE_CUSTEM_1	0x1
+#define USAGE_CUSTEM_2	0x2
+#define USAGE_CUSTEM_3	0x4
+#define USAGE_CUSTEM_4	0x8
+#define USAGE_CUSTEM_5	0x10
+
+#define USAGE_HELP	0x95
+#define USAGE_UNDO	0x21a
+#define USAGE_REDO	0x279
+#define USAGE_NEW	0x201
+#define USAGE_OPEN	0x202
+#define USAGE_CLOSE	0x203
+
+#define USAGE_REPLY	0x289
+#define USAGE_FWD	0x28b
+#define USAGE_SEND	0x28c
+#define USAGE_SPELL	0x1ab
+#define USAGE_SAVE	0x207
+#define USAGE_PRINT	0x208
+
+#define USAGE_KEYPAD_EQUAL 0x67
+#define USAGE_KEYPAD_LEFT_PARENTHESES 0xb6
+#define USAGE_KEYPAD_RIGHT_PARENTHESES 0xb7
+
+
+#define MSNEK4K_ID_VENDOR	0x045e
+#define MSNEK4K_ID_PRODUCT	0x00db
+
+
+static struct usb_device_id __id[] = {
+	{
+		USB_DEVICE(MSNEK4K_ID_VENDOR, MSNEK4K_ID_PRODUCT)
+	},
+	{0, }
+};
+
+MODULE_DEVICE_TABLE(usb, __id);
+
+static char driver_name[] = "Microsoft Natural Ergonomic Keyboard 4000";
+
+static void nek4k_setup_usage(struct hid_field *field,	struct hid_usage *usage)
+{	
+	struct hid_input *hidinput = field->hidinput;
+	struct input_dev *input = hidinput->input;
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+		switch (usage->hid & HID_USAGE) {
+			case USAGE_ZOOM_IN:
+				map_key(KEY_F13);break;
+			case USAGE_ZOOM_OUT:
+				map_key(KEY_F14);break;
+			case USAGE_HOME:
+				map_key(KEY_HOMEPAGE);break;
+			case USAGE_SEARCH:
+				map_key(KEY_SEARCH);break;
+			case USAGE_EMAIL:
+				map_key(KEY_EMAIL);break;
+			case USAGE_FAVORITES:
+				map_key(KEY_FAVORITES);break;
+			case USAGE_MUTE:
+				map_key(KEY_MUTE);break;
+			case USAGE_VOLUME_DOWN:
+				map_key(KEY_VOLUMEDOWN);break;
+			case USAGE_VOLUME_UP:
+				map_key(KEY_VOLUMEUP);	break;
+			case USAGE_PLAY_PAUSE:
+				map_key(KEY_PLAYPAUSE);break;
+			case USAGE_CALCULATOR:
+				map_key(KEY_CALC);break;
+			case USAGE_BACK:
+				map_key(KEY_BACK);break;
+			case USAGE_FORWARD:
+				map_key(KEY_FORWARD);break;
+			case USAGE_HELP:
+				map_key(KEY_HELP);break;
+			case USAGE_UNDO:
+				map_key(KEY_UNDO);break;
+			case USAGE_REDO:
+				map_key(KEY_REDO);break;
+			case USAGE_NEW:
+				map_key(KEY_NEW);break;
+			case USAGE_OPEN:
+				map_key(KEY_OPEN);break;
+			case USAGE_CLOSE:
+				map_key(KEY_CLOSE);break;
+			case USAGE_REPLY:
+				map_key(KEY_REPLY);break;
+			case USAGE_FWD:
+				map_key(KEY_FORWARDMAIL);break;
+			case USAGE_SEND:
+				map_key(KEY_SEND);break;
+			case USAGE_SPELL:
+				map_key(KEY_F13);break;
+			case USAGE_SAVE:
+				map_key(KEY_SAVE);break;
+			case USAGE_PRINT:
+				map_key(KEY_PRINT);break;
+			default:	
+				return;
+		}
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) {
+		if ((usage->hid & HID_USAGE) == USAGE_CUSTOM) {
+			map_key(KEY_FN_F1);
+			map_key(KEY_FN_F2);
+			map_key(KEY_FN_F3);
+			map_key(KEY_FN_F4);
+			map_key(KEY_FN_F5);
+		}
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_KEYBOARD) {
+		switch (usage->hid & HID_USAGE) {
+			case USAGE_KEYPAD_EQUAL:
+				map_key(KEY_EQUAL);
+				break;
+			case USAGE_KEYPAD_LEFT_PARENTHESES:
+				map_key(KEY_9);
+				break;
+			case USAGE_KEYPAD_RIGHT_PARENTHESES:
+				map_key(KEY_0);
+				break;
+			default:
+				return;
+		}
+	}
+	else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_LED)
+		return;
+	else
+		printk(KERN_WARNING"Unknown usage page 0x%x in %s:%d\n",
+			usage->hid & HID_USAGE_PAGE, __FUNCTION__, __LINE__);
+	return;
+}
+
+static void nek4k_clear_usage(struct hid_field *field,	struct hid_usage *usage)
+{
+	struct hid_input *hidinput = field->hidinput;
+	struct input_dev *input = hidinput->input;
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+		switch (usage->hid & HID_USAGE) {
+			case USAGE_ZOOM_IN:
+				clear_key(KEY_F13);break;
+			case USAGE_ZOOM_OUT:
+				clear_key(KEY_F14);break;
+			case USAGE_HOME:
+				clear_key(KEY_HOMEPAGE);break;
+			case USAGE_SEARCH:
+				clear_key(KEY_SEARCH);break;
+			case USAGE_EMAIL:
+				clear_key(KEY_EMAIL);break;
+			case USAGE_FAVORITES:
+				clear_key(KEY_FAVORITES);break;
+			case USAGE_MUTE:
+				clear_key(KEY_MUTE);break;
+			case USAGE_VOLUME_DOWN:
+				clear_key(KEY_VOLUMEDOWN);break;
+			case USAGE_VOLUME_UP:
+				clear_key(KEY_VOLUMEUP);	break;
+			case USAGE_PLAY_PAUSE:
+				clear_key(KEY_PLAYPAUSE);break;
+			case USAGE_CALCULATOR:
+				clear_key(KEY_CALC);break;
+			case USAGE_BACK:
+				clear_key(KEY_BACK);break;
+			case USAGE_FORWARD:
+				clear_key(KEY_FORWARD);break;
+			case USAGE_HELP:
+				clear_key(KEY_HELP);break;
+			case USAGE_UNDO:
+				clear_key(KEY_UNDO);break;
+			case USAGE_REDO:
+				clear_key(KEY_REDO);break;
+			case USAGE_NEW:
+				clear_key(KEY_NEW);break;
+			case USAGE_OPEN:
+				clear_key(KEY_OPEN);break;
+			case USAGE_CLOSE:
+				clear_key(KEY_CLOSE);break;
+			case USAGE_REPLY:
+				clear_key(KEY_REPLY);break;
+			case USAGE_FWD:
+				clear_key(KEY_FORWARDMAIL);break;
+			case USAGE_SEND:
+				clear_key(KEY_SEND);break;
+			case USAGE_SPELL:
+				clear_key(KEY_F13);break;
+			case USAGE_SAVE:
+				clear_key(KEY_SAVE);break;
+			case USAGE_PRINT:
+				clear_key(KEY_PRINT);break;
+			default:	
+				return;
+		}
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) {
+		if ((usage->hid & HID_USAGE) == USAGE_CUSTOM) {
+			clear_bit(KEY_FN_F1,input->keybit);
+			clear_bit(KEY_FN_F2,input->keybit);
+			clear_bit(KEY_FN_F3,input->keybit);
+			clear_bit(KEY_FN_F4,input->keybit);
+			clear_bit(KEY_FN_F5,input->keybit);
+			return;
+		}
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_KEYBOARD) {
+		clear_usage(USAGE_EQUAL);
+		return;
+	} else 
+		printk(KERN_ERR"Unknown usage page 0x%x in %s:%d\n",
+			usage->hid & HID_USAGE_PAGE, __FUNCTION__, __LINE__);
+
+}
+
+static int
+nek4k_hid_event(const struct hid_device *hid, const struct hid_field *field,
+			const struct hid_usage * usage, const __s32 value,
+						const struct pt_regs *regs)
+{
+	struct hid_input *hidinput = field->hidinput;
+	struct input_dev *input = hidinput->input;
+	int code=0;
+
+	if ( (usage->hid&HID_USAGE_PAGE)==HID_UP_MSVENDOR &&
+	     (usage->hid&HID_USAGE) == USAGE_CUSTOM ) {
+		switch (value) {
+			case 0x0:
+				input_event(input, EV_KEY, code, 0);
+				input_sync(input);
+				return 0;
+			case USAGE_CUSTEM_1:	
+				code = KEY_FN_F1;
+				break;
+			case USAGE_CUSTEM_2:
+				code = KEY_FN_F2;
+				break;
+			case USAGE_CUSTEM_3:
+				code = KEY_FN_F3;
+				break;
+			case USAGE_CUSTEM_4:
+				code = KEY_FN_F4;
+				break;
+			case USAGE_CUSTEM_5:
+				code = KEY_FN_F5;
+				break;
+			default:
+				return -EINVAL;
+		};
+		input_event(input, EV_KEY, code, 1);
+		input_sync(input);
+	}
+	else if ( (usage->hid&HID_USAGE_PAGE) == HID_UP_KEYBOARD ) {
+		if ((usage->hid&HID_USAGE) == USAGE_KEYPAD_LEFT_PARENTHESES)
+			code = KEY_9;
+		else if ((usage->hid&HID_USAGE) == USAGE_KEYPAD_RIGHT_PARENTHESES)
+			code = KEY_0;
+		else 
+			return -EINVAL;
+		if (1 == value) {
+			if (!is_kpp_pressed(hid)) {
+				input_event(input, EV_KEY, KEY_LEFTSHIFT, 1);
+				input_sync(hidinput->input);
+			}
+			input_event(input, EV_KEY, code, 1);
+			input_sync(hidinput->input);
+			if (KEY_9 == code)
+				set_leftp_pressed(hid);
+			else if (KEY_0 == code)
+				set_rightp_pressed(hid);
+		} else if (0 == value) {
+			input_event(input, EV_KEY, code, 0);
+			input_sync(hidinput->input);
+			if (KEY_9 == code)
+				clear_leftp_pressed(hid);
+			else if (KEY_0 == code) 
+				clear_rightp_pressed(hid);
+			if (!is_kpp_pressed(hid)) {
+				input_event(input, EV_KEY, KEY_LEFTSHIFT, 0);
+				input_sync(input);
+			}
+		}
+	}
+	return 0;
+}
+
+static void nek4k_disconnect(struct hid_device *hid, struct hid_input *hidinput)
+{
+	if (is_leftp_pressed(hid)) {
+		input_event(hidinput->input, EV_KEY, KEY_9, 0);
+		input_sync(hidinput->input);
+		input_event(hidinput->input, EV_KEY, KEY_LEFTSHIFT, 0);
+	}
+	if (is_rightp_pressed(hid)) {
+		input_event(hidinput->input, EV_KEY, KEY_0, 0);
+		input_sync(hidinput->input);
+		input_event(hidinput->input, EV_KEY, KEY_LEFTSHIFT, 0);
+	}
+	input_sync(hidinput->input);
+}
+
+static struct usb_device_id my_id_table[] = {
+	{ USB_DEVICE(MSNEK4K_ID_VENDOR, MSNEK4K_ID_PRODUCT) },
+	{}
+};
+
+static struct hidinput_simple_driver nek4k_driver = {
+	.name = driver_name,
+	.setup_usage = nek4k_setup_usage,
+	.clear_usage = nek4k_clear_usage,
+	.pre_event = nek4k_hid_event,
+	.disconnect = nek4k_disconnect,
+	.id_table = my_id_table,
+	.private = NULL
+};
+
+static int __init nek4k_init(void)
+{
+	int result;
+
+	result = hidinput_register_simple_driver(&nek4k_driver);
+	switch (result) {
+		case 0:
+			goto ok;
+		case -ENOMEM:
+		case -EBUSY:
+			goto ok;
+		case -EINVAL:
+			return result;
+		default:
+			return -ENODEV;
+	};
+
+	ok:
+	return 0;
+}
+
+static void __exit nek4k_exit(void)
+{
+	hidinput_unregister_simple_driver(&nek4k_driver);
+}
+
+module_init(nek4k_init);
+module_exit(nek4k_exit);
+
+MODULE_LICENSE("GPL");
#1 lore raw

+#define map_key(c) \
+ do { \

+               usage->code = c; \
+               usage->type = EV_KEY; \
+               set_bit(c,input->keybit); \

+ } while (0)

I'm not quite sure where usage is coming from. Some magical global variable? Eeek.

Josef "Jeff" Sipek.

Signature · 2 lines
If I have trouble installing Linux, something is wrong. Very wrong.
		- Linus Torvalds
author
#2 lore raw
Quoted 7 lines · Josef Sipek
drivers/usb/input/usbnek4k.c renamed from drivers/usb/input.orig/usbnek4k.c +6 -0
+#define map_key(c) \
+       do { \
+               usage->code = c; \
+               usage->type = EV_KEY; \
+               set_bit(c,input->keybit); \
+       } while (0)
Quoted 6 lines

I'm not quite sure where usage is coming from. Some magical global variable? Eeek.

Josef "Jeff" Sipek.

These macroes like map_key() only use in nek4k_setup_usage() and ne4k_clear_usage(), so the
variable "usage" is coming from their parameter.

PS: these macroes are modifed from hid-input.c

#3 lore raw
Quoted 11 lines · liyu
Quoted 10 lines · Josef Sipek
drivers/usb/input/usbnek4k.c renamed from drivers/usb/input.orig/usbnek4k.c +6 -0
+#define map_key(c) \
+       do { \
+               usage->code = c; \
+               usage->type = EV_KEY; \
+               set_bit(c,input->keybit); \
+       } while (0)

I'm not quite sure where usage is coming from. Some magical global variable? Eeek.

Quoted 6 lines
Quoted 3 lines

Josef "Jeff" Sipek.

These macroes like map_key() only use in nek4k_setup_usage() and ne4k_clear_usage(), so the variable "usage" is coming from their parameter.

It is still bad.

Quoted 1 line

PS: these macroes are modifed from hid-input.c

Well, hid-input.c is wrong. :) Yeah, I just looked at it, and it is horid.

Josef "Jeff" Sipek.

Signature · 2 lines
My public GPG key can be found at
http://www.josefsipek.net/gpg/public-0xC7958FFE.txt
#4 lore raw
In reply to liyu

Hi!

Quoted 7 lines

This new version get some improvements:

2. Support left paren key "(", right paren key ")", equal key "=" on
right-top keypad. In fact, this keyboard generate KEYPAD_XXX usage code for them, but I find many applications can not handle them on default configuration, especially X.org. To get the most best usability, I use a bit magic here: map them to "Shift+9" and "Shift+0".

That is hardly 'improvement'. 'X is broken, so lets break input, too'.

Signature · 1 line
Thanks for all the (sleeping) penguins.
author
#5 lore raw

Pavel Machek Wrote:

Quoted 5 lines

Hi!

Quoted 2 lines

This new version get some improvements:

Quoted 12 lines
Quoted 6 lines

2. Support left paren key "(", right paren key ")", equal key "=" on
right-top keypad. In fact, this keyboard generate KEYPAD_XXX usage code for them, but I find many applications can not handle them on default configuration, especially X.org. To get the most best usability, I use a bit magic here: map them to "Shift+9" and "Shift+0".

That is hardly 'improvement'. 'X is broken, so lets break input, too'.

Well, however, this can work truly. If we do not hack as this way. Many applications can not get its input. I think the usability for most people should be first, but not follow rules.

I think we can add one module parameter like "shift_hack" to switch it ?!

#6 lore raw
Quoted 13 lines · liyu

Pavel Machek Wrote:

Quoted 12 lines

Hi!

Quoted 8 lines

This new version get some improvements:

2. Support left paren key "(", right paren key ")", equal key "=" on
right-top keypad. In fact, this keyboard generate KEYPAD_XXX usage code for them, but I find many applications can not handle them on default configuration, especially X.org. To get the most best usability, I use a bit magic here: map them to "Shift+9" and "Shift+0".

Quoted 9 lines
Quoted 5 lines

That is hardly 'improvement'. 'X is broken, so lets break input, too'.

Well, however, this can work truly. If we do not hack as this way. Many applications can not get its input. I think the usability for most people should be first, but not follow rules.

I do not quite understand why X would have issues with it. KEY_KPEQUAL, KEY_KPLEFTPAREN and KEY_KPRIGHTPAREN should work fine even with legacy X keyboard driver (one that is using PS/2 protocol instead of evdev). You might want to adjust your XKB map or use xmodmap, but kernel should report true keycodes.

Quoted 2 lines

I think we can add one module parameter like "shift_hack" to switch it ?!

No please don't.

Signature · 1 line
Dmitry
author
#7 lore raw

Well, I think you are right.

I reviewed the "Linux Device Driver", there are some follow words in
that book:

"the role of a device driver is providing mechanism, not policy."

It seem my hack break this rule obviously. I will "kcah" it back
soon.

Thanks.

Quoted 43 lines · Dmitry Torokhov
Quoted 21 lines · liyu

Pavel Machek Wrote:

Quoted 6 lines

Hi!

Quoted 2 lines

This new version get some improvements:

Quoted 13 lines
Quoted 7 lines

2. Support left paren key "(", right paren key ")", equal key "=" on
right-top keypad. In fact, this keyboard generate KEYPAD_XXX usage code for them, but I find many applications can not handle them on default configuration, especially X.org. To get the most best usability, I use a bit magic here: map them to "Shift+9" and "Shift+0".

That is hardly 'improvement'. 'X is broken, so lets break input, too'.

Quoted 5 lines

Well, however, this can work truly. If we do not hack as this way. Many applications can not get its input. I think the usability for most people should be first, but not follow rules.

I do not quite understand why X would have issues with it. KEY_KPEQUAL, KEY_KPLEFTPAREN and KEY_KPRIGHTPAREN should work fine even with legacy X keyboard driver (one that is using PS/2 protocol instead of evdev). You might want to adjust your XKB map or use xmodmap, but kernel should report true keycodes.

Quoted 3 lines

I think we can add one module parameter like "shift_hack" to switch it ?!

No please don't.