0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

macOSでJavaプログラムをちょっとだけまともにしたかった

Last updated at Posted at 2023-02-21

はじめに

jlinkして作ったアプリをmacOSで動かすとき、少しでもそれっぽくしたい。
結局ちょっと微妙なので、もっといい方法があるかも。

アプリケーションバンドル

macOSのアプリはアプリケーションバンドルという形式のディレクトリ(アプリ名.app)になっていて、その下に決められた構成でファイルを置けば、普通のアプリっぽくなる。

アプリケーションバンドルは <アプリ名>.app/Contents/ の下に Info.plist を置いて、MacOS/<実行ファイル> とその他必要なファイルを Resourcesに置けば良い。

Info.plist

Info.plistはXMLファイルなので、一応手で書けるが、今のところスクリプトエディタでJavaプログラムを実行するだけの applescript を作り、それをアプリケーションとして出力するのが簡単。

apple script

apple scriptでのプログラムの実行は do shell script "コマンド" でできるが、問題はカレントディレクトリがわからない(実際には / になっているらしい)こと。
つまりJava実行ファイルをフルパスで指定しないといけない。

/Application に置く前提でフルパスで書く手もあるが、ここではちょっと頑張ってみる。

スクリプトがあるディレクトリは取得できるので、それを使ってフルパスを生成する。以下のようにすると、スクリプトのフルパスが取得できる。

set scriptdir to (path to me) as Unicode Text

ただ、これだと大昔の : 区切りパスで出てくるので、以下のようにして / 区切りに変換する。

set posixpath to POSIX path of scriptdir

ここまででスクリプトファイルのフルパスが取得できる。これをアプリケーション形式で出力すると、アプリケーションバンドルのディレクトリ(<アプリ名.app>/)がフルパスで取得できる。

メニューの「ファイル」→「書き出す」で、ファイルフォーマットを「アプリケーション」にする、この段階ではコード署名しない方がいいかも(書き換えられなくなるので)

これでアプリケーションバンドルができる。このアプリケーションバンドルの中にJavaプログラムを同梱して起動すれば良い。

アプリケーションバンドルへの追加

まずはスクリプトからjavaプログラムを実行する。

set cmdline to posixpath & "Contents/Resources/java/bin/app"
do shell script cmdline

という感じ、app は jlinkでできた起動スクリプト。
起動スクリプトが <アプリ名.app>/Contents/Resources/java/bin/app になるように、jlinkしてできたもの(app/build/image以下)はそのまま <アプリ名.app>/Contents/Resources/java 以下にコピーする。

実行ファイル以外のファイルは Resources に置くらしいので、とりあえずそうしたが、別に MacOS 以下に置いても動くので、その辺りは適当で大丈夫。

アプリケーションバンドルの書き出しとjlinkで出来たimageの配置が終われば、一応macOSのアプリの形になる。ダブルクリックで起動できるはず。/Application ディレクトリに移動しても動くはず。

というわけでまとめると、apple scriptはこんな感じ。これを「書き出す」でアプリケーションにして書き出す。

set scriptdir to (path to me) as Unicode Text
set posixpath to POSIX path of scriptdir
set cmdline to posixpath & "Contents/Resources/java/bin/app"
do shell script cmdline

shell scriptでやる場合

結局は実行できればいいだけなので、shell scriptでも問題ない。Info.plistを作るのがめんどくさいだけ。
でもまあ、最低限こんな感じでいいらしい。icnsファイルの名前にはディレクトリは書かない、icnsファイルはResourcesディレクトリに置くこと。

shell scriptでやると、shell script自体はJavaアプリの起動と同時に終了するので、アイコンが2つにならない。なので、同じアイコンを指定しておくと割とまとも。ただ、URLスキームで起動とかが出来ないっぽい。(引数が渡せない)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>アプリの名前</string>
<key>CFBundleIconFile</key>
<string>icnsファイルの名前</string>
<key>CFBundleExecutable</key>
<string>実行するファイルの名前(ここではスクリプトの名前)</string>
<key>CFBundleIdentifier</key>
<string>識別名 com.apple.Safariみたいなの</string>
</dict>
</plist>

実行するスクリプトは、やっぱりJava実行ファイルのフルパスが必要なので、こんな感じになる。ただ、apple script と違ってスクリプトそのもののフルパスになるので、../ とかを使うか Resources ディレクトリではなく MacOS ディレクトリにJavaプログラムを置くなどする。
今回はMacOSディレクトリにjlinkで出来た bin, conf, legal, lib を置く想定。

#!/bin/bash
scrpath=`readlink -f "${BASH_SOURCE:-$0}"`
scrdir=`dirname "$scrpath"`/bin
cd $scrdir
./java -m <モジュール名>/<パッケージ名.クラス名> "$@"

${BASH_SOURCE:-$0} は、source <script名> とか zsh で実行した時のことを考慮してなのだが、今回は #!/bin/bash しているから多分 $0 だけで大丈夫。でもまあ一応。ただディレクトリ名中に空白文字があったりするとダメかも。

gradle jlinkで作れる起動スクリプトは無視して作っていて、直接javaを起動している。起動スクリプトを実行するようにしても大丈夫なはず。

アイコン

もうちょっと見た目をまともにするならば、アプリケーションのアイコンとDockに出てくるアイコンをなんとかする。アプリケーションバンドルのアイコンは、アプリケーションバンドルを選択して「情報を見る」を開き、icnsファイルを左上の小さいアイコン表示にドラッグ・ドロップすると変えられる。
icnsファイルは色々な大きさのアイコンをまとめたもの、作り方は後で。

Dockアイコンが割と厄介。そのままだとスクリプトが出すアイコンとjavaが出すアイコンの2つがDockに出てきてしまう。
スクリプトのアイコンは Info.plist に以下のエントリを追加すると消せる。

<plist version="1.0">
<dict>
     .
     .
     .
    <key>LSUIElement</key>
    <true/>
     .
     .
     .
</dict>
</plist>

javaの方で消すこともできる。起動スクリプトの java 実行のところに、-Dapple.awt.UIElement=true と追加すれば良い。
ただ、こちらは一瞬Javaアイコンが出てすぐ消えるみたいな動きになるっぽい。

スクリプトのアイコンを消した場合、そのままだとJavaのDuke(だっけ?)のアイコンが出てくる。これはJavaプログラムの方で変えられるが、こっちも一瞬Javaアイコンが出て、指定したやつに変わるという微妙な動き。
こちらでは icns ファイルは使えないっぽいので、普通のpngにしておく。勝手に大きさは調整されるので、適当に大きいサイズ(512x512とか)の方が良いかも。

gradleを使ってjava-applicationで作ったプロジェクトなら、resourcesにicon.pngを置いておけば良い。

実際のコードは以下の通り(Java 9以降)

import java.net.URL;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Taskbar;

public class App {
    .
    .
    .
    try {
        URL iconurl = App.class.getClassLoader().getResource("icon.png");
        Image iconimg = Toolkit.getDefaultToolkit().getImage(imgurl);
        Taskbar taskbar = Taskbar.getTaskbar();
        taskbar.setIconImage(img);
    }
    catch (Exception e) {
        // アイコンが設定できないだけなので、とりあえず何もしなくても動くはず     
    }
    .
    .
    .
}

ここまでやれば起動時のDock表示がちょっと変な以外は一応アプリっぽいかと。

一度applescriptでアプリケーションバンドルを作ったら、gradle jlinkで自動的にコピーされるようにしておけばより良いかも。

Intel/ARM両対応

今時 m1, m2 非対応は無いし、Intelを完全に切るのもちょっとという感じなので、両対応してみる。OpenJDKはそれぞれ出ているので、両方用意して、gradle では targetPlatform を指定して、それぞれの実行モジュールをビルドする。
targetPlatformの指定はgraldeでjlinkを使う参照。

ディレクトリ分けして、アプリケーションバンドルには両方とも入れておく。

そしてapplescriptでCPUを判別して起動するjavaを変えれば良い。具体的には以下のようにする。

set cputype to CPU type of (system info)
if cputype contains "Intel" then
    do shell script "intel用コマンド"
else
    do shell script "ARM用コマンド"
end if

shell scriptでも判別できる。こんな感じ。

#!/bin/bash
cputype=`uname -m`
if [ "$cputype" == "x86_64" ]; then
    "intel用コマンド"
else
    "ARM用コマンド"
fi

icnsファイルの作り方

標準で iconutil というツールが入っているので、それを使う。
まずはいろんな解像度のアイコンをpngファイルで用意する。そして、ファイル名を以下のようにしておく。@2xがつくのはdpiが高い環境用。

ファイル名 解像度
icon_16x16.png 16x16
icon_16x16@2x.png 32x32
icon_32x32.png 32x32
icon_32x32@2x.png 64x64
icon_128x128.png 128x128
icon_128x128@2x.png 256x256
icon_256x256.png 256x256
icon_256x256@2x.png 512x512
icon_512x512.png 512x512
icon_512x512@2x.png 1024x1024

このファイルを <アイコン名>.iconset という名前のディレクトリに入れておく。
そうしたら、iconutil -c icns <アイコン名>.iconset とすると完成。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?