X でアクティブウィンドウの上にあるウィンドウだけを半透明化する

最近 Kubuntu を使い始めて割といい感じなんだけど、アクティブウィンドウの上にあるウィンドウだけを半透明化することができなかった。

設定でアクティブウィンドウ以外を半透明化することはできるんだけどなー。

というか、Compiz 以外でそういう機能を持ってるのは見たことないんで、あんまり需要はないのかもしれない。そもそもアクティブウィンドウを最前面にしない設定で使ってる人自体少数派な気がするし仕方ない。便利なんだけどな。

ChatGPT に聞いてみたらウィンドウマネージャーの外からでも制御できそうなので作ってみた。

必要そうな技術要素

ウィンドウを透明度を指定

次のコマンドを実行するとマウスカーソルが ┼ になるのでウィンドウをクリックすると、そのウィンドウが透明度50%になる。

% xprop -f _NET_WM_WINDOW_OPACITY 32c -set _NET_WM_WINDOW_OPACITY 0x80000000

C だとこんな感じ。

unsigned long opacity = 0x80000000;
XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False), XA_CARDINAL, 32, PropModeReplace, &opacity, 1);

ウィンドウの深さ順を得る

% xwininfo -root -tree

で浅い順に表示される。

C では XQueryTree() を使うと深い順のウィンドウリストが返される。

ウィンドウが重なっているかどうかを調べる

xwininfo の geometry でウィンドウの位置と幅と高さがわかるので、計算すれば重なってるかどうかわかる。 ただし、xwininfo の ┼ で選択したときのウィンドウではダメで、root 直下のウィンドウじゃないとディスプレイ上の位置がちゃんと取れないっぽい。

xwininfo -treeParent window idroot window になるまで親を辿る感じで。

% xwininfo -tree -id 0x63ae7b | grep 'Parent window'
  Parent window id: 0x137b273 (has no name)
% xwininfo -tree -id 0x137b273 | grep 'Parent window'
  Parent window id: 0x137b272 (has no name)
% xwininfo -tree -id 0x137b272 | grep 'Parent window'
  Parent window id: 0x5e3 (the root window) (has no name)

この場合は 0x137b272 の geometry を取ればいいっぽい。

% xwininfo -id 0x137b272 | grep geometry
  -geometry 582x438+2799+1344

C では XGetGeometry() で取得できる。

Window root;
int x, y;
unsigned int width, height, border_width, depth;
XGetGeometry(display, window, &root, &x, &y, &width, &height, &border_width, &depth);

アクティブウィンドウを得る

% xprop -root _NET_ACTIVE_WINDOW
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x63ae7b

C だとこんな感じ。

unsigned long type;
int format;
unsigned long nitems;
unsigned long bytes;
unsigned long *win;
XGetWindowProperty(display, window, XInternAtom(@display, '_NET_ACTIVE_WINDOW', False), 0, ~0, 0, AnyPropertyType, &type, &format, &nitems, &bytes, &win);
// win がウィンドウID

アクティブウィンドウが変わったことを知る

_NET_ACTIVE_WINDOW イベントを監視すればいいらしい。

xprop -spy を実行するとアクティブウィンドウが切り替わるごとに通知される。

% xprop -spy -root _NET_ACTIVE_WINDOW
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4a000b0
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4a000b0
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4c0002c
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4c0002c
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x6000e0
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x6000e0
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x63ae7b
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x63ae7b
^C

何故か2行ずつ出るけど理由は知らない。

C だと XSelectInput()PropertyChangeMask を指定して、XNextEvnet() を呼ぶと PropertyNotify イベントが発生すると復帰する。

Ruby で作った

C で作るのもアレだし、コマンドがあるのでシェルでも作れそうなんだけど、Ruby 好きなので Ruby で作ってみた。

Ruby から X を使うためのライブラリを探してみたけど良さそうなのが見つからなかったので、まずは Fiddle を使って必要な関数のラッパーを作って、それを使う感じで作った。

https://gitlab.com/tmtms/wmadd/

それなりに動くようになったので Gem にした。gem install wmadd でインストールできる。wmadd で起動できる。