こんにちは。
私はD言語で書く「Xlib」について書かせていただきたいと思います。
それでは早速...そもそもの話、Xlib って何なのでしょうか。
##Xlibとは
X Window System というUNIX-OSのGUIを司るプロコトルがあって、XlibはCで記述された、その底辺ライブラリです。「底辺」と聞くと聞こえが悪いですが、要するにQtやGTK+など上位ツールキットの踏み台となっているのです。
それを直接書くということは、Win32APIを叩くと思うと分かりやすいかもしれません。でもたぶん、それよりずっと簡単。
##何でXlibなの
QtやGTK+がXlibを踏み台にしていると言ったように、その中身を知るためにはXlibの知識が欠かせません。また、Xlibの書籍を読んでみるとX Window System との通信について細かく触れられることがよくあります。底辺ゆえですかね。しかし、書籍は古いのしかなく、図書館に行って求めるぐらいでしか簡単に手に入らなくなっているのが現状です。
なので今からXlibを使ってアプリ開発をしよう! という流れにはならないと思います。要するに学習用です。しかし、簡単なプログラムを書くのは楽しいかもしれません。私は「oneko」というジョークプログラムからXlibの存在を知ったのですが、ソースを覗いてみるとなかなか楽しいものがあります。今回は、「oneko」に近づく観点からXlibに迫って行きたいと思います。
##でもそろそろDの話を...
D言語 Advent Calendar 1日目 でrepeatedly さんからお話があったように、Cラッパーのプロジェクトに「Deimos」というものがあり、Member には、repeatedly さんとshoo さんが参加されています。XlibのDバインディングはこの中にあります。
さて、遅くなりましたがコードの方を見てみます。まずはウィンドウを表示するだけ。GUIにおける"Hello World."ですね。Cのコードと比較しながら、Dで書く際の注意点を見ていきます。
import deimos.X11.X;
import deimos.X11.Xlib;
import deimos.X11.Xutil;
void main()
{
Display* dpy;
Window root;
Window win;
int screen;
uint black, white;
XEvent e;
dpy = XOpenDisplay(null);
root = DefaultRootWindow(*dpy);
screen = DefaultScreen(*dpy);
white = WhitePixel(*dpy, screen);
black = BlackPixel(*dpy, screen);
win = XCreateSimpleWindow(dpy, root, 32, 32, 64, 64, 2, black, white);
XSelectInput(dpy, win, EventMask.KeyPressMask);
XMapWindow(dpy, win);
XMoveWindow(dpy, win, 32, 32);
XFlush(dpy);
while(true){
XNextEvent(dpy, &e);
switch(e.type){
case EventType.KeyPress:
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
return;
default:
}
}
}
#include <X11/Xlib.h>
#include <X11/Xutil.h>
int main(){
Display *dpy;
Window root;
Window win;
int screen;
unsigned long black, white;
XEvent e;
dpy = XOpenDisplay("");
root = DefaultRootWindow(dpy);
screen = DefaultScreen(dpy);
white = WhitePixel(dpy, screen);
black = BlackPixel(dpy, screen);
win = XCreateSimpleWindow(dpy, root, 32, 32, 64, 64, 2, black, white);
XSelectInput(dpy, win, KeyPressMask);
XMapWindow(dpy, win);
XMoveWindow(dpy, win, 32, 32);
XFlush(dpy);
while(1){
XNextEvent(dpy, &e);
switch(e.type){
case KeyPress:
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
return 0;
}
}
}
と言っても2つくらいです。
まずimport部分です。Cの#includeはpublicに対してDのimportはprivateなので、CのX.hに当たる部分も取ってくる必要があります。
次に定数です。Deimos のlibX11に記述されているenumは名前付き列挙体なので、その定数の型を調べてくる必要があります。
...付け足すとしたらバグがあるところです。現在、ある関数群は使うとSIGSEGVをもらってしまいます。
Program received signal SIGSEGV, Segmentation fault. 0x00000000 in ?? ()
これはgdbでの結果です。今原因を考えているのですが、いまいち解決にまで至っていません。もし分かる方がいれば投げてくれると嬉しいです。
make時のバグもあったりで、自分はひとつのみ訂正したのですが、他の方にも見ていただけるといいなあ、というのが個人的な感想です。寂しい開発速度です。
また、バグではないのですが、まだ中身が充実していない印象です。extensionsのshapeもないので、「oneko」も書けない状況です。これに関しては、サードパーティにお任せしているみたいなので、どしどし貢献したいです。
コンパイルについてですが、make時に生成されたPKG-CONFIG用ファイルは、dmdには合っていません。他のコンパイラについてはよく分かっていないのですが、こんな風に直します。
Libs: -L/usr/local/lib -L-lDX11-dmd
これを
Libs: -L-L/usr/local/lib -L-lDX11-dmd
こう(本来はMakefileから直すべきですが...)。
そうすれば、dmd main.d `pkg-config --libs --clags DX11` -L-lX11 でコンパイルできます。
##簡単なジョークプログラムの例
「oneko」について考えてみましょう。xbmフォーマットから猫の画像を持ってきて、マウスを追いかけます。xbmフォーマットは、Cからincludeするのにぴったりなフォーマットの形ですが、Dで利用する場合どのように書けるでしょうか。
今回、D言語マスコットキャラクタがマウスを追いかけてくるジョークアプリを考えてみます(深夜に動作確認したのですがすごく怖かったです...)。
カラー情報が失われると寂しいので、xpmフォーマットで行います。最もpngやjpegなどの一般的な画像フォーマットで行うのが適当なのでしょうが、堪忍してください。
D言語で扱う場合、結局moduleとして扱わないといけないので、思い切って書きなおしてしまいます。
/* XPM */
static char * d3_xpm[] = {
"162 309 4 1",
" c None",
". c #000000",
"+ c #FFFFFF",
"@ c #FF0000",
"
/*...(ずっと続いていく*/
これを、
/* XPM */
module resorce; // ココ
//ココ
static string[] d_image = [
"162 309 4 1",
" c None",
". c #000000",
"+ c #FFFFFF",
"@ c #FF0000",
""
// ずっと続いていく
こう書き換えてしまいます。
次に利用する側のプログラムです。
import deimos.X11.X;
import deimos.X11.Xlib;
import deimos.X11.Xutil;
// 画像の読み込み
import resorce : d_image;
extern(System){
// 自作関数で使う
struct Hints{
ulong flags;
ulong functions;
ulong decorations;
long inputMode;
ulong status;
}
const Atom XA_CARDINAL = cast(Atom)6;
}
import std.stdio : chomp;
import std.string : split;
import std.conv : to;
import std.math;
import std.range;
import std.array;
// 渡されたwinに直接描画してしまう。
void drawImage(Display* dpy, Window win, GC gc, string[] xpm){
// ヘッダ情報を入手する。
string[] head = xpm[0].chomp.split;
int width = head[0].to!int;
int height = head[1].to!int;
int num = head[2].to!int;
uint clist[char]; // 色の連想配列
Colormap cmap = DefaultColormap(*dpy, DefaultScreen(*dpy));
// clist の初期化
for(int i=1; i<=num; ++i){
string[] str = xpm[i].chomp.split;
XColor color, exact;
if(str.length<3)continue;
char c = str.front[0];
str.popFront; str.popFront;
string name = str.front;
str.popFront;
assert(str.length==0);
XAllocNamedColor(dpy, cmap, name.toCString, &color, &exact);
uint d_color = color.pixel;
clist[c] = d_color;
}
// win への描画。1画素ずつ見ていく
for(int y=0; y<height; ++y){
for(int x=0; x<width; ++x){
char c = xpm[(num+1)+y][x];
if((c in clist) != null){
uint d_color = clist[c];
XSetForeground(dpy, gc, d_color);
XDrawPoint(dpy, win, gc, x, y);
}
}
}
}
bool isEventTime(Display* dpy, EventType trg){
XEvent e;
XNextEvent(dpy, &e);
return e.type == trg;
}
void main(){
Display *dpy = XOpenDisplay(null);
Window root = DefaultRootWindow(*dpy);
uint white = WhitePixel(*dpy, DefaultScreen(*dpy));
Window win = XCreateSimpleWindow(dpy, root, 32, 32, 162, 309, 0, white, white);
GC gc = XCreateGC(dpy, win, 0, null);
// ウィンドウ装飾はウィンドウマネージャが勝手につけてくれたものです。今回は邪魔になるので自作関数にて取り除きます。
escape_decoration(dpy, win);
XSelectInput(dpy, win, EventMask.StructureNotifyMask);
XMapWindow(dpy, win);
// 最初のマップ時には、ウィンドウマネージャによって座標が決まるので、もう一回動かす必要がある。
XMoveWindow(dpy, win, 32, 32);
XFlush(dpy);
// マップが完了されるまで待つ
while(!isEventTime(dpy, EventType.MapNotify)){
}
while(true){
drawImage(dpy, win, gc, d_image);
XFlush(dpy);
// マウス位置の取得
Window q_root, q_child;
int AbsX, AbsY;
int RelX, RelY;
uint ModKeyMask;
XQueryPointer(dpy, win, &q_root, &q_child, &AbsX, &AbsY, &RelX, &RelY, &ModKeyMask);
// ウィンドウ位置の取得
int x, y;
uint w, h, b, d;
XGetGeometry(dpy, win, &root, &x, &y, &w, &h, &b, &d);
double[2] v = [cast(double)(AbsX-(x+cast(int)w/2)), cast(double)(AbsY-(y+cast(int)h/2))]; // 画像からマウスへの方向ベクトル
// 近づきすぎたら終了。逃げろ!
if(v.d < w/8.){
break;
}
v[]/=v.d; // 単位ベクトルを求める
v[]*=8.; // 距離8pixelぐらいの速さ?
int dx = cast(int)v[0];
int dy = cast(int)v[1];
XMoveWindow(dpy, win, x+dx, y+dy);
}
// リソースの開放
XFreeGC(dpy, gc);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
}
double d(double[2] v){
return sqrt((v[0]*v[0] + v[1]*v[1]));
}
extern(System){
// SIGSEGV をもらってしまう関数の一部(使うので足す)
Window DefaultRootWindow(ref Display dpy){
return ScreenOfDisplay( dpy,DefaultScreen( dpy ) ).root;
}
uint WhitePixel(ref Display dpy,int scr){
return cast(uint)ScreenOfDisplay( dpy,scr ).white_pixel;
}
Colormap DefaultColormap(ref Display dpy, int scr){
return ScreenOfDisplay(dpy, scr).cmap;
}
}
void escape_decoration(Display* dpy, Window win){
Hints hints;
Atom property;
hints.flags = 2;
hints.decorations = 0;
property = XInternAtom(dpy, "_MOTIF_WM_HINTS".toCString, Bool.True);
debug { writefln("%x", property); }
XChangeProperty(dpy, win, property, property, 32, PropertyMode.PropModeReplace, cast(ubyte *)&hints, 5);
}
void set_opacity(Display* dpy, Window win, double opacity){
Atom property = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY".toCString, Bool.False);
const uint OPACITY = 0xffff_ffff;
uint real_opacity = cast(uint)(OPACITY * opacity);
XChangeProperty(dpy, win, property, XA_CARDINAL, 32, PropertyMode.PropModeReplace, cast(ubyte *)&real_opacity, 1);
}
char* toCString(string str){
return (str ~ "\0").dup.ptr;
}
...特にD言語らしい書き方をしているわけでもなく申し訳ないです。でも連想配列やベクトル演算がささっとかけてしまうのは気持ちがいいですね。すぐ頼ってしまいます。
また、バインディングはCのライブラリ関数を呼び出す関係でexturn宣言しか見えない関数もあり、CTFEができません。うぐぐ。
##終わりに
D言語から入るXlib入門ということでしたが、いかがだったでしょうか。少しでも興味を持っていただけたら幸いです。実際に関数のひとつひとつの動きを見るなら、こちらのサイトなどがおすすめです(もちろんC)。他にも調べると結構出てくるので、やってみたい方はぜひぜひ。
また、Xlibに限らずDeimosプロジェクトはひっそりしている印象があるので、(たぶんまだ使い勝手は良くないと思うので)開発が盛り上がるといいなあ、と思います。
明日、19日は@9rnsrさんです!