mieki256's diary



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 は色々と複雑な処理をしているように見えるので、ガシガシ削って、ダブルバッファ描画処理の骨の部分だけを残して、更に、画面の中でボール(円)が跳ね回るだけの処理を書いてみた。

実行結果は以下のような感じ。どこでもよく見かけるアレ。

ソースファイル。 :

ソースは以下。長い。長いけど、「ball」で検索すれば、肝心の処理部分だけがなんとなく見えてくるかなと。それ以外はダブルバッファ描画関係の処理なので、極端な話、気にしなくいい。ダブルバッファ処理をしたいなら、まるっとコピペして、後は独自の処理だけを書き加えればいいのではなかろうかと。

_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 には、コマンドラインオプションを列挙する。

そして、一番最後の行に、以下を書く。

XSCREENSAVER_MODULE("Helloxsaver", helloxsaver)

もし、hoge という名前のスクリーンセーバを書くなら、最後の行は以下になるはず。
XSCREENSAVER_MODULE("Hoge", hoge)

ちなみに、xscreensaver用のスクリーンセーバとして公開する時は、*.c、*.man、*.xml 内の日付(年)や YOURNAME の部分を自分のソレに書き換えるようにと、 _README.hacking には書かれていた。

もう一つ書いてみた。 :

helloxsaver2 という名前でもう一つ書いてみた。実行結果は以下。ボールが複数跳ね回る例のアレ。



ソースファイル等は以下。

_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 あたりがソレっぽいかなと眺めてみたけど、何が何だか。

画像の扱い方について。 :

画像の扱い方について調べてたのだけど、少し分かってきた。

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 に変換できそう。たぶん。

以上、1 日分です。

過去ログ表示

Prev - 2022/05 - Next
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project