Linux input子系統 (2)
Linux input子系統一個很重要的特性是它提供了 event interface。它通過字元裝置節點對使用者空間匯出了原生event,允許使用者程式操作任何 event,不會遺失任何資訊。
查詢 event interface版本
使用 EVIOCGVERSION ioctl function。引數是 32位 int型別,代表 major version (two high bytes), minor version (third byte), patch level (low byte)。 Listing 1顯示了使用 EVIOCGVERSION的例子:第 1個引數是event device node的開啟檔案描述符。你需要傳遞一個指向
Listing 1. Sample EVIOCGVERSION Function/* ioctl() accesses the underlying driver */ if (ioctl(fd, EVIOCGVERSION, &version)) { perror("evdev ioctl"); } /* the EVIOCGVERSION ioctl() returns an int */ /* so we unpack it and display it */ printf("evdev driver version is %d.%d.%d/n", version >> 16, (version >> 8) & 0xff, version & 0xff); |
查詢裝置身份資訊
event interface支援獲取裝置的身份資訊,使用 EVIOCGID ioctl function。引數是指向 input_id資料結構的指標。 input_id資料結構定義如 listing 2所示。 _u16資料型別是 16為無符號整型。
Figure 2. input_id Structure Definitionsstruct input_id { __u16 bustype; __u16 vendor; __u16 product; __u16 version; }; |
bustype 域包含了你需要的準確的資料資訊。你應把它當作是一個非透明的列舉型別,需要與通過
Listing 3. Sample EVIOCGID ioctl/* suck out some device information */ if(ioctl(fd, EVIOCGID, &device_info)) { perror("evdev ioctl"); } /* the EVIOCGID ioctl() returns input_devinfo * structure - see <linux/input.h> * So we work through the various elements, * displaying each of them */ printf("vendor %04hx product %04hx version %04hx", device_info.vendor, device_info.product, device_info.version); switch ( device_info.bustype) { case BUS_PCI : printf(" is on a PCI bus/n"); break; case BUS_USB : printf(" is on a Universal Serial Bus/n"); break; ... |
除了 bus type/vendor/product/version等裝置資訊,某些裝置還會提供一個有意義的名字字串,使用EVIOCGNAME獲取。如果名字串太長,則在返回的引數裡被擷取。 Listing 4顯示了它的使用的例子。
Listing 4. Example Truncated Stringint fd = -1; char name[256]= "Unknown"; if ((fd = open(argv[1], O_RDONLY)) < 0) { perror("evdev open"); exit(1); } if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { perror("evdev ioctl"); } printf("The device on %s says its name is %s/n", argv[1], name); close(fd); |
下面是上述例子執行的結果樣例:
The device on /dev/input/event0 says its name is Logitech USB-PS/2 Optical Mouse
儘管裝置身份資訊和名字資訊通常很有用,但是它也許並沒有提供足夠的資訊告訴你當前在使用哪個裝置。例如,你當前有兩個完全相同的遙控杆,你需要確定每個使用哪個埠。這通常屬於拓撲資訊( topology information),可以使用 EVIOCGPHYS ioctl獲取。它返回一串字串(或者一個負值錯誤碼)。 Listing 5顯示了它的使用的例子。
Listing 5. Using EVIOCGPHYS for Topology Informationif(ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys) < 0) { perror("event ioctl"); } printf("The device on %s says its path is %s/n", argv[1], phys); |
它的執行結果樣例如下所示:
The device on /dev/input/event0 says its path is usb-00:01.2-2.1/input0
為了瞭解輸出的含義,你需要將其分成幾部分。 Usb部分意味著這使用 usb系統的一個物理拓撲。 00:01.2是usb host controller所在的 pci bus information (bus 0, slot 1, function 2)。 2.1表示了從 root hub到 device的路徑,這裡表示上行 hub接在 root hub的第 2個埠上,裝置接在上行 hub的第 1個埠上。 Input0表示這是裝置的第 1個 event device 節點。大部分裝置只有一個 event device節點,但是有些裝置例外。比如多媒體鍵盤,它有一個 event device節點用於 normal keyboard,另一個 event device節點用於 multimedia keyboard。拓撲示例如下圖表示。
如果你在同一根連線線上將一個裝置還成另外一個同樣的裝置,你無法區分裝置更換,除非每一個裝置有一個唯一號,比如 serial number。使用 EVIOCGUNIQ可以獲取。 Listing 6是其示例。絕大多數裝置沒有這樣的唯一號,所以你使用該 ioctl將返回一個空字串。
Listing 6. Finding a Unique Identifierif(ioctl(fd, EVIOCGUNIQ(sizeof(uniq)), uniq) < 0) { perror("event ioctl"); } printf("The device on %s says its identity is %s/n", argv[1], uniq); |
確定裝置能力和特性
對於一些裝置,也許知道裝置的身份資訊就足夠了,因為它允許你根據裝置的使用情況處理裝置的任何 case。但是這總做法的尺度不好。比如,你有一個裝置僅有一個滑輪,你想使能處理滑輪的 handler,但是你並不想在code裡列出每個帶有滑輪的滑鼠的 vendor/product資訊。為此, event interface允許你對於某個裝置確定有哪些功能和特性。 Event interface支援的 feature types有:
-
EV_KEY: absolute binary results, such as keys and buttons.
-
EV_REL: relative results, such as the axes on a mouse.
-
EV_ABS: absolute integer results, such as the axes on a joystick or for a tablet.
-
EV_MSC: miscellaneous uses that didn't fit anywhere else.
-
EV_LED: LEDs and similar indications.
-
EV_SND: sound output, such as buzzers.
-
EV_REP: enables autorepeat of keys in the input core.
-
EV_FF: sends force-feedback effects to a device.
-
EV_FF_STATUS: device reporting of force-feedback effects back to the host.
-
EV_PWR: power management events
這些僅僅是 type features。每個 type feature包含了很大範圍的不同的個體 feature。例如, EV_REL type區別了 x軸, y軸, z軸,橫輪,豎輪。同樣, EV_KEY type包含了成千上百個 keys和 buttons。
使用 EVIOCGBIT ioctl可以獲取裝置的能力和特性。它告知你裝置是否有 key或者 button。
EVIOCGBIT ioctl處理 4個引數 ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是個特殊 case,表示返回裝置支援的所有的 type features)。 max_bytes表示返回的最大位元組數。bitfield域是指向儲存結果的記憶體指標。 return value表示儲存結果的實際位元組數,如果呼叫失敗,則返回負值。Listing 7展現了其使用示例,測試了 /dev/input/event0裝置節點支援哪些 type feature。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <linux/input.h> #define BITS_PER_LONG 32 #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) static int test_bit(int nr, const volatile unsigned long *addr) { return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))); } int main(int argc, char ** argv) { int fd; unsigned long *evtype_b = malloc(sizeof(int)); int yalv; if ((fd = open(argv[1], O_RDONLY)) < 0) { perror("evdev open"); exit(1); } memset(evtype_b, 0, sizeof(evtype_b)); if (ioctl(fd, EVIOCGBIT(0, EV_MAX), evtype_b) < 0) { perror("evdev ioctl"); } printf("Supported event types:/n"); for (yalv = 0; yalv < EV_MAX; yalv++) { if (test_bit(yalv, evtype_b)) { /* the bit is set in the event types list */ printf(" Event type 0x%02x ", yalv); switch ( yalv) { case EV_SYN : printf(" (Synch Events)/n"); break; case EV_KEY : printf(" (Keys or Buttons)/n"); break; case EV_REL : printf(" (Relative Axes)/n"); break; case EV_ABS : printf(" (Absolute Axes)/n"); break; case EV_MSC : printf(" (Miscellaneous)/n"); break; case EV_LED : printf(" (LEDs)/n"); break; case EV_SND : printf(" (Sounds)/n"); break; case EV_REP : printf(" (Repeat)/n"); break; case EV_FF : case EV_FF_STATUS: printf(" (Force Feedback)/n"); break; case EV_PWR: printf(" (Power Management)/n"); break; default: printf(" (Unknown: 0x%04hx)/n", yalv); } } } close(fd); } |
這個例子使用了 evtype_bit掩碼 EV_MAX(在 <linux/input.h>裡定義),覺得 bit array需要多少記憶體。當 ioctl提交後,由 event layer填充 bit array。我們測試 bit array裡的每一個 bit,來確定裝置支援哪些 feature type。所有的裝置都支援 EV_SYNC,該 bit由 input core來設定。
下面是執行結果的樣例:
如果是 keyboard,則
Supported event types: Event type 0x00 (Synchronization Events) Event type 0x01 (Keys or Buttons) Event type 0x11 (LEDs) Event type 0x14 (Repeat |
如果是 mouse,則
Supported event types: Event type 0x00 (Synchronization Events) Event type 0x01 (Keys or Buttons) Event type 0x02 (Relative Axes) |
獲取裝置 (from or to)的 input
當確定了裝置的能力和特性後,你就知道了裝置的事件型別。
獲取裝置的 event通過 char裝置的 read function。當你從 event device (例如 /dev/input/event0)裡讀取event時,你將獲得一系列 events,每個 event由 input_event結構表示。
Listing 8示例展示了讀取一個開啟檔案描述符 fd的事件。它過濾了不屬於 key的事件,並列印 input_event結構裡的每個域。
Listing 8. Checking for Busy Spots/* how many bytes were read */ size_t rb; /* the events (up to 64 at once) */ struct input_event ev[64]; rb=read(fd,ev,sizeof(struct input_event)*64); if (rb < (int) sizeof(struct input_event)) { perror("evtest: short read"); exit (1); } for (yalv = 0; yalv < (int) (rb / sizeof(struct input_event)); yalv++) { if (EV_KEY == ev[yalv].type) printf("%ld.%06ld ", ev[yalv].time.tv_sec, ev[yalv].time.tv_usec, printf("type %d code %d value %d/n", ev[yalv].type, ev[yalv].code, ev[yalv].value); } |
下面是執行的結果樣例:
Event: time 1033621164.003838, type 1, code 37, value 1 Event: time 1033621164.027829, type 1, code 38, value 0 Event: time 1033621164.139813, type 1, code 38, value 1 Event: time 1033621164.147807, type 1, code 37, value 0 Event: time 1033621164.259790, type 1, code 38, value 0 Event: time 1033621164.283772, type 1, code 36, value 1 Event: time 1033621164.419761, type 1, code 36, value 0 Event: time 1033621164.691710, type 1, code 14, value 1 Event: time 1033621164.795691, type 1, code 14, value 0 |
對於每個 key,你會獲得一個 key press和一個 key depress的 event。
這是 char裝置的標準 read介面,所以你不需要在程式裡 busy loop。此外,如果你想同時獲得許多裝置的input事件時,使用 poll/select函式。
給裝置傳送資訊,使用 char裝置的標準 write函式,傳送的資料必須是 input_event資料結構。
Listing 9展示了簡單的給裝置寫資料的示例。這個例子先讓 Caps Lock LED開啟,等 200毫秒,然後讓 Caps Lock LED關閉;然後讓 Num Lock LED開啟,等 200毫秒,然後讓 Num Lock LED關閉。不斷迴圈這個過程,你會看到鍵盤上的指示燈交替閃爍。
Listing 9. Sample Data Write Functionstruct input_event ev; /* the event */ /* we turn off all the LEDs to start */ ev.type = EV_LED; ev.code = LED_CAPSL; ev.value = 0; retval = write(fd, &ev, sizeof(struct input_event)); ev.code = LED_NUML; retval = write(fd, &ev, sizeof(struct input_event)); ev.code = LED_SCROLLL; retval = write(fd, &ev, sizeof(struct input_event)); while (1) { ev.code = LED_CAPSL; ev.value = 1; write(fd, &ev, sizeof(struct input_event)); usleep(200000); ev.value = 0; write(fd, &ev, sizeof(struct input_event)); ev.code = LED_NUML; ev.value = 1; write(fd, &ev, sizeof(struct input_event)); usleep(200000); ev.value = 0; write(fd, &ev, sizeof(struct input_event)); } |
現在我們清楚的知道如何接收或者傳送一個事件 ---key按下 /擡起,滑鼠移動等等。對於一些程式,可能還需要知道裝置的一些全域性狀態。比如,一個管理 keyboard的程式需要知道當前的哪些指示燈在亮,哪些鍵被釋放。
EVIOCGKEY ioctl用於確定一個裝置的全域性 key/button狀態,它在 bit array裡設定了每個 key/button是否被釋放。 Listing 10展示了該示例。
Listing 10. Determining a Device's Global Key and ButtonState uint8_t key_b[KEY_MAX/8 + 1]; memset(key_b, 0, sizeof(key_b)); ioctl(fd, EVIOCGKEY(sizeof(key_b)), key_b); for (yalv = 0; yalv < KEY_MAX; yalv++) { if (test_bit(yalv, key_b)) { /* the bit is set in the key state */ printf(" Key 0x%02x ", yalv); switch ( yalv) { case KEY_RESERVED : printf(" (Reserved)/n"); break; case KEY_ESC : printf(" (Escape)/n"); break; /* other keys / buttons not shown */ case BTN_STYLUS2 : printf(" (2nd Stylus Button )/n"); break; default: printf(" (Unknown key)/n"); } } } |
EVIOCGLED和 EVIOCGSND ioctl與 EVIOCGKEY類似,分別表示當前 LED亮燈和聲音通道開啟。 Listing 11展示了 EVIOCGLED的使用。
Listing 11. Using EVIOCGLEDmemset(led_b, 0, sizeof(led_b)); ioctl(fd, EVIOCGLED(sizeof(led_b)), led_b); for (yalv = 0; yalv < LED_MAX; yalv++) { if (test_bit(yalv, led_b)) { /* the bit is set in the LED state */ printf(" LED 0x%02x ", yalv); switch ( yalv) { case LED_NUML : printf(" (Num Lock)/n"); break; case LED_CAPSL : printf(" (Caps Lock)/n"); break; /* other LEDs not shown here*/ default: printf(" (Unknown LED: 0x%04hx)/n", yalv); } } } |
使用 EVIOCGREP ioctl來獲取鍵盤的重複速率。 Listing 12展示了該示例。(重複速率指你按下鍵後,輸出的事件的次數。例如,按下 1鍵且不釋放, console裡會輸出多個 1的速率)
Listing 12. Checking the Repeat Rate Settingsint rep[2]; if(ioctl(fd, EVIOCGREP, rep)) { perror("evdev ioctl"); } printf("[0]= %d, [1] = %d/n", rep[0], rep[1]); |
其中, rep[0]表示在按鍵重複出現之前 delay的時間; rep[1]表示按鍵重複出現的時間間隔。
使用 EVIOCSREP ioctl來設定鍵盤的重複速率。 Listing 13展示了該示例。
Listing 13. Setting the Repeat Ratesint rep[2]; rep[0] = 2500; rep[1] = 1000; if(ioctl(fd, EVIOCSREP, rep)) { perror("evdev ioctl"); } |
rep[0]/rep[1]的含義同 Listing 12。
有些 input driver支援 key mapping。使用 EVIOCGKEYCODE ioctl獲取一個 key對應的 scancode。 Listing 14/Listing 15展示了 key mapping的示例。注意有些鍵盤驅動並不支援這個特性(比如 USB鍵盤)。如果你想修改 key mapping,使用 EVIOCSKEYCODE ioctl即可。
Listing 14. Looping over Scancodesint codes[2]; for (i=0; i<130; i++) { codes[0] = i; if(ioctl(fd, EVIOCGKEYCODE, codes)) { perror("evdev ioctl"); } printf("[0]= %d, [1] = %d/n", codes[0], codes[1]); } |
Listing 15. Mapping Keysint codes[2]; codes[0] = 58; /* M keycap */ codes[1] = 49; /* assign to N */ if(ioctl(fd, EVIOCSKEYCODE, codes)) { perror("evdev ioctl"); } |
使用 EVIOCGABS ioctl提供絕對座標軸裝置的狀態資訊。它為每一個絕對座標軸提供了一個 input_absinfo資料結構(參考 Listing 16)。如果你想檢視裝置的全域性狀態,對每一個座標軸呼叫 EVIOCGABS ioctl函式。 Listing 17展示了該示例。
Listing 16. input_absinfo for an Absolute Axisstruct input_absinfo { __s32 value; // current value of the axis __s32 minimum; // current limits of the axis __s32 maximum; // current limits of the axis __s32 fuzz; __s32 flat; }; |
Listing 17. Checking Global State by Axisuint8_t abs_b[ABS_MAX/8 + 1]; struct input_absinfo abs_feat; ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_b)), abs_b); printf("Supported Absolute axes:/n"); for (yalv = 0; yalv < ABS_MAX; yalv++) { if (test_bit(yalv, abs_b)) { printf(" Absolute axis 0x%02x ", yalv); switch ( yalv) { case ABS_X : printf("(X Axis) "); break; case ABS_Y : printf("(Y Axis) "); break; default: printf("(Unknown abs feature)"); } if(ioctl(fd, EVIOCGABS(yalv), &abs_feat)) { perror("evdev EVIOCGABS ioctl"); } printf("%d (min:%d max:%d flat:%d fuzz:%d)", abs_feat.value, abs_feat.minimum, abs_feat.maximum, abs_feat.flat, abs_feat.fuzz); printf("/n"); } } |
Force Feedback (力回饋)
有三個 ioctl用於控制 force-feedback裝置: EVIOCSFF, EVIOCRMFF, EVIOCGEFFECT。這三個 ioctl分別表示傳送一個 force-feedback effect,刪除一個 force-feedback effect,獲取當前多少個 effect在同時使用。