コンテンツにスキップ

X Window Programming/UNIX環境での非同期処理

出典: フリー教科書『ウィキブックス(Wikibooks)』

UNIX環境での非同期処理

[編集]

X Window Systemにおけるイベント処理は、通常非同期的に行われます。これにより、アプリケーションは複数のイベントを同時に処理することができ、ユーザーインタラクションに応じた反応を即座に行うことが可能です。本章では、UNIX環境での非同期処理に関する2つの主要な方法、select()poll()を用いたイベント処理、およびXPending()を用いた効率的なイベントループの実装方法について解説します。

select()poll()を用いたイベント処理

[編集]

UNIXのselect()およびpoll()システムコールは、複数の入力ストリームに対して非同期的に待機し、どれか一つでも読み込み可能または書き込み可能な状態になったときに処理を行うための機能を提供します。これらは、X11のイベント処理を効率的に行うために使われることが多いです。

  1. select()
    select()は、複数のファイルディスクリプタに対して監視を行い、どれか一つでもイベントが発生した場合に通知を受けることができます。特に、ネットワークソケットやX11のディスプレイ接続など、I/Oイベントを非同期に処理する際に便利です。
    #include <sys/select.h>
    #include <unistd.h>
    #include <X11/Xlib.h>
    
    int main() {
        Display *display = XOpenDisplay(NULL);
        if (!display) {
            fprintf(stderr, "Xサーバーに接続できません\n");
            return -1;
        }
    
        int xfd = ConnectionNumber(display);  // Xサーバーのファイルディスクリプタ
    
        for (;;) {
            fd_set rfds;
            FD_ZERO(&rfds);
            FD_SET(xfd, &rfds);
    
            struct timeval tv;
            tv.tv_sec = 5;
            tv.tv_usec = 0;
    
            int retval = select(xfd + 1, &rfds, NULL, NULL, &tv);
            if (retval == -1) {
                perror("select()");
                break;
            } else if (retval) {
                if (XPending(display)) {
                    XEvent event;
                    XNextEvent(display, &event);
                    // イベントの処理
                }
            } else {
                printf("タイムアウトしました。\n");
            }
        }
    
        XCloseDisplay(display);
        return 0;
    }
    
  2. poll()
    poll()は、select()と似ていますが、より高機能であり、複数のファイルディスクリプタを監視できる点で柔軟性があります。また、poll()は各ファイルディスクリプタの状態をリスト形式で管理します。以下はpoll()を使用した例です。
    #include <poll.h>
    #include <unistd.h>
    #include <X11/Xlib.h>
    
    int main() {
        Display *display = XOpenDisplay(NULL);
        if (!display) {
            fprintf(stderr, "Xサーバーに接続できません\n");
            return -1;
        }
    
        int xfd = ConnectionNumber(display);
        struct pollfd pfds[1];
        pfds[0].fd = xfd;
        pfds[0].events = POLLIN;
    
        for (;;) {
            int ret = poll(pfds, 1, 5000);  // 5秒のタイムアウト
            if (ret == -1) {
                perror("poll()");
                break;
            } else if (ret > 0 && (pfds[0].revents & POLLIN)) {
                if (XPending(display)) {
                    XEvent event;
                    XNextEvent(display, &event);
                    // イベントの処理
                }
            } else {
                printf("タイムアウトしました。\n");
            }
        }
    
        XCloseDisplay(display);
        return 0;
    }
    

これらの方法を用いることで、非同期に複数の入力を処理しながら、Xサーバーとの接続を維持することができます。

XPending()による効率的なイベントループ

[編集]

XPending()関数は、Xサーバーに対して現在保留中のイベントの数を取得するための関数です。この関数を使用すると、イベントループを効率的に実装することができ、イベントが存在する場合のみ処理を行うことができます。これにより、無駄なCPUリソースの消費を抑えることができます。

#include <X11/Xlib.h>

int main() {
    Display *display = XOpenDisplay(NULL);
    if (!display) {
        fprintf(stderr, "Xサーバーに接続できません\n");
        return -1;
    }

    for (;;) {
        if (XPending(display)) {
            XEvent event;
            XNextEvent(display, &event);
            // イベントの処理
        }
        // 他の処理
    }

    XCloseDisplay(display);
    return 0;
}

XPending()を使うことで、Xサーバーからイベントが来ているかどうかを効率的に確認できます。イベントがある場合のみXNextEvent()を呼び出して処理を行います。この方法は、CPUリソースを節約しながら効率的なイベントループを実現できます。

実践例

[編集]

次に、実際のアプリケーションでのイベント処理を例にとり、キーボード入力によるウィンドウ操作と、マウスイベントの処理およびウィジェットの基礎的な動作を見ていきます。

キーボード入力によるウィンドウ操作

[編集]

ユーザーがキーボードのキーを押したときにウィンドウの状態を変更するプログラムを作成します。例えば、Escapeキーでウィンドウを閉じる動作を実装します。

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <stdio.h>

int main() {
    Display *display = XOpenDisplay(NULL);
    if (!display) {
        fprintf(stderr, "Xサーバーに接続できません\n");
        return -1;
    }

    int screen = DefaultScreen(display);
    Window window = XCreateSimpleWindow(display, RootWindow(display, screen),
                                        10, 10, 400, 300, 1,
                                        BlackPixel(display, screen),
                                        WhitePixel(display, screen));

    XMapWindow(display, window);
    XSelectInput(display, window, KeyPressMask);

    for (;;) {
        XEvent event;
        XNextEvent(display, &event);
        if (event.type == KeyPress) {
            if (XLookupKeysym(&event.xkey, 0) == XK_Escape) {
                break;  // Escapeキーで終了
            }
        }
    }

    XCloseDisplay(display);
    return 0;
}

このプログラムでは、Escapeキーを押すとウィンドウが閉じます。KeyPressMaskを使ってキーボード入力を監視し、XLookupKeysym()で押されたキーを判定しています。

マウスイベントの処理とウィジェットの基礎

[編集]

マウスイベントを処理し、ボタンが押されたときにウィンドウの色を変更する簡単な例を示します。このようなマウスイベントの処理は、ウィジェットの動作の基礎となります。

#include <X11/Xlib.h>
#include <stdio.h>

int main() {
    Display *display = XOpenDisplay(NULL);
    if (!display) {
        fprintf(stderr, "Xサーバーに接続できません\n");
        return -1;
    }

    int screen = DefaultScreen(display);
    Window window = XCreateSimpleWindow(display, RootWindow(display, screen),
                                        10, 10, 400, 300, 1,
                                        BlackPixel(display, screen),
                                        WhitePixel(display, screen));

    XMapWindow(display, window);
    XSelectInput(display, window, ButtonPressMask);

    for (;;) {
        XEvent event;
        XNextEvent(display, &event);
        if (event.type == ButtonPress) {
            // ボタンが押されたときにウィンドウの背景色を変更
            XSetWindowBackground(display, window, 0xFF0000);  // 赤色
            XClearWindow(display, window);
        }
    }

    XCloseDisplay(display);
    return 0;
}

このプログラムでは、マウスのボタンが押されるたびにウィンドウの背景色が赤色に変更されます。ButtonPressMaskを使ってマウスボタンのイベントを監視し、`X

SetWindowBackground()`で背景色を変更しています。

これらの実践例は、X Window Systemでの基本的なインタラクションを学ぶための出発点として役立ちます。