LECTURE 4 イベント処理

○ はじめに

X ウィンドウシステムに限らず、一般的に GUI アプリケーションは外部からの何らかの働きかけ(イベント)を受けて、それに応じた処理を行うことで動作します。よって、自分がどのようなイベントを必要なのか、どうすれば必要なイベントを受け取ることができるのか、について知る必要があります。

ここでは X ウィンドウシステムにおけるイベント処理について説明していきます。

○ イベントとは

X におけるイベントとは、X サーバからクライアントに送られる情報のことです。イベントは、発生した順番に X サーバからクライアントに非同期でそれぞれのウィンドウごとに送られます。GUI アプリケーションは一般的にイベント駆動型プログラムであり、これらのイベントに応じて処理を行うという形でコードを書いていきます。

LECTURE 1 でも少し説明したように、自分が通知して欲しいイベントの種類をあらかじめ X サーバに教えておくことにより、必要なイベントが発生した時に X サーバがそれを通知してくれます。通知して欲しいイベントを X サーバに教えない場合にはイベントは送られてきませんが、中には無条件に通知されるイベントも存在します。

○ イベントの選択

前述したように、実際のアプリケーションでは必要なイベントだけを通知してくれるように X サーバに依頼します。そのためには XSelectInput() を使います。

XSelectInput( Display* display, Window w, long event_mask );

event_mask : 必要なイベントをイベントマスクで指定する。

event_mask には、win で指定したウィンドウにおいて通知が必要なイベントを指定します。イベントの指定にはイベントマスクを使います。複数のイベントを選択する場合は、イベントマスクの論理和を指定します。イベント指定はウィンドウごとに行わなくてはいけないことに注意してください。

良く使う、もしくは僕が使い方を知っているイベントについて、イベント名、イベントの内容、イベントマスクの対応を下表にまとめておきます。全てのイベントについて書かれてあるわけではありませんので、注意してください。

イベントマスク イベントの内容 イベントの種類
FocusChangeMask キーボードフォーカス状態が変わった FocusIn, FocusOut
KeyPressMask キーが押された KeyPress
KeyReleaseMask キーを離した KeyRelease
ButtonPressMask マウスボタンを押した ButtonPress
ButtonReleaseMask マウスボタンを離した ButtonRelease
EnterWindowMask マウスカーソルがウィンドウに入った EnterNotify
LeaveWindowMask マウスカーソルがウィンドウから出た LeaveNotify
PointerMotionMask マウスカーソルが移動した MotionNotify
Button1MotionMask ボタン1を押したままマウスカーソルが移動した 同上
Button2MotionMask ボタン2を押したままマウスカーソルが移動した 同上
Button3MotionMask ボタン3を押したままマウスカーソルが移動した 同上
Button4MotionMask ボタン3を押したままマウスカーソルが移動した 同上
Button5MotionMask ボタン3を押したままマウスカーソルが移動した 同上
ButtonMotionMask ボタンを押したままマウスカーソルが移動した 同上
PointerMotionHintMask マウスの他のMotionMaskと一緒に用いて、イベントを圧縮する 同上
ExposureMask GraphicsExpose、NoExpoesを除く全てのExposeイベント Expose
StructureNotifyMask ウィンドウの大きさ、位置、ボーダの幅、スタック順序の変更 DestroyNotify, UnmapNotify, MapNotify, ReparentNotify, ConfigureNotify, GravityNotify, CirculateNotify
SubstructureNotifyMask 子ウィンドウの大きさ、位置、ボーダの幅、スタック順序の変更 CreateNotify, 後はStructureNotifyMaskと同じ
なし MappingNotify イベントはキーボード/ボタンのマッピングが変わったことを表す。 ClientMessage イベントはあるクライアントから XSendEvent() を使用して直接 送られて来たり、ウィンドウマネージャプロトコルを設定した場合に送られて来 たりする MappingNotify, ClientMessage

○ イベントの読み込み

必要なイベントを選択してウィンドウをマップした直後から、選択したイベントが随時 X サーバから送られて来ます。クライアントはイベントを読み込んで適切な処理を行います。

イベントを読み込むには、XNextEvent()を使います。

XNextEvent( Display* display, XEvent* event_return );
event_return : 読み込んだイベント情報を格納するためのイベント構造体を指定

X ウィンドウシステムでは、X サーバから来るイベントのキューイング、およびクライアントからのリクエストのバッファリングを行っています。XNextEvent() は、イベントキューからイベントを取りだした後にリクエストバッファの内容を X サーバに送ります。イベントキューにイベントが無い場合は、XX サーバにイベントを読みにいって、たまっているイベントを全てイベントキューにセットします。イベントが X サーバに無い場合には、イベントが送られて来るまでブロックします。

リクエストバッファのフラッシュは、XNextEvent() 以外にもいくつかの X ライブラリ関数( XCreateSimpleWindow() など)を実行したときにも行われます。しかし場合によってはリクエストバッファをすぐにフラッシュしたい時もあります。強制的にリクエストバッファをフラッシュするには XFlush() を使います。また、同様の関数に XSync() があります。

XFlush( Display* display );
XSync( Display* display, Bool discard );
discard : True の場合、その時点でたまっているイベントキューの内容を破棄する。

XSync() はリクエストバッファのフラッシュ後 X サーバがそのリクエストを全て処理するまで待っている点が XFlush() と異なります。

○ イベントが無い場合の処理

前述の通り、XNextEvent() はイベントキューにイベントが無いと X サーバにイベントを読みにいきます。このとき X サーバにもイベントがない場合、イベントが発生するまでブロッキングします。つまり、 XNextEvent() を実行するとアプリケーションはイベントが発生するまで処理が止まってしまいます。しかしイベントが無い場合には別の処理を行いたいことがよくあります( センサ入力の監視など、バックグラウンドで行いたいような処理)。

このような処理は、イベントを読みにいく前にまずイベントキューにたまっているイベントの数を調べて、イベントがあることが分かった時点で XNextEvent() を実行することで実現できます。イベントキューにたまっているイベントの数を調べるには、 XPending() を使います。

int XPending( Display* display );

XPending() は、イベントキューから取りだされていないイベントの数を返します。これを使ってイベントループを以下のように書くと、イベントが無い場合に別の処理を行うことができます。しかしここではあまり時間がかかる処理は行わないようにします。でなければ、次のイベントが発生してもそれを処理に行くのに時間がかかるためアプリケーションが固まったように見えてしまいます。

/* イベントループ */
while( 1 ) {
  while ( XPending( dpy ) ) {
    XNextEvent( dpy, &event );
    /* 通常のイベント処理 */
  }

  /* イベントが無い場合の処理 */
}

○ イベント構造体

キー押下イベント、マウス関連のイベントなど、イベントには数種類あります。そしてそれぞれのイベントごとにイベント情報を格納するための構造体が用意されています。各イベント構造体は <X11/Xlib.h> で定義されています。このようにイベントごとに構造体が異なると、必要なイベント分の構造体変数をプログラム中に用意しなければならないと思ってしまいますが、全てのイベントを一括して扱うために XEvent 型構造体が用意されています。

typedef union _XEvent {
  int type; /* イベントの種類 */

  /* 以降、全てのイベント構造体を表す変数宣言が続く */
} XEvent;

全てのイベント構造体の最初のメンバがイベントの種類( int type; )となっているので、上のように union 定義することで、 XEvent 型変数にイベント情報を読みこんだ後に type メンバを調べれば、実際に送られて来たイベントの種類が分かります。その後は必要に応じてイベント変数をキャストして使えば良いというわけです。

xev というアプリケーションを実行すると、表示されたウィンドウに対して発生したイベントを受け取り、対応するイベント構造体の内容を出力する様子を見ることができます。

○ MappingNotify イベント

MappingNotify イベントは通知を拒否できないイベントの一つです。このイベントは、クライアントのキーボードマッピングやポインタマッピングが変更された場合に通知されるイベントです。このイベントを受け取った場合は、XRefreshKeyboardMapping を実行すべきとされています。

XRefreshKeyboardMapping( XMappingEvent* event_map );

これにより、Xlib 内のキーボードに関する情報が更新されます。