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

如果以阻塞模式開啟裝置,讀取操作將永遠阻塞(即等待),直到生成並有效讀取事件。如果您無法承受永遠等待(這確實是很長的時間;),則有兩種替代方法:

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