カーソル形状の変更
ここでは、マウスカーソルの形状を変更してみます。
※カーソル画像を読み込むのに wayland-cursor のライブラリが必要なので、リンクします。
ウィンドウ (wl_surface) の enter イベントが来るごとに、マウスカーソルを "wait" と "text" の2つに、交互に切り替えます。
カーソルがアニメーション付きの場合は、アニメ処理も行っています。
ウィンドウ内で中ボタンを押すと終了します。
<08_cursor.c>
$ cc -o test 08_cursor.c client.c imagebuf.c -lwayland-client -lwayland-cursor -lrt
※カーソル画像を読み込むのに wayland-cursor のライブラリが必要なので、リンクします。
ウィンドウ (wl_surface) の enter イベントが来るごとに、マウスカーソルを "wait" と "text" の2つに、交互に切り替えます。
カーソルがアニメーション付きの場合は、アニメ処理も行っています。
ウィンドウ内で中ボタンを押すと終了します。
<08_cursor.c>
/****************************** * カーソル形状の変更 * * 中ボタン押しで終了。 ******************************/ #include <stdio.h> #include <string.h> #include <linux/input.h> #include <wayland-client.h> #include <wayland-cursor.h> #include "client.h" #include "imagebuf.h" //------------- typedef struct { Client b; struct wl_cursor_theme *cursor_theme; //カーソルテーマ struct wl_cursor *cursor[2]; //各カーソルデータ struct wl_surface *surface_cursor; //カーソル用サーフェス struct wl_callback *callback; //アニメコールバック uint32_t time_start, //アニメ開始時間 serial_enter; //enter 時の serial int cursor_no; //現在のカーソル番号 }ClientEx; //------------- static void _surface_cursor_frame_callback( void *data,struct wl_callback *callback,uint32_t time); //------------- //---------------------- // カーソル変更 //---------------------- /* カーソル画像変更 * * index: 複数枚ある場合、画像のインデックス */ static void _set_cursor_image(ClientEx *p,int index) { struct wl_buffer *buffer; struct wl_cursor_image *img; img = p->cursor[p->cursor_no]->images[index]; //wl_buffer 取得 buffer = wl_cursor_image_get_buffer(img); if(!buffer) return; // wl_surface_attach(p->surface_cursor, buffer, 0, 0); wl_surface_damage(p->surface_cursor, 0, 0, img->width, img->height); wl_surface_commit(p->surface_cursor); //ポインタのカーソル形状としてセット //[!] ここで、enter 時の serial が必要 wl_pointer_set_cursor(p->b.pointer, p->serial_enter, p->surface_cursor, img->hotspot_x, img->hotspot_y); } /* アニメーションコールバック */ static const struct wl_callback_listener g_cursor_frame_listener = { _surface_cursor_frame_callback }; void _surface_cursor_frame_callback( void *data,struct wl_callback *callback,uint32_t time) { ClientEx *p = (ClientEx *)data; int index,commit = 1; wl_callback_destroy(callback); //コールバック再セット p->callback = wl_surface_frame(p->surface_cursor); wl_callback_add_listener(p->callback, &g_cursor_frame_listener, p); // if(p->time_start == 0) //最初のコールバックの場合、開始時間をセット p->time_start = time; else { //2回目以降の場合、経過時間によって必要な画像を取得し、セット index = wl_cursor_frame(p->cursor[p->cursor_no], time - p->time_start); _set_cursor_image(p, index); commit = 0; } //コールバックを適用 //[!] カーソル画像が変わった時はすでに実行しているので呼ばない if(commit) wl_surface_commit(p->surface_cursor); } /* カーソル形状変更 */ static void _change_cursor(ClientEx *p) { struct wl_cursor *cursor; p->cursor_no ^= 1; cursor = p->cursor[p->cursor_no]; if(cursor) { //wl_callback 破棄 if(p->callback) { wl_callback_destroy(p->callback); p->callback = NULL; } //カーソル画像セット p->time_start = 0; _set_cursor_image(p, 0); //複数枚ある場合、アニメ開始 if(cursor->image_count > 1) { p->callback = wl_surface_frame(p->surface_cursor); wl_callback_add_listener(p->callback, &g_cursor_frame_listener, p); wl_surface_commit(p->surface_cursor); printf("start animation: %d images\n", cursor->image_count); } } } //---------------------- // wl_pointer //---------------------- /* enter */ static void _pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) { ClientEx *p = (ClientEx *)data; p->serial_enter = serial; //カーソル変更 _change_cursor(p); printf("change cursor: %d\n", p->cursor_no); } static void _pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { } static void _pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { } /* ボタン */ static void _pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { //中ボタンで終了 if(button == BTN_MIDDLE && state == WL_POINTER_BUTTON_STATE_PRESSED) CLIENT(data)->finish_loop = 1; } static void _pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static void _pointer_frame(void *data, struct wl_pointer *pointer) { } static void _pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { } static void _pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { } static void _pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete) { } static const struct wl_pointer_listener g_pointer_listener = { _pointer_enter, _pointer_leave, _pointer_motion, _pointer_button, _pointer_axis, _pointer_frame, _pointer_axis_source, _pointer_axis_stop, _pointer_axis_discrete }; //------------------------ /* ClientEx 解放 */ static void _clientex_destroy(Client *cl) { ClientEx *p = (ClientEx *)cl; wl_cursor_theme_destroy(p->cursor_theme); if(p->callback) wl_callback_destroy(p->callback); wl_surface_destroy(p->surface_cursor); } /* カーソル初期化 */ static void _cursor_init(ClientEx *p) { //wl_surface 作成 p->surface_cursor = wl_compositor_create_surface(p->b.compositor); //カーソルテーマ読み込み p->cursor_theme = wl_cursor_theme_load(NULL, 32, p->b.shm); //カーソル読み込み p->cursor[0] = wl_cursor_theme_get_cursor(p->cursor_theme, "text"); p->cursor[1] = wl_cursor_theme_get_cursor(p->cursor_theme, "wait"); if(!p->cursor[0]) printf("not found cursor 'text'\n"); if(!p->cursor[1]) printf("not found cursor 'wait'\n"); } //----------------- int main(void) { ClientEx *p; Window *win; p = (ClientEx *)Client_new(sizeof(ClientEx)); p->b.destroy = _clientex_destroy; p->b.init_flags = INIT_FLAGS_SEAT | INIT_FLAGS_POINTER; p->b.pointer_listener = &g_pointer_listener; Client_init(CLIENT(p)); //カーソル初期化 _cursor_init(p); //ウィンドウ win = Window_create(CLIENT(p), 256, 256, NULL); ImageBuf_fill(win->img, 0xffff0000); Window_update(win); // Client_loop_simple(CLIENT(p)); //解放 Window_destroy(win); Client_destroy(CLIENT(p)); return 0; }
カーソルの読み込み
はじめに
カーソル画像を扱うために、「wayland-cursor.h」のインクルードと、「libwayland-cursor」のリンクが必要です。
wayland-cursor では、/usr/share/icons や ~/.icons のディレクトリに置かれているカーソルテーマから、カーソル画像を読み込んで使うことができます。
wayland-cursor では、/usr/share/icons や ~/.icons のディレクトリに置かれているカーソルテーマから、カーソル画像を読み込んで使うことができます。
カーソルテーマの読み込み
まずは、カーソルテーマを読み込みます。
引数 name には、テーマ名を指定します。
NULL で、デフォルトのテーマ ("default") になります。
テーマ名は、インストールされている各テーマの、先頭ディレクトリ名を指定します ("Adwaita" など)。
index.theme 内で定義されている正式なテーマの名前を指定しても、読み込まれません。
size は、カーソル画像のおおよその px サイズです。
とりあえず、32 にしておけば良いです。
shm は、wl_shm のポインタを指定します。
共有メモリを使って画像を読み込むので、wl_shm が必要になります。
# テーマ読み込み struct wl_cursor_theme *wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm); # 読み込んだテーマを破棄 void wl_cursor_theme_destroy(struct wl_cursor_theme *theme);
引数 name には、テーマ名を指定します。
NULL で、デフォルトのテーマ ("default") になります。
テーマ名は、インストールされている各テーマの、先頭ディレクトリ名を指定します ("Adwaita" など)。
index.theme 内で定義されている正式なテーマの名前を指定しても、読み込まれません。
size は、カーソル画像のおおよその px サイズです。
とりあえず、32 にしておけば良いです。
shm は、wl_shm のポインタを指定します。
共有メモリを使って画像を読み込むので、wl_shm が必要になります。
各カーソルの読み込み (wl_cursor)
wl_cursor_theme から、使用するカーソルを読み込みます。
name は、カーソルの名前です。
テーマの cursors ディレクトリ内にあるファイル名を指定します。
読み込めなかった場合は、NULL が返ります。
通常の矢印カーソルの場合は、「"default", "left_ptr"」などの名前となります。
一つの形状に対して、名前の規則が複数あるので、通常はリンクファイルを使って対応されていますが、テーマによっては、一部の名前しか対応していない場合もあるので、きちんと対応するのであれば、一つのカーソル形状に対して、複数名で読み込みを試すべきです。
※戻り値の wl_cursor は、クライアント側で解放処理を行う必要はありません。
struct wl_cursor *wl_cursor_theme_get_cursor( struct wl_cursor_theme *theme,const char *name);
name は、カーソルの名前です。
テーマの cursors ディレクトリ内にあるファイル名を指定します。
読み込めなかった場合は、NULL が返ります。
通常の矢印カーソルの場合は、「"default", "left_ptr"」などの名前となります。
一つの形状に対して、名前の規則が複数あるので、通常はリンクファイルを使って対応されていますが、テーマによっては、一部の名前しか対応していない場合もあるので、きちんと対応するのであれば、一つのカーソル形状に対して、複数名で読み込みを試すべきです。
※戻り値の wl_cursor は、クライアント側で解放処理を行う必要はありません。
カーソル形状の変更
wl_cursor を元にカーソル形状を変更する場合、まず、カーソル用のイメージとして、wl_surface が必要になります。
このプログラム内では、初期化時に wl_compositor_create_surface() を使って、カーソル画像用の wl_surface を作成しています。
次に、wl_cursor から、必要な wl_cursor_image を取得します。
wl_cursor は直接中身を参照することができます。
アニメーションカーソルの場合は、複数枚の画像のデータがあります。
image_count は、画像の数です。
アニメーションの画像数となり、アニメーションがない場合は「1」となります。
images には、image_count 分の wl_cursor_image のポインタが格納されています。
wl_cursor_image は、各画像の情報です。
「幅」「高さ」「ホットスポット位置」「アニメでその画像を表示する時間」が入っています。
このプログラム内では、初期化時に wl_compositor_create_surface() を使って、カーソル画像用の wl_surface を作成しています。
次に、wl_cursor から、必要な wl_cursor_image を取得します。
wl_cursor は直接中身を参照することができます。
アニメーションカーソルの場合は、複数枚の画像のデータがあります。
wl_cursor 構造体
struct wl_cursor { unsigned int image_count; // 画像の数 struct wl_cursor_image **images; // 各画像 char *name; // カーソル名 }; struct wl_cursor_image { uint32_t width; // 実際の幅 uint32_t height; // 実際の高さ uint32_t hotspot_x; // hotspot x uint32_t hotspot_y; // hotspot y uint32_t delay; // 表示時間 (ms) };
image_count は、画像の数です。
アニメーションの画像数となり、アニメーションがない場合は「1」となります。
images には、image_count 分の wl_cursor_image のポインタが格納されています。
wl_cursor_image は、各画像の情報です。
「幅」「高さ」「ホットスポット位置」「アニメでその画像を表示する時間」が入っています。
wl_buffer 取得
必要な wl_cursor_image を取得したら、そのポインタから、wl_buffer を取得します。
※ここで取得した wl_buffer は、クライアント側で解放してはいけません。
struct wl_buffer *wl_cursor_image_get_buffer(struct wl_cursor_image *image);
※ここで取得した wl_buffer は、クライアント側で解放してはいけません。
wl_surface に wl_buffer を適用
取得した wl_buffer を使って、カーソル画像用の wl_surface にイメージを適用します。
wl_surface_attach(p->cur.surface, buffer, 0, 0); wl_surface_damage(p->cur.surface, 0, 0, img->width, img->height); wl_surface_commit(p->cur.surface);
カーソル形状を変更
カーソル画像の wl_surface が用意できたら、それをカーソル形状としてセットします。
serial は、wl_pointer の enter イベント時に渡された serial 値をそのまま指定します。
hotspot_x, hotspot_y は、ホットスポット位置 (画像内でどの位置を原点とするか) です。
wl_cursor_image の hotspot_x, hotspot_y をそのまま指定します。
void wl_pointer_set_cursor(struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, int32_t hotspot_x, int32_t hotspot_y);
serial は、wl_pointer の enter イベント時に渡された serial 値をそのまま指定します。
hotspot_x, hotspot_y は、ホットスポット位置 (画像内でどの位置を原点とするか) です。
wl_cursor_image の hotspot_x, hotspot_y をそのまま指定します。
アニメーション処理
カーソルのアニメーション処理は、クライアントが自分で行う必要があります。
ここでは、以前にも使った wl_surface_frame() を使用して、コールバックで処理します。
なお、wl_surface_frame() 後は、wl_surface_commit() を実行しないと、コールバックが適用されません。
忘れがちなので、気を付けてください。
まずは、最初のコールバック時に、time 引数から現在の時間を記録し、以降は、「現在時間 - 開始時間」の経過時間から、表示するフレームを取得して、カーソル形状を変更しています。
wl_cursor_image の各 delay 値を参照すれば、自力で計算することもできますが、このような関数も用意されています。
time は、アニメーション開始時点からの総経過時間 (ms) です。
アニメーション全体の時間を超えている場合は、繰り返しループするように処理されます。
戻り値のフレームインデックスは、「0 〜 image_count - 1」の範囲の値です。
そのまま、wl_cursor の images のインデックス値となります。
ここでは、以前にも使った wl_surface_frame() を使用して、コールバックで処理します。
なお、wl_surface_frame() 後は、wl_surface_commit() を実行しないと、コールバックが適用されません。
忘れがちなので、気を付けてください。
まずは、最初のコールバック時に、time 引数から現在の時間を記録し、以降は、「現在時間 - 開始時間」の経過時間から、表示するフレームを取得して、カーソル形状を変更しています。
経過時間からフレーム取得
経過時間を元に、表示するフレームを取得する場合は、以下の関数を使います。# フレームインデックスのみ取得 int wl_cursor_frame(struct wl_cursor *cursor, uint32_t time); # フレームインデックスと残りの表示時間を取得 int wl_cursor_frame_and_duration(struct wl_cursor *_cursor, uint32_t time, uint32_t *duration);
wl_cursor_image の各 delay 値を参照すれば、自力で計算することもできますが、このような関数も用意されています。
time は、アニメーション開始時点からの総経過時間 (ms) です。
アニメーション全体の時間を超えている場合は、繰り返しループするように処理されます。
戻り値のフレームインデックスは、「0 〜 image_count - 1」の範囲の値です。
そのまま、wl_cursor の images のインデックス値となります。
アニメーションの注意点
通常は、カーソルのアニメーションの表示時間は短いので、wl_surface_frame() で処理しても問題はありませんが、次のフレームまでの時間が極端に長い場合は、無駄に何度もコールバックが呼ばれることになります。
(指定時間後に実行するというような扱いができないため)
そうすると無駄に CPU を消費することになるので、frame コールバックだけを使ってアニメーションを行うのは賢明ではありません。
そのため、次のフレームまでの時間が長い場合はタイマーを使うなど、CPU を軽くする方法に切り替えるべきです。
ちなみに、カーソル画像として使う wl_surface で wl_surface_frame() を使った場合、カーソルがウィンドウ内に入っている間だけコールバックが通知されます。
カーソルがウィンドウ外にある場合は、送られてきません。
(指定時間後に実行するというような扱いができないため)
そうすると無駄に CPU を消費することになるので、frame コールバックだけを使ってアニメーションを行うのは賢明ではありません。
そのため、次のフレームまでの時間が長い場合はタイマーを使うなど、CPU を軽くする方法に切り替えるべきです。
ちなみに、カーソル画像として使う wl_surface で wl_surface_frame() を使った場合、カーソルがウィンドウ内に入っている間だけコールバックが通知されます。
カーソルがウィンドウ外にある場合は、送られてきません。