GNOME-mutterさんである時からiconic起動ができなくなっていた件

しばらく前に書いて下書きに入ったまま忘れていたことにアドベントカレンダーの記事を書いていて気が付いたのでついでに公開しておきます。

# 背景
工場などで使われるシステムに関わっているhermit4です。
一度稼働を始めると、10年以上は余裕で使われるシステムに関わっているといつの間にか時代から取り残され困りものですよね。

つい最近まで利用できる最新環境はRHEL7.1でした。記事を書いた時点の最新環境はすでにわりと古いRHEL8.1です。なお、驚くことにサポートしなくてはならない一番古い環境はRHEL4系だったりします。まぁ、この手の悲しい状況は工場系あるあるらしいですけど、できればXlibとかMotifとかK&RなコードとかTcl/Tkとかとはもう使わずに生きてみたいなと思います。2021年にもなっていまだ保守しているのが32bitアプリなのもどうなのかと思いますし。まぁ、人生ってままならないものではありますが・・・。

ところで、RHEL7.1まではメンテナンスしているXアプリケーションの-iconicオプションが動作していました。ところが、RHEL8.1環境だと動かなくなりました。あれこれ調査した結果、mutter(GNOMEのWindow Manager)が原因と判明し、mutterを修正して解決もしたのですが、RHEL8.1自体がすでに２年前のOSで、採用されているmutterもだいぶ古いバージョンです。そのうえ、今時X周りなんてほとんど関心がもたれない上に古いバージョンを直したところで、アップストリームには貢献できません。かといってわざわざ最新のGNOME環境を作ってまで貢献するほど、GNOMEに対して熱意もないので、とりあえず備忘録を兼ねてここに記録を残しておきます。

誰かやる気がある人は代わりに貢献してください。mutterのコード追いかけるだけで燃え尽きたので、後はおまかせします。


# 概要
## -iconicオプション
iconicオプションは、古いXアプリケーションなどにはmanなどには載ってなくてもひっそり用意されているものが多かったです。RHEL7.1なんかだと、xeyesとかxcalc、xtermなんかは-iconicが機能しています。

実際、以下のような簡単なHello World!でも-iconicオプションが機能します。

```c:main.c
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Label.h>

int main(int argc,char *argv[])
{
    XtAppContext ac;
    Widget app,label;

    app = XtVaAppInitialize(&ac,"Sample",NULL,0, &argc,argv,NULL,NULL);
    label = XtVaCreateManagedWidget("Hello World!",labelWidgetClass, app,(void*)0);

    XtRealizeWidget(app);
    XtAppMainLoop(ac);
    return 0;
}
```

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.6)
project(test1)
find_package(X11 REQUIRED)
find_library(X11_Xaw_LIB Xaw ${X11_LIB_SEARCH_PATH}) # CMake 3.19以降なら不要

include_directories(${X11_INCLUDE_DIR})

add_executable(test1 main.c)
target_link_libraries(test1 ${X11_X11_LIB} ${X11_Xt_LIB} ${X11_Xaw_LIB})
```

上記のコードをビルドし、以下のように-iconicオプションをつけると、ウィンドウが最小化して起動するのが期待値です。

```shell:
$ ./test1 -iconic
```

まぁ、GUIを最小化して起動するだけならQtでも実現は可能です。とりあえずオプション制御まで書くのは面倒なので、デフォルトで最小化起動させるようにします。

```cpp:main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    QLabel label("Hello World!");
    label.showMinimized();
    return app.exec();
}
```

ところが、こちらもRHEL8.1だと最小化せずに表示状態で起動してしまいます。

# 原因
## RHEL7.5〜RHEL8.0まで

原因となったのはこの不具合？を修正しようとした結果のようです。

https://bugzilla.gnome.org/show_bug.cgi?id=774333

なんか、ゲームを起動したら最小化しっぱなしだから直したって事のようなのですが、対策として、最小化して起動されないようにした結果、最小化起動ができなくなりました・・・・。なんというか、最小化されっぱなしになる原因が他にあったんじゃないのかって思うのですがどうなんでしょうか。

https://gitlab.gnome.org/jwrdegoede/mutter/-/commit/be5c2ebcb59652c91a5817762e6535b357f14a47

ともあれ、RHEL8.0までのmutterは、上記パッチの適用前のように途中でbreakしてminimize解除をしないようにするとiconic起動が復活できます（この処理で本当に良いのかという問題は置いておいて・・・正直コード量が多すぎて影響度は読み切れていません）。

## RHEL8.1
ところが、肝心のRHEL8.1では上記の修正だけではだめになっています。
原因は、以下のリファクタリングのようです。

https://gitlab.gnome.org/jwrdegoede/mutter/-/commit/54febd1419e927f6e3294185e0a595439f32becf#9ecb9b17ccb3d6c1d820908bd2ddf7a2ec509f5b_1277_1242

clutter_actor_add_child でaddされた子は親の表示状態を受け継ぐ実装になっています。ところが、addされたのがトップレベルウィンドウでminimize状態で非表示で起動しようとすると問題になるわけで、以前はsrc/compositor/meta-window-actor.c の中でaddした直後に以下の関数を呼び出していました。

clutter_actor_hide (CLUTTER_ACTOR (self));

ところが、この辺りの処理を移した際にコードの移動から漏れてしまったようです。そもそも以前の変更でmanageされた当初からminimized状態になっているウィンドウはなくなったわけでして、最初からhideになるものが無いことから別の関数に移動された際に消えたのに気が付かなかったのでしょう。移動先の src/compositor/compositor.c ではhideにする処理が呼び出されていません。

というわけで、上記の移動先にclutter_actor_hideを入れてあげれば解決しました。

# 最終的に
というわけで、RHEL8.1のmutterさんに以下のパッチを当てたら一応-iconicオプションが機能するようになりました。がっつりコード追いかけて、修正はたった2行です。ただし、変更されてからだいぶ時間がたっており、コード量も多いのことから、わずか2行の修正といえどもどんな影響がでるか読み切れていません。-iconicが正しく動作する必要がある方は自己責任でお使いください。

```:
diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c
index 6c08c8f..d5942f7 100644
--- a/src/compositor/compositor.c
+++ b/src/compositor/compositor.c
@@ -810,6 +810,7 @@ meta_compositor_add_window (MetaCompositor    *compositor,
     window_group = compositor->window_group;

   clutter_actor_add_child (window_group, CLUTTER_ACTOR (window_actor));
+  clutter_actor_hide (CLUTTER_ACTOR (window_actor));

   /* Initial position in the stack is arbitrary; stacking will be synced
    * before we first paint.
diff --git a/src/x11/events.c b/src/x11/events.c
index 905b5bf..f6a0659 100644
--- a/src/x11/events.c
+++ b/src/x11/events.c
@@ -1414,6 +1414,7 @@ handle_other_xevent (MetaX11Display *x11_display,
            * MapRequest, fall through to ensure it is unminimized in
            * that case.
            */
+         break;
         }
       else if (frame_was_receiver)
         {
```

## 補足
なお、RHEL8.1で利用している環境は、過去からの互換性のためもちろん Classic(X11 display server)です。
Waylandってなんだっけ・・・orz