6.6. 编程接口¶
- 作者:
Ragnar Hojland Espinosa <ragnar@macula.net> - 1998 年 8 月 7 日
6.6.1. 简介¶
重要
本文档描述了旧版 js
接口。建议新客户端切换到通用事件 (evdev
) 接口。
1.0 驱动程序对操纵杆驱动程序采用了新的、基于事件的方法。操纵杆驱动程序现在只报告其状态的任何变化,而不是由用户程序轮询操纵杆值。更多信息请参阅操纵杆包中包含的编程接口、joystick.h 和 jstest.c。操纵杆设备可以在阻塞或非阻塞模式下使用,并支持 select() 调用。
为了向后兼容,旧版 (v0.x) 接口仍然包含在内。任何使用旧版接口对操纵杆驱动程序的调用都将返回与旧版接口兼容的值。此接口仍限于 2 个轴,使用它的应用程序通常只解码 2 个按钮,尽管驱动程序提供多达 32 个按钮。
6.6.2. 初始化¶
按照通常的语义(即使用 open)打开操纵杆设备。由于驱动程序现在报告事件而不是轮询变化,因此在打开后会立即发出一系列合成事件 (JS_EVENT_INIT),您可以读取这些事件以获取操纵杆的初始状态。
默认情况下,设备以阻塞模式打开
int fd = open ("/dev/input/js0", O_RDONLY);
6.6.3. 事件读取¶
struct js_event e;
read (fd, &e, sizeof(e));
其中 js_event 定义为
struct js_event {
__u32 time; /* event timestamp in milliseconds */
__s16 value; /* value */
__u8 type; /* event type */
__u8 number; /* axis/button number */
};
如果读取成功,它将返回 sizeof(e),除非您希望每次读取多个事件,如第 3.1 节所述。
6.6.3.1. js_event.type¶
type
的可能值是
#define JS_EVENT_BUTTON 0x01 /* button pressed/released */
#define JS_EVENT_AXIS 0x02 /* joystick moved */
#define JS_EVENT_INIT 0x80 /* initial state of device */
如上所述,驱动程序将在打开时发出合成的 JS_EVENT_INIT ORed 事件。也就是说,如果它发出一个 INIT BUTTON 事件,当前的 type 值将是
int type = JS_EVENT_BUTTON | JS_EVENT_INIT; /* 0x81 */
如果您选择不区分合成事件和真实事件,可以关闭 JS_EVENT_INIT 位
type &= ~JS_EVENT_INIT; /* 0x01 */
6.6.3.2. js_event.number¶
number
的值对应于生成事件的轴或按钮。请注意,它们具有单独的编号(也就是说,您有一个轴 0 和一个按钮 0)。通常,
轴
编号
第一轴 X
0
第一轴 Y
1
第二轴 X
2
第二轴 Y
3
……依此类推
方向帽因操纵杆类型而异。有些可以向 8 个方向移动,有些只能向 4 个方向移动。然而,驱动程序总是将方向帽报告为两个独立的轴,即使硬件不允许独立移动。
6.6.3.3. js_event.value¶
对于轴,value
是一个介于 -32767 和 +32767 之间的有符号整数,表示操纵杆沿该轴的位置。如果操纵杆处于 死区
时您没有读取到 0,或者它没有覆盖整个范围,则应重新校准它(例如使用 jscal)。
对于按钮,按下按钮事件的 value
为 1,释放按钮事件的 value
为 0。
尽管这
if (js_event.type == JS_EVENT_BUTTON) {
buttons_state ^= (1 << js_event.number);
}
如果您单独处理 JS_EVENT_INIT 事件,可能效果不错,
if ((js_event.type & ~JS_EVENT_INIT) == JS_EVENT_BUTTON) {
if (js_event.value)
buttons_state |= (1 << js_event.number);
else
buttons_state &= ~(1 << js_event.number);
}
则安全得多,因为它不会与驱动程序失去同步。由于您必须在第一个代码片段中为 JS_EVENT_INIT 事件编写单独的处理程序,因此这最终会更短。
6.6.3.4. js_event.time¶
事件生成的时间存储在 js_event.time
中。它是自过去某个时间以来的毫秒数。这简化了检测双击、判断轴移动和按钮按下是否同时发生等任务。
6.6.4. 读取¶
如果以阻塞模式打开设备,读取操作将永远阻塞(即等待),直到生成并有效读取事件。如果您无法承受永远等待(这确实是很长的时间;),则有两种替代方法:
使用 select 等待 fd 上有数据可读,或者直到超时。select(2) 手册页上有一个很好的示例。
以非阻塞模式 (O_NONBLOCK) 打开设备
6.6.4.1. O_NONBLOCK¶
如果在 O_NONBLOCK 模式下读取时 read 返回 -1,这不一定是“真实”错误(检查 errno(3));它可能只是意味着驱动程序队列中没有待读取的事件。您应该读取队列中的所有事件(即,直到您得到 -1)。
例如,
while (1) {
while (read (fd, &e, sizeof(e)) > 0) {
process_event (e);
}
/* EAGAIN is returned when the queue is empty */
if (errno != EAGAIN) {
/* error */
}
/* do something interesting with processed events */
}
清空队列的一个原因是,如果队列满了,您将开始丢失事件,因为队列是有限的,并且旧的事件将被覆盖。
另一个原因是您想知道所有发生的事情,而不是将处理推迟到以后。
为什么队列会满?因为您没有像前面提到的那样清空队列,或者因为两次读取之间的时间过长,产生了太多事件无法存储在队列中。请注意,高系统负载可能会进一步影响这些读取的间隔。
如果读取之间的时间足以填满队列并导致事件丢失,驱动程序将切换到启动模式,下次您读取时,将生成合成事件 (JS_EVENT_INIT) 以告知您操纵杆的实际状态。
注意
自 1.2.8 版本起,队列是循环的,能够容纳 64 个事件。您可以通过增加 joystick.h 中的 JS_BUFF_SIZE 并重新编译驱动程序来增加此大小。
在上面的代码中,您不妨使用典型的 read(2) 功能一次读取多个事件。为此,您可以将上面的 read 替换为类似以下内容:
struct js_event mybuffer[0xff];
int i = read (fd, mybuffer, sizeof(mybuffer));
在这种情况下,如果队列为空,read 将返回 -1;或者返回其他值,其中读取的事件数将是 i / sizeof(js_event)。同样,如果缓冲区已满,最好处理事件并继续读取,直到清空驱动程序队列。
6.6.5. IOCTLs¶
操纵杆驱动程序定义了以下 ioctl(2) 操作
/* function 3rd arg */
#define JSIOCGAXES /* get number of axes char */
#define JSIOCGBUTTONS /* get number of buttons char */
#define JSIOCGVERSION /* get driver version int */
#define JSIOCGNAME(len) /* get identifier string char */
#define JSIOCSCORR /* set correction values &js_corr */
#define JSIOCGCORR /* get correction values &js_corr */
例如,要读取轴的数量
char number_of_axes;
ioctl (fd, JSIOCGAXES, &number_of_axes);
6.6.5.1. JSIOGCVERSION¶
JSIOGCVERSION 是在运行时检查当前驱动程序是否为 1.0+ 版本并支持事件接口的好方法。如果不是,IOCTL 将失败。对于编译时决策,可以测试 JS_VERSION 符号
#ifdef JS_VERSION
#if JS_VERSION > 0xsomething
6.6.5.2. JSIOCGNAME¶
JSIOCGNAME(len) 允许您获取操纵杆的名称字符串——与启动时打印的名称相同。'len' 参数是应用程序请求名称时提供的缓冲区的长度。它用于避免在名称过长时可能发生的溢出
char name[128];
if (ioctl(fd, JSIOCGNAME(sizeof(name)), name) < 0)
strscpy(name, "Unknown", sizeof(name));
printf("Name: %s\n", name);
6.6.5.3. JSIOC[SG]CORR¶
对于 JSIOC[SG]CORR 的用法,我建议您查阅 jscal.c。它们在普通程序中不需要,仅在操纵杆校准软件(如 jscal 或 kcmjoy)中需要。这些 IOCTL 和数据类型不被视为 API 的稳定部分,因此在驱动程序的后续版本中可能会在没有警告的情况下发生变化。
JSIOCSCORR 和 JSIOCGCORR 都要求 &js_corr 能够保存所有轴的信息。也就是说,struct js_corr corr[MAX_AXIS];
struct js_corr 定义为
struct js_corr {
__s32 coef[8];
__u16 prec;
__u16 type;
};
和 type
#define JS_CORR_NONE 0x00 /* returns raw values */
#define JS_CORR_BROKEN 0x01 /* broken line */
6.6.6. 向后兼容性¶
0.x 操纵杆驱动程序 API 功能非常有限,其使用已被弃用。然而,该驱动程序提供了向后兼容性。以下是快速摘要:
struct JS_DATA_TYPE js;
while (1) {
if (read (fd, &js, JS_RETURN) != JS_RETURN) {
/* error */
}
usleep (1000);
}
正如您可以从示例中看出,read 会立即返回操纵杆的实际状态
struct JS_DATA_TYPE {
int buttons; /* immediate button state */
int x; /* immediate x axis value */
int y; /* immediate y axis value */
};
而 JS_RETURN 定义为
#define JS_RETURN sizeof(struct JS_DATA_TYPE)
要测试按钮的状态,
first_button_state = js.buttons & 1;
second_button_state = js.buttons & 2;
原始 0.x 驱动程序中的轴值没有定义的范围,除了值是非负的。1.2.8+ 驱动程序使用固定范围来报告值,其中 1 为最小值,128 为中心,255 为最大值。
v0.8.0.2 驱动程序还为“数字操纵杆”(在此驱动程序中现在称为多系统操纵杆)提供了一个接口,位于 /dev/djsX 下。此驱动程序不试图与该接口兼容。
6.6.7. 最终说明¶
____/| Comments, additions, and specially corrections are welcome.
\ o.O| Documentation valid for at least version 1.2.8 of the joystick
=(_)= driver and as usual, the ultimate source for documentation is
U to "Use The Source Luke" or, at your convenience, Vojtech ;)