イベント

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;

XIfEventdisplay 上のイベントキューを順番に見ていき,そのイベントを(*predicate)で判断します.この(*predicate)Trueを 返すまで処理がブロックされたままになります.条件が成立するものが見付かる と,そのイベントはキューから削除され,event_returnにコピーされます.arg(*predicate)への 引数です.XPointerchar *で宣言されていますが,これはANSIでいうところのvoid *であると理解していいと思います. この例では(*predicate)として WindowMappedという関数を使っています.

XIfEvent関数の条件判断をする(*predicate)には,イベントの発生したサーバ displayとキューの中のイベントeventが渡されることになっています. そのほかにデータを渡したい場合にargsを使いま す.XPointerなので,任意の型の変数を渡すことが できるようになっているわけです.(*predicate)TrueFalseを返すことになっています.

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_eventXSendEventか ら送られたイベントの場合に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_storeAlwaysを指定し,常にバッキングストアを有効にします.

       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イベントを 検出しなければなりません.そのためにXSelectInputExposureMaskを設定します.

これで,ウインドウが隠されたりしても,また表に出てきたときにはちゃんと線 が描かれています.

しかし,全てのサーバがバッキングストアの機能を備えているわけではありませ ん.サーバでバッキングストアを使えるかどうかはxdpyinfoを実行した際にoptions: backing-storeYESになっているかで分かります.

消えたら描き直す

そういうわけで,もっとも一般的な消えたらば書き直す方法です.具体的には, 隠されていた面が画面上に現れたらば描画する,というのが正確です.

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イベントが発生した場合に線を再描 画しています.

Back to TOP