Compose for DesktopがLinux(OpenSuSE)で起動しなかった話

# Compose for Desktopとは？
JetBrainsが提供するKotlin向けのUIフレームワークです。
AndroidのUIフレームワークであるJetpackComposeを利用し、各Platformにネイティブなデスクトップアプリを作ることができます。Windows、Linux、MacOSで動きます。
コーディング、UIの設計はAndroidのJetpackComposeと同等です。それがJVM上で動きます。なので、Windows、Linux、MacOSで動きます。(nativeアプリというわけではない)

Androidの世界では、従来のLayout xmlの方式から、徐々にJetpackComposeに移行しつつあります。
ただ・・・書き方が全然違うので、機械的にまるっと書き換えは不可能です。

[Compose Multiplatform（jetbrains）](https://www.jetbrains.com/ja-jp/lp/compose-mpp/)
[チュートリアル](https://github.com/JetBrains/compose-jb/blob/master/tutorials/Getting_Started/README.md)

今回、Compose for Desktopをお試しでやってみましたが、Windowsではうまく動いたのにLinux(OpenSuSE Leap 15.4)では動かなかったというお話と、解決策を紹介します。
(普段使いがLinuxなもので・・・Linux上で動かないと困ったことになる)
# 一番簡単なアプリ
1. まず、IntelliJ IEDAをインストールします。Community版で大丈夫です
1. 新規プロジェクトを作成します。左側の一覧から「Compose Multiplatfrom」を選択します。その右側は全てデフォルトのままでよいです。
![Screenshot_20221211_124757.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/0338e9e9-3f7c-7818-c1b1-981f2618be6f.png)
1. もうこれだけで雛形が出来上がっています。
![Screenshot_20221211_125043.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/1a106677-b0d4-2ac7-4d4f-e6e9e48c3082.png)
1. 実行はメイン関数の「fun main()」の左の緑の矢印をクリックして実行か、
![Screenshot_20221211_125312.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/590832e3-7bc4-f582-b332-dc114a5f6c0a.png)
1. Gradleタスクの「Tasks」→「compose desktop」→「run」で実行できます
![Screenshot_20221211_125418.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/090a8481-f259-e39c-d24d-a2164675f691.png)
1. こんな画面が上がってきます。
![Screenshot_20221211_125746.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/c990dbdd-7561-0f4c-bf84-cfda56d40bb5.png)
# Windowsだと起動できるのに、Linuxだと起動できない
Windowsではこの手順でうまく起動できたのですが、Linux(OpenSuSE Leap 15.4)では起動できませんでした。
```
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "kotlin.Lazy.getValue()" because "<local1>" is null
	at androidx.compose.ui.awt.ComposeLayer.getA11yDisabled(ComposeLayer.desktop.kt:185)
	at androidx.compose.ui.awt.ComposeLayer.access$getA11yDisabled(ComposeLayer.desktop.kt:85)
	at androidx.compose.ui.awt.ComposeLayer$makeAccessible$1.getAccessibleContext(ComposeLayer.desktop.kt:192)
	at org.jetbrains.skiko.HardwareLayer.getAccessibleContext(HardwareLayer.kt:90)
	at atk.wrapper/org.GNOME.Accessibility.AtkWrapper$2.eventDispatched(AtkWrapper.java:245)
	at java.desktop/java.awt.Toolkit$SelectiveAWTEventListener.eventDispatched(Toolkit.java:2201)
	at java.desktop/java.awt.Toolkit.notifyAWTEventListeners(Toolkit.java:2049)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4907)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
	at java.desktop/java.awt.Container.addImpl(Container.java:1167)
	at java.desktop/java.awt.Container.add(Container.java:440)
	at org.jetbrains.skiko.SkiaLayer.<init>(SkiaLayer.awt.kt:107)
	at org.jetbrains.skiko.SkiaLayer.<init>(SkiaLayer.awt.kt:57)
	at org.jetbrains.skiko.SkiaLayer.<init>(SkiaLayer.awt.kt:51)
	at androidx.compose.ui.awt.ComposeLayer$ComponentImpl.<init>(ComposeLayer.desktop.kt:205)
	at androidx.compose.ui.awt.ComposeLayer.<init>(ComposeLayer.desktop.kt:90)
	at androidx.compose.ui.awt.ComposeWindowDelegate.<init>(ComposeWindowDelegate.desktop.kt:54)
	at androidx.compose.ui.awt.ComposeWindow.<init>(ComposeWindow.desktop.kt:61)
	at androidx.compose.ui.awt.ComposeWindow.<init>(ComposeWindow.desktop.kt:59)
	at androidx.compose.ui.window.Window_desktopKt$Window$3.invoke(Window.desktop.kt:162)
	at androidx.compose.ui.window.Window_desktopKt$Window$3.invoke(Window.desktop.kt:156)
	at androidx.compose.ui.window.Window_desktopKt$Window$10$1.invoke(Window.desktop.kt:378)
	at androidx.compose.ui.window.Window_desktopKt$Window$10$1.invoke(Window.desktop.kt:377)
	at androidx.compose.ui.window.AwtWindow_desktopKt$AwtWindow$2.invoke(AwtWindow.desktop.kt:75)
	at androidx.compose.ui.window.AwtWindow_desktopKt$AwtWindow$2.invoke(AwtWindow.desktop.kt:74)
	at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:81)
	at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:1032)
	at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:793)
	at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:813)
	at androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:827)
	at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
	at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2.invokeSuspend(Application.desktop.kt:219)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:741)
	at atk.wrapper/org.GNOME.Accessibility.AtkWrapper$4.dispatchEvent(AtkWrapper.java:643)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
 
```
nullポが出てます。stackTraceの一番上、「Cannot invoke "kotlin.Lazy.getValue()" because "<local1>" is null」で検索しても有用な情報は得られず・・・困っていたところで、「GNOME.Accessibility.AtkWrapper」で検索すると、こんな情報がありました。
[« Ubuntu18.04 にアップグレードしたら ePSXe が動かなくなってハマる。 | トップページ | Ubuntu MATE 18.04 で Ubuntu MATE Welcome をインストールする »](http://hirotyanteikoku.cocolog-nifty.com/cocolog/2018/08/ubuntu1804-mine.html)
Minecraftが動かなくなったと言っているのですが、同じようなstackTraceが出ています。このページで、
> で、解決法が載ってました。
> /etc/java-11-openjdk/accessibility.properties
> の
> assistive_technologies=org.GNOME.Accessibility.AtkWrapper
> をコメントアウト（行の先頭に#をつける。）しろ、というものでした


というのがあったので、自分のOpenSuSEで、
```
$:~> rpm -qa '*openjdk*'
（中略）
java-17-openjdk-accessibility-17.0.5.0-150400.3.6.1.x86_64
```
んんん・・・なんか、怪しいのがある
```
$:~> rpm -ql java-17-openjdk-accessibility-17.0.5.0-150400.3.6.1.x86_64
/usr/lib64/jvm/java-17-openjdk-17/conf/accessibility.properties
/usr/lib64/jvm/java-17-openjdk-17/lib/libatk-wrapper.so
```
ああ、これだ。と、accessibility.propertiesの中身を見てみると。
```
#Config file to  enable java-atk-wrapper

assistive_technologies=org.GNOME.Accessibility.AtkWrapper
```
この3行目をコメントアウトにするとCompose for Desktopが起動するようになりました。(もしくは、このファイル自体がない、パッケージをアンインストールでもいいんじゃないかな？)
# AtkWrapperって、何ぞ？何故、起動しなかった？
じゃ、このAtkWrapperって何者なのか？何故、これがあると動かなかったのか？・・・て話ですが、rpmパッケージの説明を見てみると
```
$:~> rpm -qi java-17-openjdk-accessibility-17.0.5.0-150400.3.6.1.x86_64
Name        : java-17-openjdk-accessibility
Version     : 17.0.5.0
Release     : 150400.3.6.1
Architecture: x86_64
Install Date: 2022年11月19日 13時44分26秒
Group       : Development/Languages/Java
Size        : 97605
License     : Apache-1.1 AND Apache-2.0 AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-only WITH Classpath-exception-2.0 AND LGPL-2.0-only AND MPL-1.0 AND MPL-1.1 AND SUSE-Public-Domain AND W3C
Signature   : RSA/SHA256, 2022年10月24日 17時09分58秒, Key ID 70af9e8139db7c82
Source RPM  : java-17-openjdk-17.0.5.0-150400.3.6.1.src.rpm
Build Date  : 2022年10月24日 16時57分04秒
Build Host  : sheep92
Relocations : (not relocatable)
Packager    : https://www.suse.com/
Vendor      : SUSE LLC <https://www.suse.com/>
URL         : https://openjdk.java.net/
Summary     : OpenJDK 17 accessibility connector
Description :
Enables accessibility support in OpenJDK 17 by using java-atk-wrapper. This allows
compatible at-spi2 based accessibility programs to work for AWT and Swing-based
programs.

Please note, the java-atk-wrapper is still in beta, and OpenJDK itself is still
being tuned to be working with accessibility features. There are known issues
with accessibility on, so please do not install this package unless you really
need to.
Distribution: SUSE Linux Enterprise 15
```
うーん、そもそもjava-atk-wrapperはまだベータ版だと書いてある。「まだ問題が残っているので、必要なければこのrpmパッケージをインストールするな」と・・・。やっぱり、そうなのか。
## AtkWrapperとは
AtkWrapperが何なのかですが、
[Java ATK Wrapper](https://wiki.gnome.org/Accessibility/JavaAtkWrapper)
が本家の情報源のようです。(URLがgnomeだからgnomeの一部なのか？)
> about
> Java ATK Wrapper は、JNI 技術を使用した ATK の実装です。 Java Swing イベントを ATK が理解できるイベントに変換し、これらのイベントを ATK-Bridge に送信します。 ATK までの Java スタックの順序は Java アプリケーション JRE JAAPI JAW ATK であり、JAW は 2 つの主要な部分で構成されています。

> wrapper
> Swing イベントをリッスンし、ATK のインターフェースに適合するように JAAPI を適応させる Java ライブラリ

なるほど、内部でAPIの変換をやっているようだ・・・
openJDKの場合はまだ、この辺の実装が不十分のようだ。それでも、rpmパッケージとか、openJDKに付属されているので知らないでインストールしてJavaのアプリが突然動かなくなったという報告が多いようです。(だったら、デフォルトdisableにしとけばいいのに・・・)

## openJDKのheadless版
色々調べてみると、openJDKのheadless版というのがあって、このheadless版がインストールされているとダメらしいという情報がありました。headless版とは？
> ヘッドレスモードは、ディスプレイデバイス、キーボード、またはマウスが不足しているシステム構成です。予想外のように聞こえますが、実際には、グラフィックモードでも、このモードでさまざまな操作を実行できます。

らしいです。そもそも、グラフィックの機能を省いているので起動しない。おそらく、サーバサイドのように裏方で黙々と稼働するような場合に使うものだと思います。その分、軽量化されているのだと思います。

自分のOpenSuSEを見てみると
```
$:~> rpm -qa '*openjdk*' | sort
java-17-openjdk-17.0.5.0-150400.3.6.1.x86_64
java-17-openjdk-demo-17.0.5.0-150400.3.6.1.x86_64
java-17-openjdk-devel-17.0.5.0-150400.3.6.1.x86_64
java-17-openjdk-headless-17.0.5.0-150400.3.6.1.x86_64
java-17-openjdk-javadoc-17.0.5.0-150400.3.6.1.noarch
java-17-openjdk-jmods-17.0.5.0-150400.3.6.1.x86_64
java-17-openjdk-src-17.0.5.0-150400.3.6.1.x86_64
```
となっていて、java-17-openjdk-headlessをアンインストールしようとすると、他のjava-17-openjdkと依存関係を持っていて、アンインストールできません。

おそらく、openJDKで配布しているheadless版と意味が違うのでは・・・？(てか、openJDKで配布しているheadless版ってどこにあるのか見つけられない)

headless版はキーボード、マウス、グラフィックを除くと言う意味では同じなのかもしれませんが、rpmパッケージの場合は複数のパッケージに別れて構成されます。

openJDKで配布しているheadless版は純粋にサーバサイドで使う、キーボード、マウス、グラフィックを除いた他全部。

rpmやdebパッケージはheadless版は **「キーボード、マウス、グラフィックを除いたコアな部分」それ以外は他のパッケージで賄う。** なので、他のパッケージと依存関係を持っている。のだ・・・という、結論に達しました。

**Javaアプリを起動して、AtkWrapper絡みのエラーがでたらdisable(もしくは、パッケージのアンインストール)しとけ、という教訓でした。**