Macと Electron Forge + React + MUI でPCアプリを作る(その3)の続きです。
こちらを参考にさせていただき、OSの任意Shellコマンド(lsとかdfとか)を実行し表示する機能を作成してみます。
まずメインプロセスでコマンドを実行するためのchild_processというモジュールと、コマンドの実行時に返ってくる文字のコードをShiftJISかUnicodeに変換するために、encoding-japaneseをインストールします。
$ npm install child_process encoding-japanese
メインプロセスに関数を作ります。src/mainにfunctionsディレクトリを作成し、中にShellコマンド実行機能 shellFunction.ts を作成します。
$ cd ~/electron/my-app/src/main $ mkdir functions $ cd functions
$ vim shellFunction.ts ----- import { ipcMain, NotificationConstructorOptions, Notification, } from "electron"; import { promisify } from "util"; const childProcess = require("child_process"); const Encoding = require("encoding-japanese"); /** * s-jisをUnicodeに変換する関数 * @param bytes s-jisの文字列 * @returns */ export const SJIStoUNICODE = (bytes: string) => { return Encoding.convert(bytes, { from: "SJIS", to: "UNICODE", type: "string", }); }; /** * コマンドを実行する関数 * @param cmd 実行したいコマンド * @returns コマンドの実行結果 */ const cmdFunction = async (cmd: string): Promise<string> => { const exec = promisify(childProcess.exec); try { const result = await exec(cmd, { encoding: "Shift_JIS" }); if (result?.error) { const errorstr = SJIStoUNICODE(result.error); return errorstr; } const stdout = SJIStoUNICODE(result.stdout); return stdout; } catch (err) { console.error(err); return "コマンドが不正です"; } }; const shellFunctionListener = () => { // 戻り値のあるものは"handle"でリスナーを立てて、 // レンダラー側はinvokeを使って ipcMain.handle( "exec-cmd", async (event, [cmd]: string[]) => await cmdFunction(cmd) ); }; export default shellFunctionListener; ----- :wq
main.ts を編集して、以下のように修正します。mainWindowは外に出しておきます。
$ cd ../ $ vim main.ts ----- import { app, BrowserWindow } from 'electron'; import path from 'path'; import shellFunctionListener from "./functions/shellFunction"; // 機能 shellFunctionListener(); // letで宣言。もとのconstは削除 let mainWindow: BrowserWindow | null = null; // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (require('electron-squirrel-startup')) { app.quit(); } const createWindow = (): void => { // Create the browser window. mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), }, }); // and load the index.html of the app. if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); }); // Open the DevTools. // mainWindow.webContents.openDevTools(); mainWindow.on("ready-to-show", () => { if (!mainWindow) { throw new Error('"mainWindow" is not defined'); } if (process.env.START_MINIMIZED) { mainWindow.minimize(); } else { mainWindow.show(); } }); mainWindow.on("closed", () => { mainWindow = null; }); }; // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', createWindow); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and import them here.
preload.ts を以下のように編集。
$ vim preload.ts ----- // See the Electron documentation for details on how to use preload scripts: // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts import { contextBridge, ipcRenderer, } from "electron"; // レンダラープロセスとメインプロセスの通信はこちらで定義する const electronHandler = { shell: { execCmd: async (cmd: string): Promise => { return await ipcRenderer.invoke("exec-cmd", [cmd]); }, }, }; contextBridge.exposeInMainWorld("electron", electronHandler); export type ElectronHandler = typeof electronHandler; ----- :wq
srcディレクトリ直下に preload.d.ts を作成。preload.tsで定義してエクスポートしたelectronHandlerの型をインポートし、winodowオブジェクトのインターフェースにelectron型として追加します。
$ cd ../ $ vim preload.d.ts ----- import { ElectronHandler } from "./main/preload"; declare global { // windowからpreloadで定義したelectronHandlerを呼び出せるように型を追加 interface Window { electron: ElectronHandler; } } export {}; ----- :wq
これでページからShellコマンド機能を呼び出す準備ができました。つぎにrendererにページを作っていきます。