Macと Electron Forge + React + MUI でPCアプリを作る(その4)

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

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


リンクが含まれる投稿はサイト管理者の承認後に表示されます(スパム対策)