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にページを作っていきます。