X11: SHAPE 拡張機能

SHAPE 拡張機能
SHAPE 拡張機能を使うことで、ウィンドウを四角形以外の形状にすることができます。
(範囲外を透明にする形になります)

形状については、単純に、複数の矩形座標のリストから生成されるので、縁を半透明にするようなことはできません。

ウィンドウの本来の四角形の範囲内において、指定された領域に含まれていない範囲は、画面上では透明になります。
その範囲では、後ろのウィンドウの内容が表示され、その範囲内で起きたポインタイベントなども、後ろのウィンドウを対象として動作します。
必要なもの
<X11/extensions/shape.h> のインクルードと、-lXext のリンクが必要です。

※ Xext ライブラリは、複数の一般的な拡張機能を含めたライブラリです。
領域
SHAPE 拡張機能においては、各ウィンドウに対して、以下の3つの領域が定義されます。

境界領域 (Bounding)親ウィンドウ内で、このウィンドウが占めている領域 (境界線を含む)。
この範囲外では、ウィンドウ内容が透明になる。
クリップ領域 (Clip)子ウィンドウが表示される領域、また、グラフィックスで描画できる領域。
境界領域内で、かつクリップ領域外にあたる範囲は、そのウィンドウの境界線の領域として扱われる。
入力領域 (Input)ポインタがこの領域にある場合、このウィンドウ上にポインタが存在するものとする。

※「ウィンドウの境界線」とは、ウィンドウ属性の border_pixel などで設定できる、ウィンドウ中身の周りに描画される境界線のことです。ウィンドウ装飾とは関係ありません。
形状指定がない場合
形状の指定がない場合、デフォルトの領域は、以下のようになります。

境界領域と入力領域は、「ウィンドウの境界線を含む矩形」。
クリップ領域は、「ウィンドウの境界線を含まない中身の矩形」。

境界線の幅が 0 の場合は、すべて同じになります。
領域について
各領域は、ウィンドウの本来の矩形よりも大きな範囲を指定できますが、実際に表示されるのは、本来の領域内までです。
クリップ領域と入力領域については、境界領域の範囲内のみ有効になります。

基本的には、境界領域だけを変更して、形状を指定すれば、領域外の部分は透明扱いになります。
領域の指定方法
ウィンドウ形状を指定したい場合、以下のような複数の方法があります。

  • Xlib の XPolygonRegion() で多角形の Region を作成して、XShapeCombineRegion() によって領域を変更する。
  • XShapeCombineRectangles() で、複数の矩形のリストから領域を変更する。
  • 1bit の Pixmap を使って、XShapeCombineMask() で領域をマスクする。

ウィンドウに形状が設定されていない場合、ウィンドウの現在の領域は、通常のデフォルトの領域となります。

ウィンドウの現在の各領域を、指定された結合方法で結合できます。

詳しくは、SHAPE 拡張機能 の関数リストをご覧ください。
プログラム
ひし形のウィンドウ形状にします。
閉じるボタンか、ひし形内でポインタボタンを押すと終了します。

※通常のウィンドウなので、ウィンドウマネージャによる装飾フレームも付いています。

$ cc -o run e02-shape.c util.c -lX11 -lXext

<e02-shape.c>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/shape.h>
#include "util.h"

#define WIDTH  101
#define HEIGHT 101

static void _set_shape(Display *disp,Window win)
{
    Region r;
    XPoint pt[5];

    pt[0].x = WIDTH / 2, pt[0].y = 0;
    pt[1].x = WIDTH - 1, pt[1].y = HEIGHT / 2;
    pt[2].x = WIDTH / 2, pt[2].y = HEIGHT - 1;
    pt[3].x = 0, pt[3].y = HEIGHT / 2;
    pt[4] = pt[0];

    r = XPolygonRegion(pt, 5, EvenOddRule);

    XShapeCombineRegion(disp, win, ShapeBounding, 0, 0, r, ShapeSet);

    XDestroyRegion(r);
}

int main(int argc,char **argv)
{
    Display *disp;
    Window win;
    XEvent ev;
    int major,minor;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    if(!XShapeQueryVersion(disp, &major, &minor))
        printf("unsupported SHAPE\n");
    else
        printf("SHAPE ver %d.%d\n", major, minor);

    //

    win = create_test_window2(disp, WIDTH, HEIGHT, 0, ButtonPress);

    _set_shape(disp, win);

    //イベント

    XMapWindow(disp, win);

    while(1)
    {
        XNextEvent(disp, &ev);

        if(event_quit(&ev)) break;

        if(ev.type == ButtonPress) break;
    }

    XCloseDisplay(disp);

    return 0;
}

形状を指定する場合、通常は、ウィンドウマネージャによる装飾フレームは必要ないと思うので、ウィンドウ属性を override_redirect = True にしてウィンドウを作成することになると思いますが、装飾フレームを付けたまま形状を指定することもできるので、ウィンドウにぽっかり穴を開けて、透けている範囲のスクリーンショットを撮るようなアプリケーションを作ることもできます。