6.6. 编程接口

作者:

Ragnar Hojland Espinosa <ragnar@macula.net> - 1998 年 8 月 7 日

6.6.1. 简介

重要

本文档描述了旧的 js 接口。 建议新的客户端切换到通用事件(evdev)接口。

1.0 驱动程序对操纵杆驱动程序使用了一种新的、基于事件的方法。操纵杆驱动程序现在仅报告其状态的任何更改,而不是用户程序轮询操纵杆的值。有关更多信息,请参阅 编程接口、joystick.h 和 joystick 包中包含的 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 事件,则当前的类型值将为

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. 读取

如果以阻塞模式打开设备,则读取操作将永远阻塞(即等待),直到生成并有效读取事件。如果你无法承受永远等待(诚然,这确实是很长的时间),则有两种替代方案;)

  1. 使用 select 等待 fd 上有数据要读取,或者直到超时。select(2) 手册页上有一个很好的示例。

  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. IOCTL

操纵杆驱动程序定义以下 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);
}

你可以从示例中看出,读取操作会立即返回,并返回操纵杆的实际状态

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 ;)