さて,イベントがわかったところで,キー入力のイベントを検出してみます. コマンドラインからC-cで終了させるのではなく,ESCキーが押されたら終了する ことにします.
0001: /*
0002: key.c
0003: gcc key.c -I/usr/X11R6/include -L/usr/X11R6/lib -lX11
0004: */
0005:
0006: #include <X11/Xlib.h>
0007: #include <X11/keysym.h>
0008:
0009: #define DEFAULT_SIZE 256
0010:
0011: int main( int argc, char *argv[] )
0012: {
0013: Display *dpy;
0014: int screen;
0015: Window win, root;
0016: GC gc;
0017: unsigned int win_width=DEFAULT_SIZE, win_height=DEFAULT_SIZE;
0018: XEvent event;
0019: KeySym key_sym;
0020:
0021: dpy = XOpenDisplay( NULL ); /* Xサーバに接続する */
0022:
0023: screen = DefaultScreen( dpy ); /* スクリーン設定 */
0024:
0025: win = XCreateSimpleWindow( /* ウィンドウを開く */
0026: dpy, /* Xサーバ */
0027: RootWindow(dpy,screen), /* 親ウィンドウ */
0028: 0, 0, /* 表示時の左上隅の座標 x, y */
0029: win_width, win_height, /* ウィンドウの幅と高さ */
0030: 1, BlackPixel(dpy,screen),/* ボーダーの幅と色 */
0031: WhitePixel(dpy,screen) /* ウィンドウの背景色 */
0032: );
0033: XSelectInput( dpy, win, ExposureMask|KeyPressMask);
0034: XMapWindow( dpy, win ); /* ウィンドウのマッピング */
0035:
0036: gc = DefaultGC( dpy, screen ); /* グラフィックコンテキストの設定 */
0037: XSetForeground( dpy, gc, BlackPixel(dpy,screen) ); /* 描画色設定 */
0038:
0039: while( 1 ){
0040: XNextEvent( dpy, &event );
0041: switch( event.type ){
0042: case Expose:
0043: XDrawLine( dpy, win, gc, 0, 0, win_width, win_height ); /* 線を描く */
0044: XFlush( dpy ); /* バッファのフラッシュ */
0045: break;
0046: case KeyPress:
0047: key_sym = XKeycodeToKeysym( dpy, event.xkey.keycode, 0 );
0048: if( key_sym == XK_Escape ){
0049: XDestroyWindow( dpy, win );
0050: exit(0);
0051: }
0052: break;
0053: default:
0054: ;
0055: }
0056: }
0057: }
19行目にKeySymという型がでてきます.これは X11/X.hの中で以下のように定義されています.
typedef XID KeySym;
typedef unsigned long XID;
となっていて,つまり,単なる整数ということです.
0020: KeySym key_sym;
これで,キーシンボルと呼ばれる,各キーに対応した数値を表します.どのキー に何の数値が割り当てられているかは,X11/keysym.hと X11/keysymdef.hで定義されています.
Xではさまざまなメーカーのキーボードが接続される可能性があるので,直接キー コードを扱ってしまうと,他のキーボードでは別のキーを表している可能性があ ります.そのため,それらの違いをここで吸収するようにしています.
このマッピングを変更することによって,たとえば,キーの仮想入替などができ ます.
サーバへの接続やウインドウの生成はこれまでと同じです.キーイベントを受け 付けるために33行目のXSelectInputで,KeyPressMaskを設定します.
あとは,イベントループの中でKeyPressイベント を拾います.
0046: case KeyPress:
0047: key_sym = XKeycodeToKeysym( dpy, event.xkey.keycode, 0 );
0048: if( key_sym == XK_Escape ){
0049: XDestroyWindow( dpy, win );
0050: exit(0);
0051: }
0052: break;
KeySym XKeycodeToKeysym(display, keycode, index)
Display *display;
KeyCode keycode;
int index;
XKeycodeToKeysymで,キーボードから入力され たキーコードをキーシンボルに変換します.キーコードは XEvent.XKeyEvent.keycodeに格納されていますので displayとともに指定します.ここで,indexは,そのキーコードに対してKeySymを取り出したいキーコードベクトルでの番号を指定 します.
たとえば,「A」のキーからキーコード「0x26」が得られるとします.このキー コードに対して,キーコードベクトル(a,A)が設定されているとすると,indexに0を指定すると,キーシンボルXK_aが,1を指定するとXK_Aが得られます. 通常キーコードベクトルは2つの要素になっているそうで,最初のが通常のキー シンボル,次のはシフトキーを押している場合のキーシンボルになるのだそうで す.簡単にいえば,各キーコードに対して,キーシンボルの配列があるというこ とですね.
47行では,押されたキーのシンボルがESCAPEキーのものであることがわかればよ いので,indexは0をしています.
そして,キーシンボルがXK_Escapeの場合に XDestroyWindowを呼んで終了します.
さて,これでウインドウを開いて,自分で閉じるということができるようになり ました.
Windows SDKの方ではメッセージのところ でウインドウのリサイズの説明をしました,Xでもリサイズに対応してみます.
0001: /*
0002: resize.c
0003: gcc key.c -I/usr/X11R6/include -L/usr/X11R6/lib -lX11
0004: */
0005:
0006: #include <X11/Xlib.h>
0007: #include <X11/keysym.h>
0008:
0009: #define DEFAULT_SIZE 256
0010:
0011: int main( int argc, char *argv[] )
0012: {
0013: Display *dpy;
0014: int screen;
0015: Window win, root;
0016: GC gc;
0017: unsigned int win_width=DEFAULT_SIZE, win_height=DEFAULT_SIZE;
0018: unsigned int dummy;
0019: XEvent event;
0020: KeySym key_sym;
0021:
0022: dpy = XOpenDisplay( NULL ); /* Xサーバに接続する */
0023:
0024: screen = DefaultScreen( dpy ); /* スクリーン設定 */
0025:
0026: win = XCreateSimpleWindow( /* ウィンドウを開く */
0027: dpy, /* Xサーバ */
0028: RootWindow(dpy,screen), /* 親ウィンドウ */
0029: 0, 0, /* 表示時の左上隅の座標 x, y */
0030: win_width, win_height, /* ウィンドウの幅と高さ */
0031: 1, BlackPixel(dpy,screen),/* ボーダーの幅と色 */
0032: WhitePixel(dpy,screen) /* ウィンドウの背景色 */
0033: );
0034: XSelectInput( dpy, win, ExposureMask|KeyPressMask|StructureNotifyMask);
0035: XMapWindow( dpy, win ); /* ウィンドウのマッピング */
0036:
0037: gc = DefaultGC( dpy, screen ); /* グラフィックコンテキストの設定 */
0038: XSetForeground( dpy, gc, BlackPixel(dpy,screen) ); /* 描画色設定 */
0039:
0040: while( 1 ){
0041: XNextEvent( dpy, &event );
0042: switch( event.type ){
0043: case Expose:
0044: XDrawLine( dpy, win, gc, 0, 0, win_width, win_height ); /* 線を描く */
0045: XFlush( dpy ); /* バッファのフラッシュ */
0046: break;
0047: case ConfigureNotify:
0048: XGetGeometry( dpy, win, &root,
0049: &dummy, &dummy, &win_width, &win_height,
0050: &dummy, &dummy );
0051: XDrawLine( dpy, win, gc, 0, 0, win_width, win_height ); /* 線を描く */
0052: XFlush( dpy ); /* バッファのフラッシュ */
0053: break;
0054: case KeyPress:
0055: key_sym = XKeycodeToKeysym( dpy, event.xkey.keycode, 0 );
0056: if( key_sym == XK_Escape ){
0057: XDestroyWindow( dpy, win );
0058: exit(0);
0059: }
0060: break;
0061: default:
0062: ;
0063: }
0064: }
0065: }
0034: XSelectInput( dpy, win, ExposureMask|KeyPressMask|StructureNotifyMask);
XSelectInputにStructureNotifyMaskを設定します.これによってリサイ ズ時に発生するConfigureNotifyイベントを拾うこ とができるようになります.
0047: case ConfigureNotify: 0048: XGetGeometry( dpy, win, &root, 0049: &dummy, &dummy, &win_width, &win_height, 0050: &dummy, &dummy ); 0051: XDrawLine( dpy, win, gc, 0, 0, win_width, win_height ); /* 線を描く */ 0052: XFlush( dpy ); /* バッファのフラッシュ */ 0053: break;
ConfigureNotifyイベントが発生したらウインドウ のサイズを取得し,また線を描き直します.
Status XGetGeometry(display, d, root_return, x_return, y_return, width_return,
height_return, border_width_return, depth_return)
Display *display;
Drawable d;
Window *root_return;
int *x_return, *y_return;
unsigned int *width_return, *height_return;
unsigned int *border_width_return;
unsigned int *depth_return;
XGetGeometryはdisplay上の,指定したDrawabledに関するいろいろな属性が返ります.ここで必要なのは ウインドウの幅と高さだけなので,必要のない値はdummyで受け取っています. そして,新たな幅と高さのウインドウに対して対角線を引いています.
さて,普通はウインドウマネージャが動いている環境だと思います.たいていは, タイトルバーに終了ボタンがついていることと思います.今までのプログラムを ウインドウマネージャから終了すると,以下のようなメッセージがでると思いま す.
X connection to xxxx broken (explicit kill or server shutdown).
これは,サーバから強制的に終了させられたことを意味します.実はウインドウ マネージャはプログラムに対して終了することを通知をすることができるのです が,これまではその処理をしていないためにこのようなメッセージが出てしまい ます.これを出ないようにしてみます.
0001: /*
0002: key.c
0003: gcc key.c -I/usr/X11R6/include -L/usr/X11R6/lib -lX11
0004: */
0005:
0006: #include <X11/Xlib.h>
0007: #include <X11/keysym.h>
0008:
0009: #define DEFAULT_SIZE 256
0010:
0011: int main( int argc, char *argv[] )
0012: {
0013: Display *dpy;
0014: int screen;
0015: Window win, root;
0016: GC gc;
0017: unsigned int win_width=DEFAULT_SIZE, win_height=DEFAULT_SIZE;
0018: unsigned int dummy;
0019: XEvent event;
0020: KeySym key_sym;
0021: Atom atom1, atom2;
0022:
0023: dpy = XOpenDisplay( NULL ); /* Xサーバに接続する */
0024:
0025: screen = DefaultScreen( dpy ); /* スクリーン設定 */
0026:
0027: win = XCreateSimpleWindow( /* ウィンドウを開く */
0028: dpy, /* Xサーバ */
0029: RootWindow(dpy,screen), /* 親ウィンドウ */
0030: 0, 0, /* 表示時の左上隅の座標 x, y */
0031: win_width, win_height, /* ウィンドウの幅と高さ */
0032: 1, BlackPixel(dpy,screen),/* ボーダーの幅と色 */
0033: WhitePixel(dpy,screen) /* ウィンドウの背景色 */
0034: );
0035: XSelectInput( dpy, win, ExposureMask|KeyPressMask|StructureNotifyMask);
0036: XMapWindow( dpy, win ); /* ウィンドウのマッピング */
0037:
0038: gc = DefaultGC( dpy, screen ); /* グラフィックコンテキストの設定 */
0039: XSetForeground( dpy, gc, BlackPixel(dpy,screen) ); /* 描画色設定 */
0040:
0041: atom1 = XInternAtom( dpy, "WM_PROTOCOLS", False );
0042: atom2 = XInternAtom( dpy, "WM_DELETE_WINDOW", False );
0043: XSetWMProtocols( dpy, win, &atom2, 1 );
0044:
0045: while( 1 ){
0046: XNextEvent( dpy, &event );
0047: switch( event.type ){
0048: case Expose:
0049: XDrawLine( dpy, win, gc, 0, 0, win_width, win_height ); /* 線を描く */
0050: XFlush( dpy ); /* バッファのフラッシュ */
0051: break;
0052: case ConfigureNotify:
0053: XGetGeometry( dpy, win, &root,
0054: &dummy, &dummy, &win_width, &win_height,
0055: &dummy, &dummy );
0056: XDrawLine( dpy, win, gc, 0, 0, win_width, win_height ); /* 線を描く */
0057: XFlush( dpy ); /* バッファのフラッシュ */
0058: break;
0059: case KeyPress:
0060: key_sym = XKeycodeToKeysym( dpy, event.xkey.keycode, 0 );
0061: if( key_sym == XK_Escape ){
0062: XDestroyWindow( dpy, win );
0063: }
0064: break;
0065: case ClientMessage:
0066: if( event.xclient.message_type == atom1 &&
0067: event.xclient.data.l[0] == atom2 ){
0068: XDestroyWindow( dpy, win );
0069: }
0070: break;
0071: case DestroyNotify:
0072: XCloseDisplay( dpy );
0073: exit(0);
0074: break;
0075: default:
0076: ;
0077: }
0078: }
0079: }
まず21行目にAtomというのが出てきます.これは X11/X.hで以下のように定義されます.
typedef unsigned long Atom; /* Also in Xdefs.h */
これもまたある数値を表しているに過ぎません.通信に用いるデータや型を区別 するための識別番号をアトムといいます.アトムには名前をつけることができ, 同じサーバ上では,同じ名前に対しては常に同じ番号が割り当てられるようになっ ています.これは41行目からで使われます.
0041: atom1 = XInternAtom( dpy, "WM_PROTOCOLS", False ); 0042: atom2 = XInternAtom( dpy, "WM_DELETE_WINDOW", False ); 0043: XSetWMProtocols( dpy, win, &atom2, 1 );
Atom XInternAtom(display, atom_name, only_if_exists)
Display *display;
char *atom_name;
Bool only_if_exists;
XInternAtomはdisplay上のatom_nameに対 する識別番号を返します.only_if_existsがTrueの場合はatom_nameが 存在した時だけ値を返します.ない場合に何が返されるかは実装依存です. Falseの場合は,ない場合には新しいアトムが作成 されます.ここでは"WM_PROTOCOLS"と "WM_DELETE_WINDOW"というアトムの値 を求めています.
Status XSetWMProtocols(display, w, protocols, count)
Display *display;
Window w;
Atom *protocols;
int count;
ウインドウマネージャはいくつかの決められたプロパティを持っています. "WM_PROTOCOLS"プロパティもそのうち の一つです. XSetWMProtocolsはdisplay上のウインドウwの "WM_PROTOCOLS"プロパティをprotocolsで置き換えます.アトムの配列を渡すことができ るので,引数はポインタになっており,その個数をcount に指定します.プロパティがない場合は新たに設定されます.成功すると0が返 ります.
43行目では,"WM_DELETE_WINDOW"を wに設定しています.
0065: case ClientMessage:
0066: if( event.xclient.message_type == atom1 &&
0067: event.xclient.data.l[0] == atom2 ){
0068: XDestroyWindow( dpy, win );
0069: }
0070: break;
クライアント間通信のイベントはClientMessageで 受け取ります.ここでは,"WM_PROTOCOLS"の"WM_DELETE_WINDOW"が発生したかを確認して います.ウインドウマネージャの終了ボタンが押されるとこれが発生します. これを受け取ったらXDestroyWindowでウインド ウを破棄します.
このプログラムではEscapeキーが押された場合とウインドウマネージャから終了 した場合が考えられるので,それぞれの処理でXDestroyWindowをコールし,それにともないDestroyNotifyが発生したのを受けてXCloseDisplayで接続を切り,プログラムを終了しま す.