2022/05/29(日) [n年前の日記]
#1 [xscreensaver] xscreensaver用のスクリーンセーバを作りたい。その2
_昨日
に続いて、xscreensaver用のスクリーンセーバを自分で作れないか実験中。
_README.hacking に、新しいスクリーンセーバを作る際に意識することが書かれてあるので、眺めながら作業。
環境は Ubuntu Linxu 20.04 LTS。
とりあえず、hacks/ディレクトリ内で、deluxe.c、deluxe.man、config/deluxe.xml をコピーして、helloxsaver.c、helloxsaver.man、config/helloxsaver.xml の3ファイルを作ったけれど。まだ中身が deluxe.c のままだし、deluxe.c は色々と複雑な処理をしているように見えるので、ガシガシ削って、ダブルバッファ描画処理の骨の部分だけを残して、更に、画面の中でボール(円)が跳ね回るだけの処理を書いてみた。
実行結果は以下のような感じ。どこでもよく見かけるアレ。
_README.hacking に、新しいスクリーンセーバを作る際に意識することが書かれてあるので、眺めながら作業。
環境は Ubuntu Linxu 20.04 LTS。
とりあえず、hacks/ディレクトリ内で、deluxe.c、deluxe.man、config/deluxe.xml をコピーして、helloxsaver.c、helloxsaver.man、config/helloxsaver.xml の3ファイルを作ったけれど。まだ中身が deluxe.c のままだし、deluxe.c は色々と複雑な処理をしているように見えるので、ガシガシ削って、ダブルバッファ描画処理の骨の部分だけを残して、更に、画面の中でボール(円)が跳ね回るだけの処理を書いてみた。
実行結果は以下のような感じ。どこでもよく見かけるアレ。
◎ ソースファイル。 :
ソースは以下。長い。長いけど、「ball」で検索すれば、肝心の処理部分だけがなんとなく見えてくるかなと。それ以外はダブルバッファ描画関係の処理なので、極端な話、気にしなくいい。ダブルバッファ処理をしたいなら、まるっとコピペして、後は独自の処理だけを書き加えればいいのではなかろうかと。
_helloxsaver.c
マニュアルファイル(.man)と、設定画面定義ファイル(.xml) は以下。
_helloxsaver.man
_helloxsaver.xml
make でビルド。sudo make install でインストールして、xscreensaverの設定画面を出して動作確認。
_helloxsaver.c
/* xscreensaver, Copyright (c) 2022 YOURNAME <YOURNAME@example.com> * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ #include <math.h> #include "screenhack.h" #include "alpha.h" #ifdef HAVE_DOUBLE_BUFFER_EXTENSION #include "xdbe.h" #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ #define PI 3.141592 /* degree to radian */ static double deg2rad(double angle) { return (angle * PI / 180.0); } /* screensaver state */ struct state { Display *display; Window window; int nplanes; unsigned long base_pixel; unsigned long *plane_masks; Bool monochrome; Bool use_dbuf; int delay; int speed; double x, y; /* ball position */ double dx, dy; /* ball direction */ int w, h; /* ball width, height */ GC gc; /* graphics context */ XWindowAttributes xgwa; XColor color; GC erase_gc; /* double-buffer to reduce flicker */ Pixmap backbuf; Pixmap ba; Pixmap bb; #ifdef HAVE_DOUBLE_BUFFER_EXTENSION Bool dbeclear_p; XdbeBackBuffer backb; #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ }; /* initialize ball position */ static void init_ball_pos(struct state *st) { int sw, sh; sw = st->xgwa.width; /* window width */ sh = st->xgwa.height; /* window height */ /* set width and height */ st->w = st->h = ((sw > sh) ? sh : sw) / 16; /* set position */ st->x = (random() % (sw / 2)) + (sw / 4); st->y = (random() % (sh / 2)) + (sh / 4); /* set direction */ { double rad; rad = deg2rad(random() % 360); st->dx = st->speed * cos(rad); st->dy = st->speed * sin(rad); } } /* * init screensaver. * Return an object holding your global state. */ static void * helloxsaver_init(Display *display, Window window) { struct state *st = (struct state *)calloc(1, sizeof(*st)); st->display = display; st->window = window; /* get parameter */ st->delay = get_integer_resource(st->display, "delay", "Integer"); st->speed = get_integer_resource(st->display, "speed", "Speed"); st->use_dbuf = get_boolean_resource(st->display, "doubleBuffer", "Boolean"); st->monochrome = get_boolean_resource(st->display, "mono", "Boolean"); #ifdef HAVE_DOUBLE_BUFFER_EXTENSION st->dbeclear_p = get_boolean_resource(st->display, "useDBEClear", "Boolean"); #endif #ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */ st->use_dbuf = False; #endif /* get window attributes */ XGetWindowAttributes(st->display, st->window, &st->xgwa); /* define colors */ st->color.pixel = get_pixel_resource(st->display, st->xgwa.colormap, "foreground", "Foreground"); if (st->use_dbuf) { /* init double buffer */ #ifdef HAVE_DOUBLE_BUFFER_EXTENSION if (st->dbeclear_p) st->backbuf = xdbe_get_backbuffer(st->display, st->window, XdbeBackground); else st->backbuf = xdbe_get_backbuffer(st->display, st->window, XdbeUndefined); st->backb = st->backbuf; #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ if (!st->backbuf) { /* create buffer A, B */ st->ba = XCreatePixmap(st->display, st->window, st->xgwa.width, st->xgwa.height, st->xgwa.depth); st->bb = XCreatePixmap(st->display, st->window, st->xgwa.width, st->xgwa.height, st->xgwa.depth); st->backbuf = st->ba; } } else { st->backbuf = st->window; } /* init graphics context. use erase. */ { XGCValues gcv; gcv.foreground = get_pixel_resource(st->display, st->xgwa.colormap, "background", "Background"); st->erase_gc = XCreateGC(st->display, st->backbuf, GCForeground, &gcv); } /* clear buffer A, B */ if (st->ba) XFillRectangle(st->display, st->ba, st->erase_gc, 0, 0, st->xgwa.width, st->xgwa.height); if (st->bb) XFillRectangle(st->display, st->bb, st->erase_gc, 0, 0, st->xgwa.width, st->xgwa.height); /* initialize ball position */ init_ball_pos(st); /* init graphics context */ { XGCValues gcv; unsigned long flags; flags = GCForeground; gcv.foreground = st->color.pixel; gcv.line_width = 2; gcv.cap_style = CapProjecting; gcv.join_style = JoinMiter; flags |= (GCLineWidth | GCCapStyle | GCJoinStyle); st->gc = XCreateGC(st->display, window, flags, &gcv); } return st; } /* Draw a single frame */ static unsigned long helloxsaver_draw(Display *display, Window window, void *closure) { struct state *st = (struct state *)closure; /* clear double buffer */ #ifdef HAVE_DOUBLE_BUFFER_EXTENSION if (!st->dbeclear_p || !st->backb) XFillRectangle(st->display, st->backbuf, st->erase_gc, 0, 0, st->xgwa.width, st->xgwa.height); #else XFillRectangle(st->display, st->backbuf, st->erase_gc, 0, 0, st->xgwa.width, st->xgwa.height); #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ /* ball move and draw */ { /* draw circle */ XFillArc(st->display, st->backbuf, st->gc, st->x, st->y, st->w, st->h, 0, 360 * 64); /* move */ st->x += st->dx; st->y += st->dy; /* change directon */ if (st->x <= 0 || (st->x + st->w) >= st->xgwa.width) st->dx *= -1; if (st->y <= 0 || (st->y + st->h) >= st->xgwa.height) st->dy *= -1; } /* draw double buffer */ #ifdef HAVE_DOUBLE_BUFFER_EXTENSION if (st->backb) { XdbeSwapInfo info[1]; info[0].swap_window = st->window; info[0].swap_action = (st->dbeclear_p ? XdbeBackground : XdbeUndefined); XdbeSwapBuffers(st->display, info, 1); } else #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ if (st->use_dbuf) { XCopyArea(st->display, st->backbuf, st->window, st->erase_gc, 0, 0, st->xgwa.width, st->xgwa.height, 0, 0); st->backbuf = (st->backbuf == st->ba ? st->bb : st->ba); } return st->delay; } /* Called when the window is resized. */ static void helloxsaver_reshape(Display *display, Window window, void *closure, unsigned int w, unsigned int h) { struct state *st = (struct state *)closure; if (!st->use_dbuf) { /* #### more complicated if we have a back buffer... */ XGetWindowAttributes(st->display, st->window, &st->xgwa); XClearWindow(display, window); init_ball_pos(st); } } /* Called when a keyboard or mouse event happens. */ static Bool helloxsaver_event(Display *display, Window window, void *closure, XEvent *event) { return False; } /* Free everything you've allocated. */ static void helloxsaver_free(Display *display, Window window, void *closure) { struct state *st = (struct state *)closure; XFreeGC(display, st->erase_gc); if (st->ba) XFreePixmap(display, st->ba); if (st->bb) XFreePixmap(display, st->bb); if (st->plane_masks) free(st->plane_masks); XFreeGC(display, st->gc); free(st); } /* Default values for the resources you use. */ static const char *helloxsaver_defaults[] = { ".background: black", ".foreground: white", "*delay: 10000", "*speed: 15", "*doubleBuffer: True", #ifdef HAVE_DOUBLE_BUFFER_EXTENSION "*useDBE: True", "*useDBEClear: True", #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ #ifdef HAVE_MOBILE "*ignoreRotation: True", #endif 0}; /* The command-line options you accept. */ static XrmOptionDescRec helloxsaver_options[] = { {"-delay", ".delay", XrmoptionSepArg, 0}, {"-speed", ".speed", XrmoptionSepArg, 0}, {"-db", ".doubleBuffer", XrmoptionNoArg, "True"}, {"-no-db", ".doubleBuffer", XrmoptionNoArg, "False"}, {0, 0, 0, 0}}; /* * The last line of the file should be * XSCREENSAVER_MODULE ("YourSaverName", yoursavername) */ XSCREENSAVER_MODULE("Helloxsaver", helloxsaver)
マニュアルファイル(.man)と、設定画面定義ファイル(.xml) は以下。
_helloxsaver.man
_helloxsaver.xml
- *.c, *.man は、hacks/ 以下にコピー。
- *.xml は、hacks/config/ 以下にコピー。
make でビルド。sudo make install でインストールして、xscreensaverの設定画面を出して動作確認。
make sudo make install xscreensaver-settings &
◎ 少し解説。 :
最初のあたりで state という構造体を定義している。これは、このスクリーンセーバが動いてる最中、ずっと保持されるべきワークエリアの定義。動作に必要な変数は、全てこの state の中に入れておくことになる。
helloxsaver_init() は初期化処理を担当する。state 分のメモリ領域を確保・初期化して、関数を抜けるときに return st; で、呼び出し元に state のポインタを返している。xscreensaver本体は、この返されたメモリ領域を、スクリーンセーバが動いてる最中、ずっと保持しているのだろう。たぶん。
helloxsaver_draw() は、1フレーム分の描画を担当する関数。引数として state のポインタが渡されているので、そのワークエリアを使って処理をする。
helloxsaver_reshape() は、ウインドウサイズが変更された時に呼ばれるらしい。ここでは、ウインドウの情報(横幅、縦幅等々)を取得し直して、画面クリアと、ボールの位置を初期化してる。
helloxsaver_event() は、キーボード等が押された時に呼ばれるらしいが、return False; だけを書いておけばいい。
helloxsaver_free() は、ワークエリアの開放処理を書く。
グローバル変数、helloxsaver_defaults は、パラメータのデフォルト値を列挙。helloxsaver_options には、コマンドラインオプションを列挙する。
そして、一番最後の行に、以下を書く。
もし、hoge という名前のスクリーンセーバを書くなら、最後の行は以下になるはず。
ちなみに、xscreensaver用のスクリーンセーバとして公開する時は、*.c、*.man、*.xml 内の日付(年)や YOURNAME の部分を自分のソレに書き換えるようにと、 _README.hacking には書かれていた。
helloxsaver_init() は初期化処理を担当する。state 分のメモリ領域を確保・初期化して、関数を抜けるときに return st; で、呼び出し元に state のポインタを返している。xscreensaver本体は、この返されたメモリ領域を、スクリーンセーバが動いてる最中、ずっと保持しているのだろう。たぶん。
helloxsaver_draw() は、1フレーム分の描画を担当する関数。引数として state のポインタが渡されているので、そのワークエリアを使って処理をする。
helloxsaver_reshape() は、ウインドウサイズが変更された時に呼ばれるらしい。ここでは、ウインドウの情報(横幅、縦幅等々)を取得し直して、画面クリアと、ボールの位置を初期化してる。
helloxsaver_event() は、キーボード等が押された時に呼ばれるらしいが、return False; だけを書いておけばいい。
helloxsaver_free() は、ワークエリアの開放処理を書く。
グローバル変数、helloxsaver_defaults は、パラメータのデフォルト値を列挙。helloxsaver_options には、コマンドラインオプションを列挙する。
そして、一番最後の行に、以下を書く。
XSCREENSAVER_MODULE("Helloxsaver", helloxsaver)
もし、hoge という名前のスクリーンセーバを書くなら、最後の行は以下になるはず。
XSCREENSAVER_MODULE("Hoge", hoge)
ちなみに、xscreensaver用のスクリーンセーバとして公開する時は、*.c、*.man、*.xml 内の日付(年)や YOURNAME の部分を自分のソレに書き換えるようにと、 _README.hacking には書かれていた。
◎ もう一つ書いてみた。 :
helloxsaver2 という名前でもう一つ書いてみた。実行結果は以下。ボールが複数跳ね回る例のアレ。
ソースファイル等は以下。
_helloxsaver2.c
_helloxsaver2.man
_helloxsaver2.xml
ファイルが増えたので、以下の2つを修正する必要がある。
_Makefile.in.patch
_XScreenSaver.ad.in.patch
各所に、helloxsaver2.* を追加する。
修正できたら、以下を実行して、Makefile や XScreenSaver.ad を自動生成し直す。
make してビルドして、インストールして、設定画面を出して動作確認。
ソースファイル等は以下。
_helloxsaver2.c
_helloxsaver2.man
_helloxsaver2.xml
ファイルが増えたので、以下の2つを修正する必要がある。
- hacks/Makefile.in
- driver/XScreenSaver.ad.in
_Makefile.in.patch
_XScreenSaver.ad.in.patch
各所に、helloxsaver2.* を追加する。
修正できたら、以下を実行して、Makefile や XScreenSaver.ad を自動生成し直す。
./configure --prefix=/usr --libexecdir=/usr/lib
make してビルドして、インストールして、設定画面を出して動作確認。
make sudo make install xscreensaver-settings &
◎ 課題。 :
X11の描画機能を使って、線のみで円を描いたり、塗り潰しで円を描いたりすることができると分かった。円が描けるなら、おそらく四角や線や点も描けそうな気がする。
ただ、できれば画像を描画したい。最低限、画像の描画さえできれば、大昔の2Dゲームっぽい画面を作れるだろうから、かなり表現の幅が広がるはず。
しかし、画像を描画する方法が分からない…。
C言語のソースに、xpm画像をインポートしてやれば ―― #include "images/hoge.xpm" とでも書いてやれば、実行バイナリに画像データを含めることができるのではないかと思ったけれど、試してみたら xpm画像は扱えないと怒られた。xscreensaver は、xpm関連ライブラリをリンクしているわけではないのかもしれない。
画像を読み込んで描画してそうなスクリーンセーバはどれかなと探してみたけど、これがなかなか…。noseguy.c あたりがソレっぽいかなと眺めてみたけど、何が何だか。
ただ、できれば画像を描画したい。最低限、画像の描画さえできれば、大昔の2Dゲームっぽい画面を作れるだろうから、かなり表現の幅が広がるはず。
しかし、画像を描画する方法が分からない…。
C言語のソースに、xpm画像をインポートしてやれば ―― #include "images/hoge.xpm" とでも書いてやれば、実行バイナリに画像データを含めることができるのではないかと思ったけれど、試してみたら xpm画像は扱えないと怒られた。xscreensaver は、xpm関連ライブラリをリンクしているわけではないのかもしれない。
画像を読み込んで描画してそうなスクリーンセーバはどれかなと探してみたけど、これがなかなか…。noseguy.c あたりがソレっぽいかなと眺めてみたけど、何が何だか。
◎ 画像の扱い方について。 :
画像の扱い方について調べてたのだけど、少し分かってきた。
hacks/images/ の中に、大量の画像が入っている。これらが、xscreensaver が利用する画像なのだろう。
また、hacks/images/Makefile による指定で、.png を .h に変換してることも分かった。ワイルドカード(*.png)を使って対象ファイル群が指定されているので、とにかく hacks/images/ の中にpng画像を入れておけば、それだけで変換処理対象として扱ってくれるらしい。
hacks/images/gen/ の中に、大量の *.h が入ってるけれど、これが、hacks/images/*.png をC言語のソース形式に変換したもの。中では unsigned char で、png画像のバイナリが列挙されている。
さておき。これら、images/gen/*.h を、スクリーンセーバの *.c の中で include すれば、スクリーンセーバの実行バイナリの中に、png画像データもまるっと含まれて、画像データにアクセスできるようになるのだろう。例えば、下記なら、nose-f[1-4].png が実行バイナリに含まれることになる。
画像の読み込みをする場合は、ximage-loader.h を include する模様。
ximage-loader.h の中を覗いたら、一番最初の行に、「ximage-loader.h --- converts XPM data to Pixmaps.」と書いてあった。もしかして、xpm画像も使える…? また、その下で、image_data_to_pixmap()、image_data_to_ximage() 等々、それらしい名前の関数が宣言されていた。これらの関数を使えば、png画像のバイナリを Pixmap や XImage に変換できそう。たぶん。
hacks/images/ の中に、大量の画像が入っている。これらが、xscreensaver が利用する画像なのだろう。
また、hacks/images/Makefile による指定で、.png を .h に変換してることも分かった。ワイルドカード(*.png)を使って対象ファイル群が指定されているので、とにかく hacks/images/ の中にpng画像を入れておけば、それだけで変換処理対象として扱ってくれるらしい。
hacks/images/gen/ の中に、大量の *.h が入ってるけれど、これが、hacks/images/*.png をC言語のソース形式に変換したもの。中では unsigned char で、png画像のバイナリが列挙されている。
static const unsigned char nose_f1_png[] = "\211PNG\015\012\032...\000IEND\256B`\202";
- hacks/images/gen/*.h は、make 時に自動生成されるので、ソースファイル群を解凍した段階では存在していない。
- png画像から *.h への変換は、utils/bin2c というシェルスクリプトで行われている。実際には、bin2c の中で、Perl のワンライナーを指定して変換処理をしている。
さておき。これら、images/gen/*.h を、スクリーンセーバの *.c の中で include すれば、スクリーンセーバの実行バイナリの中に、png画像データもまるっと含まれて、画像データにアクセスできるようになるのだろう。例えば、下記なら、nose-f[1-4].png が実行バイナリに含まれることになる。
#include "images/gen/nose-f1_png.h" #include "images/gen/nose-f2_png.h" #include "images/gen/nose-f3_png.h" #include "images/gen/nose-f4_png.h"
画像の読み込みをする場合は、ximage-loader.h を include する模様。
#include "screenhack.h" #include "ximage-loader.h"
ximage-loader.h の中を覗いたら、一番最初の行に、「ximage-loader.h --- converts XPM data to Pixmaps.」と書いてあった。もしかして、xpm画像も使える…? また、その下で、image_data_to_pixmap()、image_data_to_ximage() 等々、それらしい名前の関数が宣言されていた。これらの関数を使えば、png画像のバイナリを Pixmap や XImage に変換できそう。たぶん。
[ ツッコむ ]
以上です。