Xはクライアント・サーバシステムであることは先に述べました. クライアントからサーバに送られるのが「メッセージ」で, サーバからクライアントに通知されるのが「イベント」です.
イベントには33種類あり,マウスやキーボード,グラフィックスの状態に変化が あると発生します.X.hに記述されていますが, ここで全部あげてみます.
KeyPress キーボードのキーが押された KeyRelease キーボードのキーが離された ButtonPress マウスのボタンが押された ButtonRelease マウスのボタンが離された MotionNotify マウスが移動した EnterNotify マウスがウインドウに入った LeaveNotify マウスがウインドウから出た FocusIn ウインドウがキーボードフォーカスを得た FocusOut ウインドウがキーボードフォーカスを失った KeymapNotify キーボードの全キー状態を調べた Expose ウインドウの面が表示された GraphicsExpose グラフィックスの一部がコピーできなかった NoExpose グラフィックスを全てコピーできた VisibilityNotify ウインドウの見え方が変化した CreateNotify ウインドウが作られた DestroyNotify ウインドウが破棄された UnmapNotify ウインドウがアンマップされた MapNotify ウインドウがマップされた MapRequest ウインドウのマップリクエストがあった ReparentNotify 親ウインドウが変化した ConfigureNotify ウインドウの構成が変化した ConfigureRequest ウインドウの構成変更要求があった GravityNotify 子ウインドウが移動した ResizeRequest ウインドウがリサイズされた CirculateNotify ウインドウスタックが回転した CirculateRequest ウインドウスタックの変更要求があった PropertyNotify ウインドウの属性が変化した SelectionClear セレクションの所有権がなくなった SelectionRequest セレクション変換を要求された SelectionNotify セレクション変換が終わった ColormapNotify カラーマップに変更があった ClientMessage XSendEventでイベントが送られた MappingNotify キーボードマップに変更があった
これだけの変化を知ることができます.
それでは,具体的にイベントを使ってみます.線を書いたら消えてしまう場合が ありました.この問題を解決してみます.
MapNotifyが通知されたら描けばいいということ は想像できますね.
0001: /* 0002: mapnotify.c 0003: gcc mapnotify.c -I/usr/X11R6/include -L/usr/X11R6/lib -lX11 0004: */ 0005: 0006: #include <X11/Xlib.h> 0007: 0008: Bool WindowIsMapped( Display *dpy, XEvent *event, XPointer args ){ 0009: if( event->type == MapNotify ){ 0010: return True; 0011: }else{ 0012: return False; 0013: } 0014: } 0015: 0016: int main( int argc, char *argv[] ) 0017: { 0018: Display *dpy; 0019: int screen; 0020: Window win; 0021: GC gc; 0022: XEvent event; 0023: 0024: dpy = XOpenDisplay( NULL ); /* Xサーバに接続する */ 0025: 0026: screen = DefaultScreen( dpy ); /* スクリーン設定 */ 0027: 0028: win = XCreateSimpleWindow( /* ウィンドウを開く */ 0029: dpy, /* Xサーバ */ 0030: RootWindow(dpy,screen), /* 親ウィンドウ */ 0031: 0, 0, /* 表示時の左上隅の座標 x, y */ 0032: 256, 256, /* ウィンドウの幅と高さ */ 1, BlackPixel(dpy,screen),/* ボーダーの幅と色 */ 0033: WhitePixel(dpy,screen) /* ウィンドウの背景色 */ 0034: ); 0035: XSelectInput( dpy, win, StructureNotifyMask ); 0036: XMapWindow( dpy, win ); /* ウィンドウのマッピング */ 0037: 0038: XIfEvent( dpy, &event, WindowIsMapped, NULL ); 0039: 0040: gc = DefaultGC( dpy, screen ); /* グラフィックコンテキストの設定 */ 0041: XSetForeground( dpy, gc, BlackPixel(dpy,screen) ); /* 描画色設定 */ 0042: XDrawLine( dpy, win, gc, 0, 0, 256, 256 ); /* 線を描く */ 0043: XFlush( dpy ); /* バッファのフラッシュ */ 0044: 0045: sleep( 10 ); /* 10秒間表示を保持 */ 0046: XDestroyWindow( dpy, win ); /* ウインドウの開放 */ 0047: XCloseDisplay( dpy ); /* Xサーバを切断する */ 0048: 0049: return 0; 0050: }
WindowIsMappedという関数がありますが,これ は後で説明します.イベントを扱うためにXEventが 出現しています.
0022: XEvent event;
typedef union _XEvent { int type; XAnyEvent xany; XKeyEvent xkey; XButtonEvent xbutton; XMotionEvent xmotion; XCrossingEvent xcrossing; XFocusChangeEvent xfocus; XExposeEvent xexpose; XGraphicsExposeEvent xgraphicsexpose; XNoExposeEvent xnoexpose; XVisibilityEvent xvisibility; XCreateWindowEvent xcreatewindow; XDestroyWindowEvent xdestroywindow; XUnmapEvent xunmap; XMapEvent xmap; XMapRequestEvent xmaprequest; XReparentEvent xreparent; XConfigureEvent xconfigure; XGravityEvent xgravity; XResizeRequestEvent xresizerequest; XConfigureRequestEvent xconfigurerequest; XCirculateEvent xcirculate; XCirculateRequestEvent xcirculaterequest; XPropertyEvent xproperty; XSelectionClearEvent xselectionclear; XSelectionRequestEvent xselectionrequest; XSelectionEvent xselection; XColormapEvent xcolormap; XClientMessageEvent xclient; XMappingEvent xmapping; XErrorEvent xerror; XKeymapEvent xkeymap; long pad[24]; } XEvent;
XEventは上に示すような,各イベント構造体の共 用体です.イベント構造体はいくつかの共通メンバとイベント固有のメンバから なります.これについてはおいおい見ていきます. イベントが発生すると,このXEvent共用体に情報が 格納され,イベントキューに入れられます.
0035: XSelectInput( dpy, win, StructureNotifyMask );
XSelectInput(display, w, event_mask) Display *display; Window w; long event_mask;
XSelectInputはウインドウの通知して欲しいイ ベントを設定します.たくさんのイベントを知ることができますが,プログラマ が全てのイベントを使用するわけではありません.また,全てのイベントを通知 していたのでは,通信量も増えてしまいます.そこで,必要なイベントだけを通 知してくれるようにし,いらないイベントに関しては破棄してもらうようにしま す.サーバdisplayとウインドウwに対してevent_maskを設 定します.各イベントに対応したビットが立っているマスクが設定されています ので,必要なイベントマスクをOR(|)でつないで指定します.ここではMapNotifyイベントを検出できるStructureNotifyMaskだけが指定されています.
0038: XIfEvent( dpy, &event, WindowIsMapped, NULL );
XMapWindowの直後XIfEventで処理をブロックします.
XIfEvent(display, event_return, predicate, arg) Display *display; XEvent *event_return; Bool (*predicate)(); XPointer arg; Bool (*predicate)(display, event, arg) Display *display; XEvent *event; XPointer arg;
XIfEventはdisplay 上のイベントキューを順番に見ていき,そのイベントを(*predicate)で判断します.この(*predicate)がTrueを 返すまで処理がブロックされたままになります.条件が成立するものが見付かる と,そのイベントはキューから削除され,event_returnにコピーされます.argは(*predicate)への 引数です.XPointerはchar *で宣言されていますが,これはANSIでいうところのvoid *であると理解していいと思います. この例では(*predicate)として WindowMappedという関数を使っています.
XIfEvent関数の条件判断をする(*predicate)には,イベントの発生したサーバ displayとキューの中のイベントeventが渡されることになっています. そのほかにデータを渡したい場合にargsを使いま す.XPointerなので,任意の型の変数を渡すことが できるようになっているわけです.(*predicate)はTrueか Falseを返すことになっています.
0008: Bool WindowIsMapped( Display *dpy, XEvent *event, XPointer args ){ 0009: if( event->type == MapNotify ){ 0010: return True; 0011: }else{ 0012: return False; 0013: } 0014: }
この例では,ウインドウがマップされたかどうかを判断します.イベント eventの中身がXMapEventであるかを調べることになります.
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window event; Window window; Bool override_redirect; } XMapEvent;
イベント構造体は最初の6つのメンバがどのイベントでも共通になっています. すなわち,何のイベントがどのディスプレイのどのウインドウで発生したのかが 必ず分かるわけです.
typeにはイベントを特定するIDが入ります.この 場合だとMapNotifyがセットされているはずですの で,その場合はTrue,そうでない場合はFalseを返します. serialにはイベントの通し番号が入ります.send_eventはXSendEventか ら送られたイベントの場合にTrueになります. displayはイベントが発生したディスプレイです. eventはマップされたウインドウまたは親ウインド ウのどちらかにイベントの発生したウインドウのIDが設定されます.どちらに設定さ れるかはStructureNotifyが設定されているか StructureNotifyが設定されているかによります. windowはマップされたウインドウIDです. override_redirectには,ウインドウ属性のoverride_redirectが設定されます.このメンバが設定さ れていると,通常はウインドウマネージャが介入してきません.
これにより,XIfEventでウインドウがマップさ れるまで処理がとまり,確実にマップされた後に線が引かれます.
しかし,この場合,書いた後にウインドウが隠されてしまうと二度と線は現れま せん.これを避けるためにはバッキングストアを使う方法があります.
バッキングストアというのは,ウインドウに隠された部分をサーバが記憶してお いて,再度画面に現れた際に自動的に描画するものです.
0001: /* 0002: backingstore.c 0003: gcc backingstore.c -I/usr/X11R6/include -L/usr/X11R6/lib -lX11 0004: */ 0005: 0006: #include <X11/Xlib.h> 0007: 0008: int main( int argc, char *argv[] ) 0009: { 0010: Display *dpy; 0011: int screen; 0012: Window win; 0013: GC gc; 0014: XEvent event; 0015: XSetWindowAttributes attr; 0016: 0017: dpy = XOpenDisplay( NULL ); /* Xサーバに接続する */ 0018: 0019: screen = DefaultScreen( dpy ); /* スクリーン設定 */ 0020: 0021: win = XCreateSimpleWindow( /* ウィンドウを開く */ 0022: dpy, /* Xサーバ */ 0023: RootWindow(dpy,screen), /* 親ウィンドウ */ 0024: 0, 0, /* 表示時の左上隅の座標 x, y */ 0025: 256, 256, /* ウィンドウの幅と高さ */ 1, BlackPixel(dpy,screen),/* ボーダーの幅と色 */ 0026: WhitePixel(dpy,screen) /* ウィンドウの背景色 */ 0027: ); 0028: attr.backing_store = Always; 0029: XChangeWindowAttributes( dpy, win, CWBackingStore, &attr ); 0030: XSelectInput( dpy, win, ExposureMask ); 0031: XMapWindow( dpy, win ); /* ウィンドウのマッピング */ 0032: 0033: gc = DefaultGC( dpy, screen ); /* グラフィックコンテキストの設定 */ 0034: XSetForeground( dpy, gc, BlackPixel(dpy,screen) ); /* 描画色設定 */ 0035: XDrawLine( dpy, win, gc, 0, 0, 256, 256 ); /* 線を描く */ 0036: XFlush( dpy ); /* バッファのフラッシュ */ 0037: 0038: sleep( 10 ); /* 10秒間表示を保持 */ 0039: XDestroyWindow( dpy, win ); /* ウインドウの開放 */ 0040: XCloseDisplay( dpy ); /* Xサーバを切断する */ 0041: 0042: return 0; 0043: }
新しくでてきたのは4行です.
0015: XSetWindowAttributes attr;
XSetWindowAttributesはウインドウの属性を記憶し ておく変数です.この変数を用いることによって,ウインドウの属性を変更する ことができます.
typedef struct { Pixmap background_pixmap; unsigned long background_pixel; Pixmap border_pixmap; unsigned long border_pixel; int bit_gravity; int win_gravity; int backing_store; unsigned long backing_planes; unsigned long backing_pixel; Bool save_under; long event_mask; long do_not_propagate_mask; Bool override_redirect; Colormap colormap; Cursor cursor; } XSetWindowAttributes;
まず変更をしたい属性メンバに値をセットします.ここではバッキングストアを 有効にするためにbacking_storeにAlwaysを指定し,常にバッキングストアを有効にします.
XChangeWindowAttributes(display, w, valuemask, attributes) Display *display; Window w; unsigned long valuemask; XSetWindowAttributes *attributes;
displayはサーバ,w は属性を変更したいウインドウを指定します.valuemaskには変更を加える値のビットを立てたフラグを 与えます.attributesは先ほど値をセットした変 数です.この作業はXCreateWindowでウインド ウを生成する際に指定することもできます.
0030: XSelectInput( dpy, win, ExposureMask );
バッキングストアを使うには,Exposeイベントを 検出しなければなりません.そのためにXSelectInputでExposureMaskを設定します.
これで,ウインドウが隠されたりしても,また表に出てきたときにはちゃんと線 が描かれています.
しかし,全てのサーバがバッキングストアの機能を備えているわけではありませ ん.サーバでバッキングストアを使えるかどうかはxdpyinfoを実行した際にoptions: backing-storeがYESになっているかで分かります.
そういうわけで,もっとも一般的な消えたらば書き直す方法です.具体的には, 隠されていた面が画面上に現れたらば描画する,というのが正確です.
0001: /* 0002: eventloop.c 0003: gcc eventloop.c -I/usr/X11R6/include -L/usr/X11R6/lib -lX11 0004: */ 0005: 0006: #include <X11/Xlib.h> 0007: 0008: int main( int argc, char *argv[] ) 0009: { 0010: Display *dpy; 0011: int screen; 0012: Window win; 0013: GC gc; 0014: XEvent event; 0015: 0016: dpy = XOpenDisplay( NULL ); /* Xサーバに接続する */ 0017: 0018: screen = DefaultScreen( dpy ); /* スクリーン設定 */ 0019: 0020: win = XCreateSimpleWindow( /* ウィンドウを開く */ 0021: dpy, /* Xサーバ */ 0022: RootWindow(dpy,screen), /* 親ウィンドウ */ 0023: 0, 0, /* 表示時の左上隅の座標 x, y */ 0024: 256, 256, /* ウィンドウの幅と高さ */ 1, BlackPixel(dpy,screen),/* ボーダーの幅と色 */ 0025: WhitePixel(dpy,screen) /* ウィンドウの背景色 */ 0026: ); 0027: XSelectInput( dpy, win, ExposureMask ); 0028: XMapWindow( dpy, win ); /* ウィンドウのマッピング */ 0029: 0030: gc = DefaultGC( dpy, screen ); /* グラフィックコンテキストの設定 */ 0031: XSetForeground( dpy, gc, BlackPixel(dpy,screen) ); /* 描画色設定 */ 0032: 0033: while( 1 ){ 0034: XNextEvent( dpy, &event ); 0035: switch( event.type ){ 0036: case Expose: 0037: XDrawLine( dpy, win, gc, 0, 0, 256, 256 ); /* 線を描く */ 0038: XFlush( dpy ); /* バッファのフラッシュ */ 0039: default: 0040: ; 0041: } 0042: } 0043: }
0027: XSelectInput( dpy, win, ExposureMask );
Exposeイベントを検出するためにExposureMaskを設定します.
0033: while( 1 ){ 0034: XNextEvent( dpy, &event ); 0035: switch( event.type ){ 0036: case Expose: 0037: XDrawLine( dpy, win, gc, 0, 0, 256, 256 ); /* 線を描く */ 0038: XFlush( dpy ); /* バッファのフラッシュ */ 0039: default: 0040: ; 0041: } 0042: }
このように,ループの中でイベントに応じた処理を行う,というのがウインドウ 系のプログラムの一般的な形です.ループの中で常に処理が行われているわけで すので,ここでは10秒待つということはしていません.本来であれば終了処理 をいれるべきなのですが,省略していますのでC-cなどで強制終了してください.
XNextEvent(display, event_return) Display *display; XEvent *event_return;
XNextEventは,イベントキューの先頭にあるイ ベントをキューから取り出し,内容をevent_returnにコピーします.キューにイベントがない 場合は出力バッファを強制排出した後にイベントが溜るまで処理をブロックしま す.
ウインドウが一つしか無い場合は,この例のようにXEvent.typeによって処理を振り分けるというのが一般的 です.ここでは,画面が表に現れた=Exposeイベントが発生した場合に線を再描 画しています.