Electronでバックグラウンド通知
Electron の理解を深めるために、バックグラウンドで動作し通知するタイマーを作ってみました。
関連するコードをまとめましたので、Electron を触り始めた人の参考になれば幸いです。
作ったものについて
時間と分を入力すると、その時間に通知を表示してくれるアプリです。
実装
準備
$ pnpm create @quick-start/electron .
✔ Package name: … electron-timer
✔ Select a framework: › react
✔ Add TypeScript? … Yes
✔ Add Electron updater plugin? … No
✔ Enable Electron download mirror proxy? … No
バックグラウンドで動作させる
作成したテンプレートではアプリケーションを起動するとウィンドウが表示されます。
ウィンドウを閉じるとアプリケーションが終了するようになっています。
起動時のウィンドウ表示をやめて、その状態でもアプリケーションが落ちないようにします。
起動時のウィンドウ表示をやめる
基本的にはアプリケーション起動時の処理(app.whenReady().then( ...
)にあるウィンドウを開く処理を消せば対応できます。
MacOS ではウィンドウの有無に関わらずアプリケーション起動時にアイコンが表示されます。
ドックに表示させない処理が必要になります。
app.whenReady().then(() => {
if (process.platform === 'darwin') {
app.dock.hide()
}
}
ウィンドウ閉じても終了しないようにする
作成したテンプレートでは、ウィンドウを全て閉じるとアプリケーションが終了するコードが書かれています。
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
このコードによると、darwin
(MacOS)以外の OS にて全てのウィンドウを閉じたときにアプリケーションを終了するようになっています。
逆にいうと、MacOS では全てのウィンドウが閉じられると自動的にアプリケーションが終了するので何もしていないということになります。
というわけで、Windows ではウィンドウを閉じた時のapp.quit()
をやめるだけでいいのですが、 MacOS では特別な設定が必要になります。
ビルド時の設定を以下のように変更します。
"build": {
"mac": {
"extendInfo": {
"LSUIElement": true
}
}
}
ビルド時の設定なので、開発者モードではドックにアイコンが表示されたままになっています。
申し訳程度にアプリケーションの準備が整ったタイミングでドックから隠しておきます。
app.whenReady().then(() => {
// package.jsonのbuild設定で、起動時にドックに表示しないようにしているので不要
// 開発者モードの挙動を合わせるために消しておく
if (process.platform === 'darwin') {
app.dock.hide()
}
}
Tray を作る
ウィンドウを閉じてもアプリケーションが終了しないようにできましたが、操作する口がありません。
Tray を表示させて、アプリの起動確認と操作をできるようにします。
Tray とは MacOS ではメニューバー、Windows では通知領域のことを指します。
以下のように、Tray を追加し、ウィンドウを開くメニューとアプリケーションを終了するメニューを追加します。
app.whenReady().then(() => {
...
// テンプレートを作った時の画像を16pxに加工
const trayIcon = nativeImage.createFromPath(icon).resize({ width: 16 });
// Tray作成
const tray = new Tray(trayIcon);
// Trayにメニュー追加
const contextMenu = Menu.buildFromTemplate([
{
label: "ウィンドウを開く",
click: () => {
createWindow();
},
},
{
label: "終了",
click: () => {
app.quit();
},
},
]);
tray.setContextMenu(contextMenu);
});
実行してみると、メニューバーに表示されました。
クリックすると、設定したメニューが表示されています。
ドックの表示を切り替える(MacOS のみ)
アプリケーション起動時にはドックに表示しないようにしましたが、ウィンドウ表示時にはドックに表示させたいです。(個人的にそういう動作をするアプリが多いイメージがあります)
ウィンドウ表示時にドックにも表示させる
ウィンドウ表示用のメソッドにドック表示のコードを追加します。
function createWindow(): void {
if (process.platform === 'darwin') {
app.dock.show()
}
...
}
ウィンドウを消した時にドックからも消す
app.on('window-all-closed', () => {
if (process.platform === 'darwin') {
app.dock.hide()
}
})
タイマーを作る
Tray 経由でウィンドウを開くことができるようになったので、タイマー機能を作ります。
時間の入力フォームを作る
時分の入力フォームとボタンを一つ用意しました。
ボタンをクリックした時に、メインプロセス側に入力した時間を送信します。
const onClick = (): void => {
// time = {
// hours: number
// minutes: number
// }
window.electron.ipcRenderer.invoke('setTimer', time)
}
入力した時間をメインプロセスに保存する
時間と分を記憶するオブジェクトを定義し、レンダラー側の入力を受け付けるようにしました。
type Time = {
hours: number;
minutes: number;
};
let notifyTime: Time | null = null;
ipcMain.handle("setTimer", (event, time: Time) => {
console.debug("setTimer", time);
notifyTime = time;
});
入力した時間が来たときに通知する
保存した時間を検知する処理を書きます。
まずは、1 分ごとに発火する EventEmitter を作成します。(setInterval
で十分だったのですが使ってみたかったので、EventEmitter にしました)
// 1分ごとにtimeイベントをemitする
const intervalEventEmitter = new EventEmitter();
setInterval(() => {
intervalEventEmitter.emit("time", new Date());
}, 1000 * 60);
作った EventEmitter のイベントが発火した時に時刻を調べて、通知する処理です。
intervalEventEmitter.on("time", (now: Date) => {
if (notifyTime == null) {
return;
}
if (
notifyTime.hours === now.getHours() &&
notifyTime.minutes === now.getMinutes()
) {
const notification = new Notification({
title: "時間になりました。",
});
notification.show();
}
});
実際の通知はこんな感じです。
Discussion