Electron の Tray
クラスについては公式ドキュメントにも、Apple や Microsoft の公式ドキュメントにも詳細が載っていないので、悩む部分が多かったです。そこで、自分で実際に調べ、得たものも含めて、ここに書いてシェアしようと思います。
サンプルアプリケーション
これらの Tray
を理解する手助けとして、サンプルアプリケーションを用意しました。シンプルな3分間タイマーで、その残り時間をパーセントに変換して、Tray
にアイコン表示するというものです。これ以降、コードを記載するときは、ここにアップロードしてある実際のソースコードから抜粋しています。
SimpleTimer - GitHub
https://github.com/hibara/SimpleTimer
レポジトリには、「node_modules」が含まれておりませんので、ビルドする場合はローカルに pull した後に、当ディレクトリで npm install
コマンドを実行してから行ってください。
実際の動作を見たい場合は、アプリケーションをビルドしておきました。ダウンロードしてお試しください。
https://github.com/hibara/SimpleTimer/releases
Windows:
https://github.com/hibara/SimpleTimer/releases/download/v1.0.6/SimpleTimer-win32-ia32.zip
macOS:
https://github.com/hibara/SimpleTimer/releases/download/v1.0.6/SimpleTimer-darwin-x64.zip
Tray とは?(macOS, Windowsのそれぞれにおいて)
クラス: Tray
https://www.electronjs.org/docs/api/tray
Electronの Tray
とは、macOS のデフォルトでは右上にある「メニューバー」に表示するアイコン、
Windows のデフォルトでは、右下にある「タスクトレイ」を指します。
主な機能は、アイコンを変えることでアプリケーションの状態をコンパクトにユーザーへ伝えることと、そこに表示されるアイコンをクリックすることでコンテキストメニューを出すこともできます。
表示アイコンのサイズ
タスクバー、またはタスクトレイにアイコンを表示するには、macOS, Windows 用にそれぞれ別途用意する必要があります。
macOS の場合
タスクバー表示に必要となる分の、.png
ファイルを用意します。
そのタスクバーに表示されるアイコンのサイズを知るには、実際の画面パーツを Apple 公式からダウンロードして確かめることができます。
https://developer.apple.com/design/resources/
ただ、実際 Photoshop などで開いたところで、内部でどのサイズ、解像度を用意すれば良いか分かりません。サイズ、解像度については以下にファイル規則など含め書かれていますが、肝心の「タスクバー」のアイコンサイズは見つかりません。
ですので、実際にトライ&エラーで試してサイズと解像度を出してみました。私が独自調査した結果は以下の通りです。
縦(高さ)だけは、点線部分(18px)で揃えると、他のタスクバーアイコンと揃うと思います。
もちろん、これ以上のサイズで作成することも可能ですが、タスクバーから盛大にはみ出ます(特に高さ)。幅は、「any」という記述もあったので、横長のアイコンも表示可能でしょうが、今回は検証しません。
macOSのトレイアイコン名の規則
ここで、気になってくるのは、macOS にある独特の画像ファイルの命名規則です。
これは macOS 側が、将来的に画像解像度を上げてきたとしても、自動的に適用できるための「仕様」として機能しています。
たとえば、「100.png」というファイル名があったとして、コード内で 100.png
を指定したとしても、自動的にその環境に最適な解像度のファイル、100@1x.png
や 100@2x.png
を選択してくれます。ちなみに、100@1x.png
= 100.png
です。どちらのファイル名でも大丈夫です。
@1x
は、オリジナルサイズの解像度を指し、この画像サイズが「24 x 24px」ならば、@2x
は、「48 x 48px」となり、もし @3x
が存在するならば、「72 x 72px」です。
Windows の場合
Windows の場合は、macOS と異なり、.ico
ファイルを指定します。ただ、Windows の場合も、明確な公式ドキュメントが見つかりませんでした。
Windowsアイコンの仕様については、以下を信じることにします。
https://docs.microsoft.com/ja-jp/windows/win32/uxguide/vis-icons?redirectedfrom=MSDN#size-requirements
- フルセット:16x16, 32x32, 48x48, 256x256
- クラシックモード(フルセット): 16x16, 24x24, 32x32, 48x48, 64x64
どう考えてもタスクトレイに入るアイコンにしては、大きすぎる 256x256
サイズが含まれていますが、一応従っておきましょう(たぶん入っていなくても表示はされるとは思いますが・・・)。
Windows アイコンの作成方法
ネット検索していただければ、オンラインで作成する方法はたくさんあると思います。
デスクトップツールだと、icofx が、確実で使いやすく鉄板でしょうか。特に大量にアイコンファイルを処理したいときにバッチ処理が使えます。ただしシェアウェアなので、購入に躊躇される方もいるかもしれません。
png2ico というフリーの老舗コマンドラインツールもありますが、最終更新が古すぎて、Windows 7 から導入された、256pxサイズのアイコンには対応していないようです。256px以上のサイズの PNG ファイルを読み込ませようとすると、以下のようなエラーメッセージが表示されてしまいます。
Width must be multiple of 8 and <256. Height must be <256.
そこで、思い切ってアイコン生成アプリケーションを自作してみました。icofx ほどの高機能性はありませんが、最低限の .ICO
ファイルを作るには十分でしょう。.NET Framework 4.0 製でソースもMITライセンスで上がっています。よろしければお試しください。
Png2WinIco
https://github.com/hibara/Png2WinIco
実行ファイル:
https://github.com/hibara/Png2WinIco/releases
アイコンファイル作成に必要な、256 x 256px 以上の高解像度を持つ .PNG ファイルをドラッグ&ドロップするだけで、自動的に各サイズにリサイズ格納されたアイコンファイルを作成することができます。
実装する
実装はさして難しくありません。process.platform
で、macOS か Windows かを判定して、それぞれに合ったアイコンファイルを割り当てます。
このコード例では、簡単なコンテキストメニュー(アプリケーションの終了コマンドのみ)も含めています。
// トレイアイコンを生成する
let tray = null;
const createTrayIcon = () => {
let imgFilePath;
if (process.platform === 'win32') { // Windows
imgFilePath = __dirname + '/images/tray-icon/white/100.ico';
}
else{ // macOS
imgFilePath = __dirname + '/images/tray-icon/black/100.png';
// 一応、macOS のダークモードに対応しておく
if ( nativeTheme.shouldUseDarkColors === true ){
isDarkTheme = true;
imgFilePath = __dirname + '/images/tray-icon/white/100.png';
}
}
const contextMenu = Menu.buildFromTemplate([
{ label: '終了', role: 'quit' }
]);
tray = new Tray(imgFilePath);
tray.setToolTip(app.name);
tray.setContextMenu(contextMenu);
}
あとは、タイマーが経過する度に残り時間を計算して、パーセンテージから5%刻みで用意されたアイコンに表示を差し替えます。ゆっくりではありますが、タスクバー(またはタスクトレイ)の円が、アニメーションして変化していくのが分かると思います。
// タイマーの表示
const displayTimer = (valMilliSeconds) => {
mainWindow.webContents.send("ipc-display-timer", valMilliSeconds);
// タスクトレイのアイコン表示
let percent = Math.floor((valMilliSeconds / MAX_MILLI_SECONDS) * 100);
// 5の倍数で丸める
let multipleOfFive = Math.round(percent / 5) * 5;
let imgFilePath;
let imgFileName = ('000' + multipleOfFive).slice(-3) + (process.platform === 'win32' ? '.ico' : '.png');
if ( process.platform === 'win32' || isDarkTheme === true ) {
imgFilePath = __dirname + '/images/tray-icon/white/' + imgFileName;
}
else {
imgFilePath = __dirname + '/images/tray-icon/black/' + imgFileName;
}
tray.setImage(nativeImage.createFromPath(imgFilePath));
tray.setToolTip(percent + "% - " + app.name);
};
macOS でリアルタイムにダークモード変更されたときにも対応しておく
アプリケーションが動作しているのが、macOS なら「ダークテーマに変更」イベントが発生しますので、それに合わせてアイコンカラーも変更するようにしておくべきでしょう。
// システムカラーの変更イベント
if ( process.platform === 'darwin' ) {
nativeTheme.on("updated", () => {
isDarkTheme = nativeTheme.shouldUseDarkColors === true;
});
}
Tray アイコンが消えてしまう問題
以下のリンクにある Electron 公式ドキュメントの FAQ にもありますが、変数のスコープによるものらしいです。グローバルではなく、ローカルで Tray を生成すると、ガベージコレクションで消えてしまうようです。
Electron FAQ | Electron
https://www.electronjs.org/docs/faq#%E6%95%B0%E5%88%86%E7%B5%8C%E3%81%A4%E3%81%A8%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE-tray-%E3%81%8C%E6%B6%88%E5%A4%B1%E3%81%97%E3%81%BE%E3%81%99%E3%80%82
前述のサンプルコードでも、関数の中にではなく、グローバルに let tray = null;
と記述してあります。
ちなみに Windows のタスクトレイアイコンは「まれに」消えない
タスクトレイにアイコンを表示したアプリケーションを終了したとき、「まれに」アイコンが消えないことがあります。これは前述の問題とは関係なく、Windows の仕様です。
これは Electron の問題でもなんでもなく、私が .NET Framework や、C++Builder で Windows アプリケーションを開発していた頃にもあった仕様です。ただ、アイコンが残ってしまったとしても、アプリケーションを終了した後で、そのアイコン部分へマウスカーソルを持って行くと消えてくれます。
これって、Windows XP 以前からある問題なんですが、Microsoft は改善する気はないんですかね・・・