输入设备
输入设备通常包括鼠标、触摸屏和键盘设备,当然还有如摇杆、写字板等特殊功能的输入设备。SylixOS 对支持的输入设备的定义位于 SylixOS/system/device/input/目录下,当前仅定义了鼠标和键盘两类,分别位于 <mouse.h> 和 <keyboard.h>。
鼠标设备
鼠标设备驱动通过报告一个 mouse_event_notify 结构的数据向系统通知一个鼠标事件的发生,该结构的定义如下:
typedef struct mouse_event_notify {
int32_t ctype; /* coordinate type */
int32_t kstat; /* mouse button stat */
int32_t wscroll[MOUSE_MAX_WHEEL]; /* wheel scroll */
int32_t xmovement;
int32_t ymovement;
/*
* if use absolutely coordinate (such as touch screen)
* if you use touch screen:
* (kstat & MOUSE_LEFT) != 0 (press)
* (kstat & MOUSE_LEFT) == 0 (release)
*/
#define xanalog xmovement /* analog samples values */
#define yanalog ymovement
} mouse_event_notify;
ctype 用以区分设备的坐标类型,可为 MOUSE_CTYPE_REL 或 MOUSE_CTYPE_ABS,即相对坐标(一般鼠标设备)或绝对坐标(触摸屏设备)。
kstat 用于标识鼠标按键状态,包括左键、中键、右键,此外还定义了额外的按键状态,用于满足特殊鼠标(比如游戏鼠标)的应用。每一个按键状态用一个数据位表示,0 表示弹起,1 表示按下。按键状态的定义如下:
按键状态名称 | 解释 |
---|---|
MOUSE_LEFT | 鼠标左键 |
MOUSE_RIGHT | 鼠标右键 |
MOUSE_MIDDLE | 鼠标中键 |
MOUSE_BUTTON4 ~ MOUSE_BUTTON7 | 额外的 4 个预定义按键 |
当设备为触摸屏时,将鼠标左键状态位用来表示其弹起和按下的状态信息,正如上段程序中所注释的那样。
xmovement 和 ymovement 表示鼠标的相对位移值。当为绝对坐标时,系统建议程序使用 xanalog 和 yanalog(虽然目前他们与 xmovement 和 ymovement 是同一个成员变量),以使程序更加直观易读。
SylixOS 中,鼠标设备名称为 /dev/input/xmse0、/dev/input/xmse1 等,触摸屏设备名称为/dev/input/touch0、/dev/input/touch1 等。下面将通过读取鼠标设备事件展示其一般操作方法,触摸屏设备的操作方法与此相似。
#include <stdio.h>
#include <fcntl.h>
#include <mouse.h>
#define MOUSE_DEV_NAME "/dev/input/xmse"
#define MOUSE_READ_CNT 50
int main(int argc, char *argv[])
{
int mse_fd;
mouse_event_notify mse_event;
ssize_t read_len;
int read_cnt = 0;
mse_fd = open(MOUSE_DEV_NAME, O_RDONLY);
if (mse_fd < 0) {
fprintf(stderr, "open %s failed.\n", MOUSE_DEV_NAME);
return (-1);
}
while (read_cnt++ < MOUSE_READ_CNT) {
read_len = read(mse_fd, (void *)&mse_event, sizeof(mouse_event_notify));
if (read_len < 0) {
fprintf(stderr, "read mouse event error, abort.\n");
break;
}
if (read_len < sizeof(mouse_event_notify)) {
fprintf(stderr, "read mouse event invalid, continue.\n");
continue;
}
fprintf(stdout, "mouse report [%d] >>\n", read_cnt);
fprintf(stdout, "key : ");
if (mse_event.kstat & MOUSE_LEFT) {
fprintf(stdout, "left ");
}
if (mse_event.kstat & MOUSE_RIGHT) {
fprintf(stdout, "right ");
}
if (mse_event.kstat & MOUSE_MIDDLE) {
fprintf(stdout, "middle ");
}
if (mse_event.kstat == 0) {
fprintf(stdout, "none");
}
fprintf(stdout, "\n");
fprintf(stdout, "move : x: %d, y: %d\n", mse_event.xmovement,
mse_event.ymovement);
fprintf(stdout, "wheel: %d[%s]\n", mse_event.wscroll[0],
mse_event.wscroll[0] == 0 ? "none" :
mse_event.wscroll[0] > 0 ? "up" : "down");
fprintf(stdout, "\n");
}
close(mse_fd);
return (0);
}
上面的程序中,将读取最多 50 次鼠标事件,运行程序后,将可能打印下面的信息:
mouse report [2] >>
key : left right middle
move : x: -1, y: 3
wheel: 1[up]
mouse report [28] >>
key : left right middle
move : x: 4, y: 1
wheel: -1[down]
上面的结构需要这样的操作:同时按下鼠标左、中(滑轮)、右三个按键,在移动鼠标的过程中,向上或向下滚动滑轮。这说明,一个鼠标事件可同时传递多个信息。如果是触摸屏设备,则不存在按键的信息,只有按下或弹起的状态。
当系统中存在多个鼠标设备(多个 USB 设备或触摸屏同时存在)时,应用程序无需分别处理这多个设备的事件。这是因为 SylixOS 提供了一个名叫 xinput.ko 的标准内核模块。当注册该模块后,将创建两个设备,分别为 /dev/input/xmse 和 /dev/input/xkbd,分别收集系统中所有的鼠标和键盘事件,应用程序则只需要读取这两个设备即可。对于 xmse 设备,由于存在一般鼠标消息和触摸屏消息,因此,应用程序需要分别处理。xmse 设备的一般操作方式如下:
mse_fd = open(/dev/input/xmse, O_RDONLY);
read(mse_fd, &mse_event, ...);
if (mse_event.ctype == MOUSE_CTYPE_REL) {
/*
* 处理一般鼠标事件
*/
} else {
/*
* 处理触摸屏事件
*/
}
键盘设备
键盘设备驱动通过报告一个 keyboard_event_notify 结构的数据向系统通知一个键盘事件的发生,该结构的定义如下:
typedef struct keyboard_event_notify {
int32_t nmsg; /* message num, usually one msg*/
int32_t type; /* press or release */
int32_t ledstate; /* LED stat */
int32_t fkstat; /* func-key stat */
int32_t keymsg[KE_MAX_KEY_ST]; /* key code */
} keyboard_event_notify;
type 的值为 KE_PRESS 或 KE_RELEASE,分别表示按下或弹起。
ledstate 用以表示带有 LED 指示灯的按键状态,如果对应的位为 0,表示 LED 按键处于打开状态,反之则处于关闭状态。在打开状态下,通常键盘驱动程序会将相应的 LED 指示灯打开,这些按键的状态位定义如下:
状态位名称 | 解释 |
---|---|
KE_LED_NUMLOCK | 用于标识数字小键盘是否打开 |
KE_LED_CAPSLOCK | 用于标识字母大写状态是否使能 |
KE_LED_SCROLLLOCK | 用于标识滚动锁定状态是否使能 |
fkstate 用以表示功能按键的状态,如果对应的位为 0,表示功能键按下,反之则没有按下。所有功能键的状态位定义如下:
状态位名称 | 解释 |
---|---|
KE_FK_CTRL | 用于标识左 Ctrl 键是否按下 |
KE_FK_ALT | 用于标识左 Alt 键是否按下 |
KE_FK_SHIFT | 用于标识左 Shift 键是否按下 |
KE_FK_CTRLR | 用于标识右 Ctrl 键是否按下 |
KE_FK_ALTR | 用于标识右 Alt 键是否按下 |
KE_FK_SHIFTR | 用于标识右 Shift 键是否按下 |
nmsg 与 keymsg 用以表示除 LED 键、功能键之外的所有普通按键的代码。宏 KE_MAX_KEY_ST 当前定义为 8,这说明系统允许键盘驱动程序一次报告最多 8 个普通按键信息。需要注意的是,字母按键的大小写逻辑不由驱动程序处理,比如按下 Shift 键后,再按下字母键 A,keymsg 里面的代码将是字符‘a’,同时 fkstate 里面的 KE_FK_SHFT 状态位为 1。在使能 capslock 的情况下,keymsg 里面的代码仍然是‘a’,只是 ledstate 里面的 KE_LED_CAPSLOCK 状态位为 1。因此,应用程序需要根据这些信息将字符进行适当的大小写转换。其他一些受 Shift 状态影响的按键,比如主键盘上的数字键 1~9,当按下 shift 时,keymsg 里面的代码则相应地为“!、@、#”等。
下面的程序展示了通过 /dev/input/xkbd 设备处理键盘消息的一般方法。
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <keyboard.h>
#define KBD_DEV_NAME "/dev/input/xkbd"
#define KBD_READ_CNT 50
int main(int argc, char *argv[])
{
int kbd_fd;
keyboard_event_notify kbd_event;
ssize_t read_len;
int read_cnt = 0;
int32_t keymsg;
int i;
kbd_fd = open(KBD_DEV_NAME, O_RDONLY);
if (kbd_fd < 0) {
fprintf(stderr, "open %s failed.\n", KBD_DEV_NAME);
return (-1);
}
while (read_cnt++ < KBD_READ_CNT) {
read_len = read(kbd_fd, (void *)&kbd_event,
sizeof(keyboard_event_notify));
if (read_len < 0) {
fprintf(stderr, "read keyboard event error, abort.\n");
break;
}
if (read_len < sizeof(keyboard_event_notify)) {
fprintf(stderr, "read keyboard event invalid, continue.\n");
continue;
}
fprintf(stdout, "keyboard report [%d] >>\n", read_cnt);
for (i = 0; i < kbd_event.nmsg; i++) {
keymsg = kbd_event.keymsg[i];
fprintf(stdout, "key code: <%3d [%c] (%s)> ",
keymsg,
isprint(keymsg) ? keymsg : ' ',
kbd_event.type == KE_PRESS ? "press" : "release");
}
fprintf(stdout, "\nled sta : ");
if (kbd_event.ledstate == 0) {
printf("none");
}
if (kbd_event.ledstate & KE_LED_NUMLOCK) {
fprintf(stdout, "numlock ");
}
if (kbd_event.ledstate & KE_LED_CAPSLOCK) {
fprintf(stdout, "capslock ");
}
if (kbd_event.ledstate & KE_LED_SCROLLLOCK) {
fprintf(stdout, "scrollock ");
}
fprintf(stdout, "\nfunc key: ");
if (kbd_event.fkstat == 0) {
fprintf(stdout, "none");
}
if (kbd_event.fkstat & KE_FK_CTRL) {
fprintf(stdout, "left-ctrl ");
}
if (kbd_event.fkstat & KE_FK_ALT) {
printf("left-alt ");
}
if (kbd_event.fkstat & KE_FK_SHIFT) {
fprintf(stdout, "left-shift ");
}
if (kbd_event.fkstat & KE_FK_CTRLR) {
fprintf(stdout, "right-ctrl ");
}
if (kbd_event.fkstat & KE_FK_ALTR) {
fprintf(stdout, "right-alt ");
}
if (kbd_event.fkstat & KE_FK_SHIFTR) {
fprintf(stdout, "right-shift ");
}
fprintf(stdout, "\n\n");
}
close(kbd_fd);
return (0);
}
以上程序仅仅只是将所获得的原始的键盘消息打印出来,并未作任何转换处理,读者可以使用此程序测试一下在不同按键组合的情况下消息内容之间的差异。通常情况下,应用程序不会直接处理输入设备的原始消息,而是由 GUI 层分析并处理,将其转换为更高层次的消息描述,供程序使用。