⚡️

ElectronでローカルショートカットキーのイベントをNext.js(レンダラープロセス)でハンドリングする方法

2023/07/09に公開

概要

  • ElectronのローカルショートカットキーのイベントをNext.jsでハンドリングできるようにする
  • メインプロセスからレンダラープロセスへ通知する方法はいくつか種類がありわかりづらい。今回は contextBridge を使用するパターンを用いる

前提

Next.js公式のElectronのサンプルプロジェクトをベースにします

$ npx create-next-app --example with-electron-typescript sample

package.json に指定されているElectronのバージョンが古いので ^24 に上げています

contextIsolationtrue にする

Electronでは、メインプロセスとレンダラープロセス間のやり取りをセキュアにするために contextBridge という仕組みが用意されています。
contextBridge を使用するためには、メインウィンドウ作成時に contextIsolationtrue にする必要があります。

electron-src/index.ts
const mainWindow = new BrowserWindow({
        // 省略
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: join(__dirname, 'preload.js'),
        },
    })

preload.tscontextBridge の設定

preloadスクリプトを書くことで、レンダラープロセスが起動する前に一部のコードを事前にロードすることができます。
contextBridge.exposeInMainWorld は2つの引数をとり、1つ目に指定された引数の文字列でアクセスできるようになります (window.electron)
2つ目の引数はレンダラープロセスで利用するメソッドなどを定義します。
ここで定義したリスナーは、window.electron.on を使って呼び出すことができます。

electron-src/preload.ts
contextBridge.exposeInMainWorld('electron', {
    on: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => IpcRenderer) => {
        ipcRenderer.on(channel, (_event, ...args) => listener(_event, ...args))
    },
})

メインウィンドウにローカルショートカットキーを追加し、発火時に通知

https://www.electronjs.org/ja/docs/latest/tutorial/keyboard-shortcuts#ローカルショートカッ��%8

公式ドキュメントのこちらの方法で追加します。
今回は ⌘+N の入力をハンドリングする想定です。

通知は、mainWindow.webContents.send で行います。

electron-src/index.ts
app.on('ready', async () => {
    // 省略

    const mainWindow = new BrowserWindow({
        // 省略
    })

    // 省略

    const menu = Menu.getApplicationMenu()
    if (menu != null) {
        menu.append(new MenuItem({
            label: 'Test',
            submenu: [{
                label: '作成',
                accelerator: process.platform === 'darwin' ? 'Cmd+N' : 'Alt+N',
                click: () => {
                    mainWindow.webContents.send('shortcut-event', 'args1')
                }
            }]
        }))

        Menu.setApplicationMenu(menu)
    }
})

レンダラープロセス側(Next.js)で購読

購読は、window.electron.on で行いますが、型定義がないので別途定義しておきます。
Electronをレンダラー側でimportしなくても済むようにいくつかの変数はany型で定義しています

declare global {
    interface Window {
        electron: {
            on: (channel: string, listener: (event: any, ...args: any[]) => void) => void
        }
    }
}

window.electron.on で指定するチャンネル名は、先ほどの mainWindow.webContents.send で指定したチャンネル名と同じものにします。
任意のReactコンポーネントの、useEffect内で購読します。

renderer/components/Top.tsx
useEffect(() => {
    window.electron.on('shortcut-event', (event, ...args) => {
        // ショートカットーキーが押された時のアクションを記載
        // argsには先ほど送信した、「args1」が含まれます
    })
}, [])

参考

https://ichiri.biz/tech/electron-ipcmain-ipcrenderer/#toc7

Discussion