Windows11秒～Electronによる初めてのアプリ開発～

## 序幕

2022年春。
WindowsUpdateで準備完了連絡が来たので、
Windows10からWindows11へUpdateしました。

<details>
<summary>:notebook_with_decorative_cover:</summary>

&nbsp;
ところが、

:::note alert
__タスクバーコーナー（＝通知領域＝タスクトレイ）__<font color="red">__時計に秒が無い!!__</font> 
:::

のです...

Windows10ではレジストリを変更すれば、
タスクトレイの時計に秒を追加することが出来たのですが、
Windows11では出来なくなってしまったらしい...

:::note warn
2023/05/10 追記
2023-04 x64 Windows 11 Version 22H2 の累積更新プログラム (KB5025305) の一部を含む、
2023-05 x64 Windows 11 Version 22H2 の累積更新プログラム (KB5026372) を適用すると、
Windows11でもShowSecondsInSystemClockをレジストリに設定することで、
タスクトレイの時計に秒を表示することが出来るようになったようです。
:::

アプリを検索してみるも高機能and多機能すぎる...
時計の横に秒があればよいだけなのです...
時計の傍に秒があればよいだけなのです...
こんな感じに...

```
|　　　　　 [タスクトレイ]
|･ω･)ﾁﾗｯ　　[タスクトレイ]
|彡ｻｯ!　　　[タスクトレイ]
|･ω･)ﾁﾗｯ　　[タスクトレイ]
|　ｽｯ!( ･ω･)⊃(秒)トレイ]
|･ω･)　　　 [タ(秒)トレイ]
|彡ｻｯ!　　　[タ(秒)トレイ]
|　　　　　 [タ(秒)トレイ]
```

その日は悶々としながら就寝しました。
するとマリーアントワネットが言うのです。

:::note info
<font color="red">__秒がないなら（自分で）描画したらいいじゃない__</font> 
:::

```
m9(･∀･)ｿﾚﾀﾞｯ!!
```

</details>

ということで、
初めてさわるNode.jsとElectron、
素人によるアプリ開発で出来たのが、
Windows11秒（win11sec.exe）です。

![2022.0222.0222.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55480/6de8dddf-aad6-099f-b86e-d161c2ccd4ea.png)

<details>
<summary>:notebook_with_decorative_cover:</summary>

|クソアプリな点|
|:---------------|
|クソアプリな点①|
|[通知と通知領域](https://docs.microsoft.com/ja-jp/windows/win32/shell/notification-area)や[通知領域](https://docs.microsoft.com/ja-jp/windows/win32/uxguide/winenv-notification)に記載の通り、<br> [タスクバーの通知領域をカスタマイズする](https://support.microsoft.com/ja-jp/windows/e159e8d2-9ac5-b2bd-61c5-bb63c1d437c3#WindowsVersion=Windows_11)にて、<br>クソアプリをユーザーがオンにしてくれないと、<br> タスクバーコーナーのオーバーフローに、<br>アイコンが入れられてしまうため、<br>肝心の秒が見えない...|
|クソアプリな点②|
|とことん低機能なのにも関わらず、<br>ElectronなのでChromiumを内包しているため、<br>ファイルサイズがビックサイズ!!|
|クソアプリな点③|
|とことん低機能なのにも関わらず、<br>起動の際にそのChromiumを展開するので、<br>起動に時間が掛かる。|

</details>

## 注意

:::note warn
注意
Windows11秒というタイトルの通り、
Windows10の環境でも大丈夫だとは思いますが、
Windows11の環境を本投稿は前提としています。
:::

:::note warn
注意
htmlやjsはある程度は分かるという人を本投稿は対象としています。
:::

## Node.js

:::note info
参照
[Node.jsとはなにか？なぜみんな使っているのか？](https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb)
[npmって何者？](https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb#npm%E3%81%A3%E3%81%A6%E4%BD%95%E8%80%85)
:::

とても分かりやすいです。

Electronによるアプリ開発には、
Node.js および npm（or yarn）が必要です。

## Node.jsのインストール

:::note info
参照
[Node.jsをインストールする](https://qiita.com/sefoo0104/items/0653c935ea4a4db9dc2b)
:::

とても分かりやすいです。

https://nodejs.org/ja/
ダウンロードしてインストールします。
Windowsの一般的なアプリのインストールと変わりませんので、
Windowsの一般的なアプリのインストールをするように、
Node.jsをインストールください。

## 作業場所

win11secフォルダをWindows11のデスクトップに新規作成。
win11secフォルダを開いてアドレスバーにcmdと打ってEnter。

コマンドプロンプトが自動的に開いて、
`C:\Users\USERNAME\Desktop\win11sec>`
となっていると思います。

:::note warn
注意
全角文字や半角スペースが含まれている場合は、
Cドライブ直下にwin11secフォルダとかの方が無難です。
:::

:::note info
参考
[エクスプローラの今すぐ使えるアドレスバー芸](https://qiita.com/divideby_zero/items/17956579bd115d2613ac)
:::

:::note info
情報
本投稿において、コマンドプロンプトにて、
以下のディレクトリの中で各種全ての作業を行ないます。
以下のディレクトリ以外に移動しての作業はありません。
`C:\Users\USERNAME\Desktop\win11sec>`
:::

:::note info
情報
本投稿において、
ディレクトリとフォルダは同じものとして扱います。
[「ディレクトリ」と「フォルダ」の違いと共通点](https://uxmilk.jp/27546)
[Windowsにおけるフォルダーとディレクトリとは](https://ascii.jp/elem/000/004/081/4081147/)
:::

:::note warn
注意
Windowsではバックスラッシュが円記号になりがちです。
C:\Users\USERNAME\Desktop\win11sec> が、
C:&yen;Users&yen;USERNAME&yen;Desktop&yen;win11sec> と、
コマンドプロンプトの表記もなってしまいます。
適時「\」を「&yen;」に置き換えてお読みください。
[バックスラッシュと円記号の悲劇](https://juangotoh.hatenablog.com/entry/2016/06/13/104139)
[Windowsのパス区切り文字は、なぜ逆スラッシュになったのか？](https://ascii.jp/elem/000/001/763/1763591/)
:::

Windows11か念の為の確認をします。
winverと打ってEnter。

win11sec> `winver`
Windows11であればOK。

## npmの準備

Node.jsのインストールが無事完了しているかの確認、および、
Node.jsのインストールの際に一緒にインストールされている、
npmの確認、さらに、
npmの初期化処理（package.jsonの作成）をします。

win11sec> `node -v`
バージョン番号が表示されればOK。

win11sec> `npm -v`
バージョン番号が表示されればOK。

win11sec> `npm init -y`

-yオプションを付けないと色々質問されますが、
-yオプションを付けるとデフォルトの内容で、
npmの初期化処理（package.jsonの作成）をしてくれます。

処理が終わると、
win11secフォルダにpackage.jsonが出来ています。

win11secフォルダのpackage.jsonを、
テキストエディタで開いて、
ひとまず以下に書き換えます。

```json:package.json
{
  "name": "win11sec",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {},
  "keywords": [],
  "author": "",
  "license": ""
}
```

package.jsonの内容をきちんと書きたい場合は、
以下参考になります。とても分かりやすいです。

:::note info
参考
[package.jsonの内容をまとめてみました](https://qiita.com/gght/items/f74788c728b33f071e50)
[package.json dependencies メンテの仕方 最短ルート](https://qiita.com/alfe_below/items/1141ec9acbb81b504855)
:::

## Electronのインストール

Electronをnpmでインストールします。

win11sec> `npm i -D electron`

Electronのインストールが無事完了したか確認します。

<!--
win11sec> `npx electron -v`
-->
win11sec> `node_modules\.bin\electron -v`
バージョン番号が表示されればOK。

:::note info
情報
ダウンロードしながらインストールするので、
1時間弱とか結構時間が掛かる場合があります。
:::

:::note warn
注意
エラーがインストール時に何か表示された場合は、
エラーの内容でググって解決をしてくださいませ。
エラーの内容次第では未解決で放置の場合もあり。
:::

## electron-builderのインストール

electron-builderもnpmでインストールします。

win11sec> `npm i -D electron-builder`

electron-builderのインストールが無事完了したか確認します。

<!--
win11sec> `npx electron-builder --version`
-->
win11sec> `node_modules\.bin\electron-builder --version`
バージョン番号が表示されればOK。

:::note info
情報
ダウンロードしながらインストールするので、
1時間弱とか結構時間が掛かる場合があります。
:::

:::note warn
注意
エラーがインストール時に何か表示された場合は、
エラーの内容でググって解決をしてくださいませ。
エラーの内容次第では未解決で放置の場合もあり。
:::

## 各種インストール完了

Electronのインストールと、
electron-builderのインストールにより、
win11secのフォルダに以下変更が施されています。

①node_modulesフォルダが作成されています。
②package-lock.jsonが新規に作成されています。
③package.jsonの下部に以下が追記されています。

```diff_json:package.json
{
  "name": "win11sec",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {},
  "keywords": [],
  "author": "",
  "license": "",
+ "devDependencies": {
+   "electron": "^xx.x.x",
+   "electron-builder": "^xx.x.x"
+ }
}
```

## npmの参考

:::note info
参考 npm install
[そろそろ適当に npm install するのを卒業する](https://qiita.com/sugurutakahashi12345/items/3cc49926faeaf25d3051)
:::

:::note info
参考 npm
[よく利用する npm コマンド](https://qiita.com/taqumo/items/9db77622c99dfb7f05f7)
[npm よく使うコマンドまとめ](https://qiita.com/standard-software/items/2ac49a409688733c90e7)
[npm自体のバージョンを上げる](https://qiita.com/n0bisuke/items/b2704b6ebb84f21c03c1)
[npm package をアップデートしたい時の便利なコマンド](https://qiita.com/taigamikami/items/2f2ac9d5197916a49132)
[package.json に記載されているパッケージのバージョンアップ方法](https://qiita.com/sugurutakahashi12345/items/df736ddaf65c244e1b4f)
:::

:::note info
参考 npx
[npm 5.2.0の新機能！ 「npx」でローカルパッケージを手軽に実行しよう](https://qiita.com/tonkotsuboy_com/items/8227f5993769c3df533d)
[npm でローカルインストールしたパッケージを CLI でコマンド実行する方法（npm-srcipts, npx）](https://qiita.com/sugurutakahashi12345/items/b814a09b65d8852226ad)
:::

:::note info
参考 npm-scripts
[npm-scriptsについて](https://qiita.com/axross/items/a2a0d148e40b66074858)
[npm-scriptsを使ってみる](https://qiita.com/tminasen/items/986feac7150ed74e13d8)
:::

:::note info
参考 npm fund
[npm fundとは何か](https://qiita.com/yukibear/items/0fd0a02e7ef75cfdaecf)
:::

:::note info
参考 npm cli
https://github.com/npm
https://github.com/npm/cli
https://github.com/npm/cli/releases
:::

## Electronの参考

:::note info
参考 Electron
[【Electron】HTML/CSS/JavaScript でデスクトップアプリを作った【インストールから exe 化まで】](https://qiita.com/ndj/items/a07b433aa3f6f6e8742f)
[Electronで1からデスクトップアプリを作り、electron-builderを使ってビルド・リリースするまで](https://qiita.com/saki-engineering/items/203892838e15b3dbd300)
[日頃お世話になっているElectronのアプリ開発に入門してみる](https://qiita.com/y-tsutsu/items/179717ecbdcc27509e5a)
[Electronの環境構築（for Windows）](https://qiita.com/TigRig/items/64d55b5fc5483b01c3b5)
:::

:::note warn
注意 Electron
Electronはバージョンアップで[破壊的変更](https://www.electronjs.org/ja/docs/latest/breaking-changes)が結構あるようです。
[Ver14.0のremoteモジュールの削除](https://www.electronjs.org/blog/electron-14-0#removed-remote-module)等の破壊的変更は影響が大きく、
数年前に書かれたコードは動かない可能性があります。
インターネット上のElectronの情報を参考にする際は、
`contextBridge`のコードの有無がひとつの目安かなと思います（2022年現在）
`contextBridge`に関しては後述します。
:::

## Hello World

では Hello World してみましょう。

:::note warn
注意
[electron.orgのクイックスタート](https://www.electronjs.org/ja/docs/latest/tutorial/quick-start)、および、
[electron.orgのサンプル](https://www.electronjs.org/ja/docs/latest/tutorial/examples)と同様の内容です。

:::

win11secフォルダで以下ファイルを新規作成します。

`index.html`
`main.js`
`preload.js`
`renderer.js`

win11secフォルダの構成は結果以下となります。

```console:win11sec（フォルダ）
node_modules（フォルダ）
index.html
main.js
package.json
package-lock.json
preload.js
renderer.js
```

テキストエディタで開いて、
各々の中身を以下の内容にしてください。

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
</head>
<body>
<p>Hello World</p>
<script src="renderer.js"></script>
</body>
</html>
```

```javascript:renderer.js
console.log('Hello World ( renderer.js )');
```

```javascript:preload.js
console.log('Hello World ( preload.js )');
```

```javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
} = require('electron');

let mainWindow = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});
```

ひとまず実行してみましょう。

コマンドプロンプト、
C:\Users\USERNAME\Desktop\win11sec>
にて、
win11sec> `npx electron .`
です。

npxは手軽な実行用のコマンドです。
npxに関しては前述のリンク等を参照ください。

上記コマンドは、
どういう表現が正しいのか分かりませんが、
npxにてnpmのelectronでカレントディレクトリ（＝［.］）を読んで実行、
というところでしょうか。

カレントディレクトリ（＝［.］）の何を読んでいるのでしょうか。
package.jsonです。そして、
package.jsonのmainにエントリポイントのファイル名が記載されています。
ですのでmain.jsを起点に動作を開始します。

```json:package.json
  "main": "main.js",
```

:::note info
参考
[メインプロセスの用語説明内のエントリポイント関する記述](https://www.electronjs.org/ja/docs/latest/glossary#%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9-main-process)
:::

起動したら、
デベロッパーツールのConsoleのタブをクリックしてください。

コマンドプロンプトに「Hello World ( main.js )」、
デベロッパーツールに「Hello World ( preload.js )」、
デベロッパーツールに「Hello World ( renderer.js )」、
と表示されています。
各jsの`console.log()`の出力先が分かりました。
今後のデバッグに活用ください。

:::note warn
注意
何らかのエラーが出て処理が戻ってこない場合は、
コマンドプロンプトをアクティブウィンドウにしてCTRL+Cを押します。
バッチ ジョブを終了しますか (Y/N)?
と尋ねられるので、
キーボードのYを押す事で終了します。
キーボードのNを押しても終了します (^-^A
:::

## Hello World 説明

では、各ファイルの説明です。

`index.html`
`renderer.js`
`preload.js`

上記三ファイルの中身の内容は、
htmlやjsを触っている人であれば、
特に難しい内容では無いと思いますので割愛します。

問題は`main.js`ですね。

#### Hello World 説明①

```javascript
const {
  app,
  BrowserWindow,
} = require('electron');
```

```javascript
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
```

Node.jsでのElectronのモジュールの読み込みです。
前者は後者を[オブジェクトの分割代入](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#object_destructuring)を使用して書いたコードになります。

:::note info
参考
[jsのimportとrequireの違い](https://qiita.com/minato-naka/items/39ecc285d1e37226a283)
[[Node.js]モジュール定義について整理（exports-require / export-import）](https://qiita.com/aki3061/items/1dc76e193d8e4cccf4ce)
:::

:::note info
参考
[electronjs.org app](https://www.electronjs.org/ja/docs/latest/api/app)
[electronjs.org BrowserWindow](https://www.electronjs.org/ja/docs/latest/api/browser-window)
:::

#### Hello World 説明②

```javascript
let mainWindow = null;
```

メインウインドウのインスタンスの保持用のグローバル変数の宣言です。

#### Hello World 説明③

```javascript
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});
```

> 一般的に Windows や Linux では、
> すべてのウィンドウを終了するとアプリケーションが完全に終了

するので本処理を記述するそうです。

:::note info
参考
[electron.orgのクイックスタート](https://www.electronjs.org/ja/docs/latest/tutorial/quick-start#%E3%82%A6%E3%82%A4%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%82%92%E7%AE%A1%E7%90%86%E3%81%99%E3%82%8B)
:::

:::note info
参考
[Darwin](https://qiita.com/zawawahoge/items/2ec3b638422c7c8cac8f#3-darwin)
[Node.js documentation process.platform](https://nodejs.org/api/process.html#processplatform)
:::

#### Hello World 説明④

```javascript
app.on('quit', () => {
});
```

Electronの終了時に実行されます。
Hello World では処理する内容はありません。
カラの状態です。

#### Hello World 説明⑤-1

```javascript
app.whenReady().then(() => {
});
```

Electronの初期化時に実行されます。

#### Hello World 説明⑤-2

```javascript
  mainWindow = new BrowserWindow({
    width: 1600,
    height: 900,
    useContentSize: true,
    webPreferences: {
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
```

メインウインドウのインスタンスの生成をして、
メインウインドウのインスタンスの保持用のグローバル変数に格納します。

#### Hello World 説明⑤-2-1

```javascript
    width: 1600,
    height: 900,
    useContentSize: true,
```

[new BrowserWindow() のオプション](https://www.electronjs.org/ja/docs/latest/api/browser-window#new-browserwindowoptions) で以下を設定。
メインウインドウ生成時のウインドウの横幅。
メインウインドウ生成時のウインドウの高さ。
上記の横幅と高さがビューポートとしての値としてメインウインドウ生成。

#### Hello World 説明⑤-2-2

```javascript
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
```

[new BrowserWindow() のオプション](https://www.electronjs.org/ja/docs/latest/api/browser-window#new-browserwindowoptions) で webPreferences の preload を設定。
他のスクリプトがページで実行される前にロードされるスクリプトを指定?
よく分かりませんね、後述しますので、今はそっとしておきましょう。

#### Hello World 説明⑥

```javascript
  mainWindow.loadFile('index.html');
```

index.htmlを読み込んでメインウインドウに表示します。

#### Hello World 説明⑦

```javascript
  mainWindow.webContents.openDevTools();
```

デベロッパーツール（DevTools）を表示します。

:::note info
参考
Electronがデフォルトの状態の場合、
Electronのデベロッパーツールの開閉は、
F12 ではなく Ctrl+Shift+I です。
:::

#### Hello World 説明⑧

```javascript
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
```

ウィンドウが閉じた時に実行されます。

ウィンドウが閉じた時にnullを代入するようにして、
インスタンスを参照していない状態に念の為します。

## メインウインドウやタスクバーのアイコン

Windows11秒（win11sec.exe）は、
タスクトレイのアイコンを毎秒変更することで、
タスクトレイに秒を描画しています。

Hello World が上記でできましたので、
タスクトレイのアイコンの変更の前哨戦として、
メインウインドウやタスクバーのアイコンを変更してみます。

[ICOOON MONO](https://icooon-mono.com/) から画像をお借りします。
[ベルのフリーアイコン素材](https://icooon-mono.com/00151/)
[時計の無料アイコン](https://icooon-mono.com/11593/)

上記の256pxのPNGをダウンロードして、
以下のファイル名でwin11secフォルダに保存してください。
`bell.png`
`clock.png`

結果、
win11secフォルダの中は以下となっています。

```console:win11sec（フォルダ）
node_modules（フォルダ）
bell.png
clock.png
index.html
main.js
package.json
package-lock.json
preload.js
renderer.js
```

`main.js`に以下を追記します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
+ nativeImage,
} = require('electron');

let mainWindow = null;

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // 中略
    },
  });
+ // メインウインドウのアイコンの設定
+ mainWindow.setIcon('clock.png');
+ // タスクバーのアイコンにオーバーレイされる画像の設定
+ mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
} = require('electron');

let mainWindow = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});
```

</details>

[setIcon](https://www.electronjs.org/ja/docs/latest/api/browser-window#winseticonicon-windows-linux)()の引数は、
stringによるファイル名の指定がサポートされてますが、

[setOverlayIcon](https://www.electronjs.org/ja/docs/latest/api/browser-window#winsetoverlayiconoverlay-description-windows)()の引数は、
[nativeImage](https://www.electronjs.org/ja/docs/latest/api/native-image)でなければなりません（2022年現在）

[nativeImage](https://www.electronjs.org/ja/docs/latest/api/native-image)のインスタンスを作成してくれる、
[nativeImage.createFromPath](https://www.electronjs.org/ja/docs/latest/api/native-image#nativeimagecreatefrompathpath)()のメソッドが用意されているので、
それを利用して`bell.png`の画像を設定しています。

実行してみます。
win11sec> `npx electron .`

メインウインドウとタスクバーのアイコンが変更されます。

:::note info
備考
Windowsにおけるアイコンの画像ファイルフォーマットであるICO、
Wikipediaの [ICO（ファイルフォーマット）](https://ja.wikipedia.org/wiki/ICO_%28%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%29#:~:text=Windows%20Vista%E3%81%AF256,%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%80%82)に以下記載があったため、
256x256pxのpngを試しに指定してみたら大丈夫そうだったので、
本投稿ではアイコンにpngを使用しています。
> Windows Vistaは256×256ピクセルのアイコン画像を
> そのまま表示するモードもサポートしており、
> （必須ではないが）圧縮したPNGフォーマットもサポートしている。
:::

:::note info
参考
[Electron製アプリでアイコンバッジを実装する(Mac,Windows対応版)](https://qiita.com/dmnlk/items/d37bfbf52fb63eb2134e)
:::

:::note info
参考
[electronjs.org app](https://www.electronjs.org/ja/docs/latest/api/app)
[electronjs.org BrowserWindow](https://www.electronjs.org/ja/docs/latest/api/browser-window)
[electronjs.org nativeImage](https://www.electronjs.org/ja/docs/latest/api/native-image)
:::

## タスクトレイのアイコン

タスクトレイにアイコンを追加します。

:::note info
備考
タスクバーコーナー、通知領域、システムトレイ、タスクトレイ、
などと時代時代で表記されてきました。
Windows11ではタスクバーコーナーというようですが、
本投稿ではタスクトレイと表記します。
:::

`main.js`に以下を追記します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
+ Tray,
} = require('electron');

let mainWindow = null;
+ let tray = null;

// 中略

// Electronの終了時に実行
app.on('quit', () => {
+ // タスクトレイのアイコンの削除
+ tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
+ // タスクトレイのアイコンの作成
+ tray = new Tray(nativeImage.createFromPath('clock.png'));
+ // タスクトレイのアイコンのホバーテキストの設定
+ tray.setToolTip('windows11秒');
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // 中略
  });
  // 中略
});
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Tray,
} = require('electron');

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});
```

</details>

実行してみます。
win11sec> `npx electron .`

Windows11の仕様により、
タスクトレイにアイコンが表示されませんが、
[タスクバーコーナーオーバーフローアイコンの表示設定を変更](https://support.microsoft.com/ja-jp/windows/e159e8d2-9ac5-b2bd-61c5-bb63c1d437c3#WindowsVersion=Windows_11)すれば、
タスクトレイにアイコンが表示されます。

:::note info
参考
[Customize the taskbar notification area](https://support.microsoft.com/en-us/windows/e159e8d2-9ac5-b2bd-61c5-bb63c1d437c3#WindowsVersion=Windows_11)
[タスクバーの通知領域をカスタマイズする](https://support.microsoft.com/ja-jp/windows/e159e8d2-9ac5-b2bd-61c5-bb63c1d437c3#WindowsVersion=Windows_11)
[windows11タスクバーコーナーオーバーフローアイコン表示](https://www.google.com/search?q=windows11%E3%82%BF%E3%82%B9%E3%82%AF%E3%83%90%E3%83%BC%E3%82%B3%E3%83%BC%E3%83%8A%E3%83%BC%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E8%A1%A8%E7%A4%BA)
:::

:::note info
参考
[Electron で Tray を使う（macOS, Windows）](https://qiita.com/hibara/items/4a3c26817e5449ebf722)
:::

:::note info
参考
[electronjs.org Tray](https://www.electronjs.org/ja/docs/latest/api/tray)
:::

## タスクトレイのアイコンのメニュー

Windows11秒（win11sec.exe）は、
メインウインドウを非表示にすることになりますので、
メインウインドウの右上の×ボタンを押せません。

そのままですと終了できなくなってしまうので、
タスクトレイのアイコンにメニューを追加して、
終了することができるようにします。

ついでに、
Windows11のタスクバーの設定を開けるようにします。
`main.js`に以下を追記します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
+ Menu,
  Tray,
+ shell,
} = require('electron');

let mainWindow = null;
let tray = null;

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
+ // メニューの構築
+ const contextMenu = Menu.buildFromTemplate([
+   {
+     label: 'Settings',
+     click: () => {
+       shell.openExternal('ms-settings:taskbar');
+     },
+   },
+   {
+     label: 'Quit',
+     click: () => {
+       app.quit();
+     },
+   },
+ ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
+ // タスクトレイのアイコンのコンテキストメニューの設定
+ tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // 中略
  });
  // 中略
});
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  shell,
} = require('electron');

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});
```

</details>

実行してみます。
win11sec> `npx electron .`

タスクトレイのアイコンを右クリックするとメニューが表示されます。

実行してみてください。
win11sec> `start ms-settings:taskbar`
Windows11のタスクバーの設定が開きます。
`shell.openExternal('ms-settings:taskbar');`の部分は、
これとほぼ同じことをしています。

:::note info
駄文
`ms-settings:personalization-start`
`ms-settings:personalization-start-places`
`ms-settings:easeofaccess-narrator`
などはtoggleにfocusした状態で設定を開けます。
`ms-settings:easeofaccess-narrator-isautostartenabled`
に至ってはアコーディオンを開いてfocusした状態で設定を開けます。

`ms-settings:taskbar`でタスクバーの設定を開くことはできますが、
タスクバーコーナーのオーバーフローのアコーディオンを開いた状態にしたい...
タスクバーコーナーのオーバーフローのアコーディオンを開いた状態の指定方法は無いのでしょうか...

[アイテムの正規コントロール パネル名](https://docs.microsoft.com/ja-jp/windows/win32/shell/controlpanel-canonical-names#notification-area-icons)
[Command line to get "select which icons appear on the taskbar"](https://www.tenforums.com/general-support/131746-command-line-get-select-icons-appear-taskbar.html#post1626087)
`explorer shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}`にて、
Windows11では通常使用しない設定画面であれば開けるようですが...
:::

:::note info
参考
[electronjs.org Menu](https://www.electronjs.org/ja/docs/latest/api/menu)
[electronjs.org shell](https://www.electronjs.org/ja/docs/latest/api/shell)
:::

## 秒の取得

`main.js`に以下を追記します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  shell,
} = require('electron');

+ let interval = null;
+ let prevSec = null;
+ let thisSec = null;

let mainWindow = null;
let tray = null;

// 中略

// Electronの終了時に実行
app.on('quit', () => {
+ // インターバルのタイマーの繰り返し動作の取り消し
+ clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
+ // 1/30秒に1回インターバルで現在時刻の秒を確認
+ interval = setInterval(() => {
+   const now = new Date();
+   thisSec = now.getSeconds();
+   if (thisSec !== prevSec) {
+     console.log(thisSec);
+   }
+   prevSec = thisSec;
+ }, 33);
});
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  shell,
} = require('electron');

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // インターバルのタイマーの繰り返し動作の取り消し
  clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
      console.log(thisSec);
    }
    prevSec = thisSec;
  }, 33);
});
```

</details>

実行してみます。
win11sec> `npx electron .`

コマンドプロンプトに秒が出力されていきます。

※Electron特有のコードではなく、
　通常のJavaScriptのコードですので説明は簡単に済ませます。

[setInterval](https://developer.mozilla.org/ja/docs/Web/API/setInterval)()にて33ミリ秒(≒1/30秒)に1回、
[Date](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/Date)()および[getSeconds](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getSeconds)()で現在の秒を取得。
秒が切り替わった瞬間のみconsole.logに秒を出力しています。

:::note info
参考
[developer.mozilla.org setInterval()](              https://developer.mozilla.org/ja/docs/Web/API/setInterval)
[developer.mozilla.org clearInterval()](            https://developer.mozilla.org/ja/docs/Web/API/clearInterval)
[developer.mozilla.org Date](                       https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date)
[developer.mozilla.org Date()](                     https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/Date)
[developer.mozilla.org Date.prototype.getSeconds()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getSeconds)
:::

## 秒の画像

[Canvas API](https://developer.mozilla.org/ja/docs/Web/API/Canvas_API) で秒の画像の作成と取得をします。

`main.js`のどこかに、
`const canvas = document.createElement('canvas');`を追記して実行すると、
`document is not defined`とエラーになり実行できません。ですので、
`renderer.js`で行なうことになります。

#### 秒の画像①

[Canvas API](https://developer.mozilla.org/ja/docs/Web/API/Canvas_API) でまずは数字の00の作成をします。

`renderer.js`に以下を追記します。

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
+ // キャンバスの生成
+ const canvas = document.createElement('canvas');
+ canvas.width = 256;
+ canvas.height = 256;
+ // キャンバスのコンテキストの取得
+ const ctx = canvas.getContext('2d');
+ ctx.textAlign = 'center';
+ ctx.fillStyle = 'black';
+ ctx.font = '200px monospace';
+ ctx.fillText('00', 128, 222);
+ // htmlにcanvas要素を追加して表示
+ document.body.append(canvas);
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
```

</details>

実行してみます。
win11sec> `npx electron .`

メインウインドウに大きく00と描画されます。

※Electron特有のコードではなく、
　通常のJavaScriptのコードですので説明は簡単に済ませます。

[document.createElement](https://developer.mozilla.org/ja/docs/Web/API/Document/createElement)()で[canvas](https://developer.mozilla.org/ja/docs/Web/HTML/Element/canvas)の要素を作成。
[CanvasRenderingContext2D](https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D)を[getContext](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext)()で取得。
[CanvasRenderingContext2Dの各種設定](https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D#reference)をして、
[CanvasRenderingContext2D.fillText](https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/fillText)()で数字の00を描画。
body要素に[append](https://developer.mozilla.org/ja/docs/Web/API/Element/append)()で追加しています。

:::note info
参考
[developer.mozilla.org Canvas API](                         https://developer.mozilla.org/ja/docs/Web/API/Canvas_API)
[developer.mozilla.org \<canvas\>](                         https://developer.mozilla.org/ja/docs/Web/HTML/Element/canvas)
[developer.mozilla.org document.createElement()](           https://developer.mozilla.org/ja/docs/Web/API/Document/createElement)
[developer.mozilla.org HTMLCanvasElement.getContext()](     https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext)
[developer.mozilla.org CanvasRenderingContext2D](           https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D)
[developer.mozilla.org CanvasRenderingContext2D.fillText()](https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/fillText)
[developer.mozilla.org Element.append()](                   https://developer.mozilla.org/ja/docs/Web/API/Element/append)
:::

#### 秒の画像②

数字の画像データを取得します。

`renderer.js`に以下を追記します。

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
+ // PNG画像のDataURLの保持用の変数
+ let pngDataURL = null;
+ // toDataURLでPNG画像のDataURLを保持用の変数に格納
+ // toDataURLは引数省略の場合はimage/png
+ pngDataURL = canvas.toDataURL();
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
// PNG画像のDataURLの保持用の変数
let pngDataURL = null;
// toDataURLでPNG画像のDataURLを保持用の変数に格納
// toDataURLは引数省略の場合はimage/png
pngDataURL = canvas.toDataURL();
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
```

</details>

[toDataURL](https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/toDataURL)()で[canvas](https://developer.mozilla.org/ja/docs/Web/HTML/Element/canvas)の要素からPNG画像を取得。
[toDataURL](https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/toDataURL)()は引数を省略した場合はデフォルトでPNG画像。

実行してみます。
win11sec> `npx electron .`

デベロッパーツール Sources か Console いづれか、
Sources の Watch で pngDataURL と入力、または、
Console の Live Expression で pngDataURL と入力すると、
pngDataURL の変数の中身が、
"data:image/png;base64,なんたらかんたら"
となっているのが見て取れます。
"data:image/png;base64,なんたらかんたら"
なのでpng画像のデータになのでしょう、たぶん...

:::note info
参考
[GoogleChrome開発者ツール便利機能 値のリアルタイム監視 Live Expression](https://qiita.com/DenjiMk2/items/9ca87513195320c3915b)
[Chrome DevTools の便利な機能を何もわかっていなかった（Debugger編）](https://qiita.com/nanakenashi/items/a88304a084610af593e6#%E5%A4%89%E6%95%B0%E3%81%AE%E7%9B%A3%E8%A6%96)
[Chrome デベロッパーツールの使い方まとめ](https://qiita.com/d-dai/items/0b580b26bb1d1622eb46)
:::

:::note info
参考
[HTMLCanvasElement.toDataURL()](https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/toDataURL)
[HTML5 の canvas 要素を base64 文字列化し画像として保存する方法まとめ](https://qiita.com/clockmaker/items/924b5b4228484e7a09f0)
:::

:::note info
参考
[base64ってなんぞ？？理解のために実装してみた](https://qiita.com/PlanetMeron/items/2905e2d0aa7fe46a36d4)
[1x1の最小GIF・PNGファイル（透過/base64）](https://qiita.com/CloudRemix/items/92e68a048a0da93ed240)
:::

#### 秒の画像③

00～60の61枚の数字の画像データを作成して取得します。
閏秒の扱いが不明なために60も一応作成して取得します。

`renderer.js`に以下を追記します。

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
- // PNG画像のDataURLの保持用の変数
- let pngDataURL = null;
- // toDataURLでPNG画像のDataURLを保持用の変数に格納
- // toDataURLは引数省略の場合はimage/png
- pngDataURL = canvas.toDataURL();
+ // 61枚のPNG画像のDataURLの保持用の配列の初期化
+ const pngDataURL = [];
+ // 61枚分処理
+ [...Array(61).keys()].forEach((i) => {
+   // キャンバスのコンテキストの全領域を透明色で消去
+   ctx.clearRect(0, 0, 256, 256);
+   // キャンバスのコンテキストに数値を二桁で描画
+   ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
+   // toDataURLでPNG画像のDataURLを保持用の配列に格納
+   // toDataURLは引数省略の場合はimage/png
+   pngDataURL.push(canvas.toDataURL());
+ });
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
// 61枚のPNG画像のDataURLの保持用の配列の初期化
const pngDataURL = [];
// 61枚分処理
[...Array(61).keys()].forEach((i) => {
  // キャンバスのコンテキストの全領域を透明色で消去
  ctx.clearRect(0, 0, 256, 256);
  // キャンバスのコンテキストに数値を二桁で描画
  ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
  // toDataURLでPNG画像のDataURLを保持用の配列に格納
  // toDataURLは引数省略の場合はimage/png
  pngDataURL.push(canvas.toDataURL());
});
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
```

</details>

:::note info
参考
[How to create an array containing 1...N](https://stackoverflow.com/questions/3746725/)
[How to create an array containing 1...N : answer](https://stackoverflow.com/questions/3746725/#33352604)
[Does JavaScript have a method like "range()" to generate a range within the supplied bounds?](https://stackoverflow.com/questions/3895478/)
[Does JavaScript have a method like "range()" to generate a range within the supplied bounds? : answer](https://stackoverflow.com/questions/3895478/#10050831)
:::

## タスクトレイのアイコンに数字を描画するためには

`renderer.js`のPNG画像データの配列の内容を、
`main.js`に伝えた上で`main.js`で処理をする必要があります。
数字の画像データの作成および取得は`renderer.js`での処理でしたが、
タスクトレイにアイコンの設定をするのは`main.js`での処理だからです。

Chromiumのマルチプロセスアーキテクチャを継承した設計とのことで、
Electronはメインプロセスとレンダラープロセスの2種類のプロセスがあります。
ただし、セキュリティ上の理由で、
メインプロセスはElectronの強力なAPIにアクセスができるのに対して、
レンダラープロセスはメインプロセスのようにはAPIにアクセスできません。

本投稿においては、
`main.js`はメインプロセスで実行、
`renderer.js`はレンダラープロセスで実行されるので、
`renderer.js`のデータを`main.js`に伝えるためには、
レンダラープロセス⇒メインプロセスのプロセス間通信が必要になります。

:::note info
参考
[electronjs.org プロセスモデル](https://www.electronjs.org/ja/docs/latest/tutorial/process-model)
[electronjs.org コンテキストの分離](https://www.electronjs.org/ja/docs/latest/tutorial/context-isolation)
[electronjs.org プロセス間通信](https://www.electronjs.org/ja/docs/latest/tutorial/ipc)
:::

## プリロードスクリプト

プロセス間通信を行なうためのAPIにも、
レンダラープロセスはアクセスできません。
それでは困るので、
プリロードスクリプトに働いてもらうことになります。
<font color="red">プリロードスクリプトはAPIにアクセス可能</font>、かつ、
<font color="red">プリロードスクリプトはレンダラープロセス開始前に、</font>
<font color="red">プリロードスクリプトはレンダラープロセス内で実行</font>されます。

本投稿においては、
プリロードスクリプト＝`preload.js`
です。

Hello World の ⑥-2-2 で説明を保留にした下記の処理にて、
プリロードスクリプト（`preload.js`）を指定しています。

`mainWindow.loadFile('index.html')`の処理が行なわれる際に、
プリロードスクリプト（`preload.js`）がレンダラープロセス内で実行されます。

```diff_javascript:main.js
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // 中略
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // 中略
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
```

:::note info
参考
[electronjs.org プリロードスクリプト](https://www.electronjs.org/ja/docs/latest/tutorial/process-model#%E3%83%97%E3%83%AA%E3%83%AD%E3%83%BC%E3%83%89%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88)
:::

:::note info
参考
[electronjs.org app.getAppPath()](https://www.electronjs.org/ja/docs/latest/api/app#appgetapppath)
:::

:::note info
参考
[developer.mozilla.org テンプレートリテラル](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals)
:::

## contextBridge

#### contextBridge①

プリロードスクリプトが実行されて、
プリロードスクリプトにてAPIにアクセス可能だとしても、
レンダラープロセスは依然APIにアクセス不可のままです。

そこで、
[contextBridge](https://www.electronjs.org/ja/docs/latest/api/context-bridge)、および、
[contextBridge.exposeInMainWorld](https://www.electronjs.org/ja/docs/latest/api/context-bridge#contextbridgeexposeinmainworldapikey-api)()です。

プリロードスクリプトにて、
[contextBridge.exposeInMainWorld](https://www.electronjs.org/ja/docs/latest/api/context-bridge#contextbridgeexposeinmainworldapikey-api)()を行なうことで、
<font color="red">`window`にオブジェクトとメソッドを追加</font>します。

`window`とは、
`window.innerWidth`
`window.innerHeight`
`window.scrollY`
などでお会いするやつですね。

:::note info
参考
[electronjs.org contextBridge](https://www.electronjs.org/ja/docs/latest/api/context-bridge)
[electronjs.org contextBridge.exposeInMainWorld()](https://www.electronjs.org/ja/docs/latest/api/context-bridge#contextbridgeexposeinmainworldapikey-api)
:::

#### contextBridge②

`additionalAPI`オブジェクトと、
`helloContextBridge`メソッドを追加してみます。
※追加するオブジェクトおよびメソッドの名称は任意。

`preload.js`と`renderer.js`に以下を追記します。

```diff_javascript:preload.js
console.log('Hello World ( preload.js )');

+ const {
+   contextBridge,
+ } = require('electron');
+ 
+ contextBridge.exposeInMainWorld('additionalAPI', {
+   helloContextBridge: () => {
+     console.log('Hello Context Bridge');
+   },
+ });
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// 中略
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
+ // preload.jsで設定した関数の使用実験
+ window.additionalAPI.helloContextBridge();
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  shell,
} = require('electron');

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // インターバルのタイマーの繰り返し動作の取り消し
  clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
      console.log(thisSec);
    }
    prevSec = thisSec;
  }, 33);
});
```

```diff_javascript:preload.js
console.log('Hello World ( preload.js )');

const {
  contextBridge,
} = require('electron');

contextBridge.exposeInMainWorld('additionalAPI', {
  helloContextBridge: () => {
    console.log('Hello Context Bridge');
  },
});
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
// 61枚のPNG画像のDataURLの保持用の配列の初期化
const pngDataURL = [];
// 61枚分処理
[...Array(61).keys()].forEach((i) => {
  // キャンバスのコンテキストの全領域を透明色で消去
  ctx.clearRect(0, 0, 256, 256);
  // キャンバスのコンテキストに数値を二桁で描画
  ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
  // toDataURLでPNG画像のDataURLを保持用の配列に格納
  // toDataURLは引数省略の場合はimage/png
  pngDataURL.push(canvas.toDataURL());
});
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
```

</details>

実行してみます。
win11sec> `npx electron .`

デベロッパーツール、
Console に「Hello Context Bridge」と表示されます。

デベロッパーツール、
Sources の Watch で window と入力。
window に additionalAPI が、
追加されているのが見えます。

```console:DevTools > Sources > Watch
▼ window: Window
 ? additionalAPI: {helloContextBridge: ?}
 ? alert: ? alert()
 //以下略
```

デベロッパーツール、
Sources の Watch で window.additionalAPI と入力。
window.additionalAPI に helloContextBridge が、
追加されているのが見えます。

```console:DevTools > Sources > Watch
▼ window.additionalAPI: Object
 ? helloContextBridge: ? ()
 ? [[Prototype]]: Object
```

## プロセス間通信(IPC通信)

:::note warn
注意
レンダラープロセス⇒メインプロセスのプロセス間通信の話になります。
レンダラープロセス⇔メインプロセスのプロセス間通信や、
メインプロセス⇒レンダラープロセスのプロセス間通信は、
Windows11秒（win11sec.exe）では必要ないので取り扱いません。
:::

`main.js`と`preload.js`と`renderer.js`に以下を追記します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
+ ipcMain,
  shell,
} = require('electron');

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
});

+ // renderer.jsからメッセージの文字列を受信
+ ipcMain.on('send_msg_data_render_to_main', (event, data) => {
+   console.log(data);
+ });
```

```diff_javascript:preload.js
console.log('Hello World ( preload.js )');

const {
  contextBridge,
+ ipcRenderer,
} = require('electron');

contextBridge.exposeInMainWorld('additionalAPI', {
  helloContextBridge: () => {
    console.log('Hello Context Bridge');
  },
+ sendMsgData: (data) => {
+   ipcRenderer.send('send_msg_data_render_to_main', data);
+ },
});
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// 中略
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
+ // preload.jsで設定した関数を使用して、
+ // main.jsへとメッセージの文字列を送信
+ window.additionalAPI.sendMsgData('Hello InterProcess Communication');
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  ipcMain,
  shell,
} = require('electron');

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // インターバルのタイマーの繰り返し動作の取り消し
  clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
      console.log(thisSec);
    }
    prevSec = thisSec;
  }, 33);
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  console.log(data);
});
```

```diff_javascript:preload.js
console.log('Hello World ( preload.js )');

const {
  contextBridge,
  ipcRenderer,
} = require('electron');

contextBridge.exposeInMainWorld('additionalAPI', {
  helloContextBridge: () => {
    console.log('Hello Context Bridge');
  },
  sendMsgData: (data) => {
    ipcRenderer.send('send_msg_data_render_to_main', data);
  },
});
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
// 61枚のPNG画像のDataURLの保持用の配列の初期化
const pngDataURL = [];
// 61枚分処理
[...Array(61).keys()].forEach((i) => {
  // キャンバスのコンテキストの全領域を透明色で消去
  ctx.clearRect(0, 0, 256, 256);
  // キャンバスのコンテキストに数値を二桁で描画
  ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
  // toDataURLでPNG画像のDataURLを保持用の配列に格納
  // toDataURLは引数省略の場合はimage/png
  pngDataURL.push(canvas.toDataURL());
});
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
// preload.jsで設定した関数を使用して、
// main.jsへとメッセージの文字列を送信
window.additionalAPI.sendMsgData('Hello InterProcess Communication');
```

</details>

実行してみます。
win11sec> `npx electron .`

コマンドプロンプトに「Hello InterProcess Communication」と表示されます。

#### 概要説明

`ipcRenderer.send('識別子', data)`で送信したものを、
`ipcMain.on('識別子', function(event, data){})`で待ち受けして、
受信が発生した際は`function(event, data){}`で処理しています。
※electron.orgのドキュメントと表現を若干変えています。

:::note info
参考
[electronjs.org ipcMain](https://www.electronjs.org/ja/docs/latest/api/ipc-main)
[electronjs.org ipcRenderer](https://www.electronjs.org/ja/docs/latest/api/ipc-renderer)
[electronjs.org ipcMain.on(channel, listener)](https://www.electronjs.org/ja/docs/latest/api/ipc-main#ipcmainonchannel-listener)
[electronjs.org ipcRenderer.send(channel, ...args)](https://www.electronjs.org/ja/docs/latest/api/ipc-renderer#ipcrenderersendchannel-args)
[electronjs.org IpcMainEvent Object](https://www.electronjs.org/ja/docs/latest/api/structures/ipc-main-event)
:::

#### addEventListener()

```html:addEventListener.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p>addEventListener</p>
<script src="addEventListener.js"></script>
</body>
</html>
```

```javascript:addEventListener.js
document.querySelector('p').addEventListener('click', (e) => {
  console.log(`Hello Click => x:${e.x} y:${e.y}`);
  console.log(e.type);
  console.log(e.target);
});
window.addEventListener('message', (e) => {
  console.log(e.data);
  console.log(e.type);
  console.log(e.target);
});
window.postMessage('Hello Post Message', '*');
```

唐突ですが、何の変哲もない、
通常のJavaScriptでのイベント処理です。

システム(ブラウザ)からの'識別子'のイベントの配信を、
`hoge.addEventListener('識別子', (e) => {})`で待ち受けして、
受信が発生した際は`(e) => {}`で処理しています。

受信が発生した際は`(e) => {}`の引数にて、
[イベントオブジェクト](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks/Events#event_objects)`e`を受け取ります。
[イベントオブジェクト](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks/Events#event_objects)`e`には、
イベント共通の情報、および、
マウスイベントであれば座標情報など、
メッセージイベントであればメッセージデータなど、
が格納されており、
それを使用して処理をしているわけですね。

:::note info
参考
[developer.mozilla.org Event Objects](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks/Events#event_objects)
[developer.mozilla.org EventTarget.addEventListener()](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)
[developer.mozilla.org EventTarget.dispatchEvent()](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/dispatchEvent)
:::

:::note info
参考
[developer.mozilla.org Element Mouse Events](https://developer.mozilla.org/ja/docs/Web/API/Element#%E3%83%9E%E3%82%A6%E3%82%B9%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88)
:::

:::note info
参考
[developer.mozilla.org Window Messaging Events](https://developer.mozilla.org/ja/docs/Web/API/Window#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88)
[developer.mozilla.org Window postMessage](https://developer.mozilla.org/ja/docs/Web/API/Window/postMessage)
[developer.mozilla.org Window postMessage dispatched event](https://developer.mozilla.org/ja/docs/Web/API/Window/postMessage#%E9%85%8D%E4%BF%A1%E3%81%95%E3%82%8C%E3%82%8B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88)
:::

:::note info
参考
[developer.mozilla.org Event](https://developer.mozilla.org/ja/docs/Web/API/Event)
[developer.mozilla.org Event Properties](https://developer.mozilla.org/ja/docs/Web/API/Event#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)
[developer.mozilla.org MouseEvent](https://developer.mozilla.org/ja/docs/Web/API/MouseEvent)
[developer.mozilla.org MouseEvent Properties](https://developer.mozilla.org/ja/docs/Web/API/MouseEvent#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)
[developer.mozilla.org MessageEvent](https://developer.mozilla.org/ja/docs/Web/API/MessageEvent)
[developer.mozilla.org MessageEvent Properties](https://developer.mozilla.org/ja/docs/Web/API/MessageEvent#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)
:::

#### ipcMain.on()

`ipcMain.on('識別子', (event, data) => {})`も似たような関数です。
`ipcMain.on('識別子', (event, data) => {})`で'識別子'を待ち受けして、
受信が発生した際は`(event, data) => {}`で処理しています。

`window.addEventListener('message', (e) => {})`では、
[イベントオブジェクト](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks/Events#event_objects)`e`の中に`e.data`がありますが、

`ipcMain.on('識別子', (event, data) => {})`では、
`(event, data) => {}`の第二引数にdataがあります。

第一引数の[IpcMainEventObject](https://www.electronjs.org/ja/docs/latest/api/structures/ipc-main-event)`event`は、
Windows11秒（win11sec.exe）では特には必要ないので未使用です。

#### ipcRenderer.send()

`ipcRenderer.send('識別子', data)`は見たままです。
第一引数の'識別子'に向けて、
第二引数のdataを送信します。

#### Hello InterProcess Communication

`'Hello InterProcess Communication'`
という文字列を上記では送信しています。

他の言語出身からすると、
文字列を指定して大丈夫なのかしら?
と思ったのですが大丈夫なようです。

JavaScriptでは文字列がプリミティブなので、
で、自分としては決着しましたが、
それで正しいのかは不明です...

:::note info
参考
[developer.mozilla.org プリミティブ](https://developer.mozilla.org/ja/docs/Glossary/Primitive)
[developer.mozilla.org JavaScript のデータ型とデータ構造](https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures)
:::

## プロセス間通信(IPC通信)まとめ

プリロードスクリプト、および、contextBridgeは、
プロセス間通信(IPC通信)に関してセットの内容です。
Electronの過去の経緯を含めての詳細は、
下記などを別途参照くださいませ。

:::note info
参考
[ElectronでcontextBridgeによる安全なIPC通信](https://qiita.com/pochman/items/64b34e9827866664d436)
[ElectronでcontextBridgeによる安全なIPC通信（受信編）](https://qiita.com/pochman/items/62de713a014dcacbad68)
[Electron（v.15.0.0 現在）の IPC 通信入門 - よりセキュアな方法への変遷](https://qiita.com/hibara/items/c59fb6924610fc22a9db)
:::

## 秒の画像のプロセス間通信(IPC通信)

#### 秒の画像のプロセス間通信(IPC通信)①

00の画像を1枚`renderer.js`から`main.js`に送信してみます。

`main.js`と`preload.js`と`renderer.js`に以下を追記します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  console.log(data);
});

+ // renderer.jsから00番のPNG画像のDataURLを受信
+ ipcMain.on('send_png_data_render_to_main', (event, data) => {
+   console.log(data);
+ });
```

```diff_javascript:preload.js
console.log('Hello World ( preload.js )');

const {
  contextBridge,
  ipcRenderer,
} = require('electron');

contextBridge.exposeInMainWorld('additionalAPI', {
  helloContextBridge: () => {
    console.log('Hello Context Bridge');
  },
  sendMsgData: (data) => {
    ipcRenderer.send('send_msg_data_render_to_main', data);
  },
+ sendPngDataURL: (data) => {
+   ipcRenderer.send('send_png_data_render_to_main', data);
+ },
});
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// 中段
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
// preload.jsで設定した関数を使用して、
// main.jsへとメッセージの文字列を送信
window.additionalAPI.sendMsgData('Hello InterProcess Communication');
+ // preload.jsで設定した関数を使用して、
+ // main.jsへと00番のPNG画像のDataURLを送信
+ window.additionalAPI.sendPngDataURL(pngDataURL[0]);
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  ipcMain,
  shell,
} = require('electron');

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // インターバルのタイマーの繰り返し動作の取り消し
  clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
      console.log(thisSec);
    }
    prevSec = thisSec;
  }, 33);
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  console.log(data);
});

// renderer.jsから00番のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
  console.log(data);
});
```

```diff_javascript:preload.js
console.log('Hello World ( preload.js )');

const {
  contextBridge,
  ipcRenderer,
} = require('electron');

contextBridge.exposeInMainWorld('additionalAPI', {
  helloContextBridge: () => {
    console.log('Hello Context Bridge');
  },
  sendMsgData: (data) => {
    ipcRenderer.send('send_msg_data_render_to_main', data);
  },
  sendPngDataURL: (data) => {
    ipcRenderer.send('send_png_data_render_to_main', data);
  },
});
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
// 61枚のPNG画像のDataURLの保持用の配列の初期化
const pngDataURL = [];
// 61枚分処理
[...Array(61).keys()].forEach((i) => {
  // キャンバスのコンテキストの全領域を透明色で消去
  ctx.clearRect(0, 0, 256, 256);
  // キャンバスのコンテキストに数値を二桁で描画
  ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
  // toDataURLでPNG画像のDataURLを保持用の配列に格納
  // toDataURLは引数省略の場合はimage/png
  pngDataURL.push(canvas.toDataURL());
});
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
// preload.jsで設定した関数を使用して、
// main.jsへとメッセージの文字列を送信
window.additionalAPI.sendMsgData('Hello InterProcess Communication');
// preload.jsで設定した関数を使用して、
// main.jsへと00番のPNG画像のDataURLを送信
window.additionalAPI.sendPngDataURL(pngDataURL[0]);
```

</details>

実行してみます。
win11sec> `npx electron .`

コマンドプロンプトに文字がズラズラ出力されます。
ズラズラ出力された文字の先頭が、
"data:image/png;base64,なんたらかんたら"
となっているのが見て取れます。
"data:image/png;base64,なんたらかんたら"
なのでpng画像のデータになのでしょう、たぶん...

#### 秒の画像のプロセス間通信(IPC通信)②-1

00～60の画像を61枚`renderer.js`から`main.js`に送信してみます。

`main.js`と`renderer.js`を以下に変更します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  console.log(data);
});

- // renderer.jsから00番のPNG画像のDataURLを受信
+ // renderer.jsから61枚のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
  console.log(data);
});
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// 中略
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
// preload.jsで設定した関数を使用して、
// main.jsへとメッセージの文字列を送信
window.additionalAPI.sendMsgData('Hello InterProcess Communication');
- // preload.jsで設定した関数を使用して、
- // main.jsへと00番のPNG画像のDataURLを送信
- window.additionalAPI.sendPngDataURL(pngDataURL[0]);
+ // preload.jsで設定した関数を使用して、
+ // main.jsへと61枚のPNG画像のDataURLを送信
+ window.additionalAPI.sendPngDataURL(pngDataURL);
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
ctx.fillText('00', 128, 222);
// 61枚のPNG画像のDataURLの保持用の配列の初期化
const pngDataURL = [];
// 61枚分処理
[...Array(61).keys()].forEach((i) => {
  // キャンバスのコンテキストの全領域を透明色で消去
  ctx.clearRect(0, 0, 256, 256);
  // キャンバスのコンテキストに数値を二桁で描画
  ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
  // toDataURLでPNG画像のDataURLを保持用の配列に格納
  // toDataURLは引数省略の場合はimage/png
  pngDataURL.push(canvas.toDataURL());
});
// htmlにcanvas要素を追加して表示
document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
// preload.jsで設定した関数を使用して、
// main.jsへとメッセージの文字列を送信
window.additionalAPI.sendMsgData('Hello InterProcess Communication');
// preload.jsで設定した関数を使用して、
// main.jsへと61枚のPNG画像のDataURLを送信
window.additionalAPI.sendPngDataURL(pngDataURL);
```

</details>

実行してみます。
win11sec> `npx electron .`

コマンドプロンプトに文字がズラズラ出力されますが、
console.log()で確認する量ではないですね。。。
まあ、それはさておき、

`window.additionalAPI.sendPngDataURL(pngDataURL[0]);`を、
`window.additionalAPI.sendPngDataURL(pngDataURL);`にして大丈夫なのでしょうか。

[ipcMain.on()](https://www.electronjs.org/ja/docs/latest/api/ipc-main#ipcmainonchannel-listener)
[ipcRenderer.send()](https://www.electronjs.org/ja/docs/latest/api/ipc-renderer#ipcrenderersendchannel-args)
ドキュメントは各々以下となっています。

> ipcMain.on(channel, listener)
> &nbsp;&nbsp;&nbsp;&nbsp;channel string
> &nbsp;&nbsp;&nbsp;&nbsp;listener Function
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;event IpcMainEvent
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...args any[]

> ipcRenderer.send(channel, ...args)
> &nbsp;&nbsp;&nbsp;&nbsp;channel string
> &nbsp;&nbsp;&nbsp;&nbsp;...args any[]

該当箇所の引数は`...args any[]`となっています。

[スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)でしょうか?
[残余引数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters)です。
[残余引数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters)の説明の中ほどに

> 残余引数は`Array`インスタンスです。

と書かれています。そのため、
`window.additionalAPI.sendPngDataURL(pngDataURL);`で大丈夫なのでしょうか。

:::note info
参考
[developer.mozilla.org スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
[developer.mozilla.org 残余引数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters)
[developer.mozilla.org 分割代入](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
:::

#### 秒の画像のプロセス間通信(IPC通信)②-2

コマンドプロンプトに出力する文字量でないのでコメントアウトしておきます。
もう不要でしょうからついでに秒の数値の出力もコメントアウトしておきます。
代わりに(?)、debugger; を追記しておきます。

`main.js`を以下に変更します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
-     console.log(thisSec);
+     // console.log(thisSec);
    }
    prevSec = thisSec;
  }, 33);
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
- console.log(data);
+ // console.log(data);
});

// renderer.jsから61枚のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
- console.log(data);
+ // console.log(data);
+ debugger;
});
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  ipcMain,
  shell,
} = require('electron');

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // インターバルのタイマーの繰り返し動作の取り消し
  clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
      // console.log(thisSec);
    }
    prevSec = thisSec;
  }, 33);
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  // console.log(data);
});

// renderer.jsから61枚のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
  // console.log(data);
  debugger;
});
```

</details>

実行してみます。
ただしいつもとは違う以下コマンドを実行してください。
win11sec> `npx electron --inspect-brk .`

コマンドプロンプトにメッセージが何か出力されるだけで、
特に何も動きませんが正しい動作です。

ブラウザのChromeを起動して[chrome://inspect](chrome://inspect)を開きます。
しばらく待っていると Remote Target に項目が追加され、
inspectがクリックできるようになりますのでクリックします。
ブラウザのChromeのDevToolsが起動します。
Sources を開き、また、しばらく待ちます。

"どっか知らないところの" 最初の行でスクリプトが中断しますので F8 を押します。
すると、上記で追記した`debugger;`のところで止まるので、
Sources の Watch に data と入力します。

`main.js`に送信されてきた`data`の中身が確認できます。

"data:image/png;base64,なんたらかんたら"
が61個あるので無事送信されているのでしょう。

:::note info
参考
[ChromeDevToolを使ってNodeJSのデバッグ](https://qiita.com/nju33/items/9c5fd326bd7a58886af8)
[electronjs.org メインプロセスのデバッグ](https://www.electronjs.org/ja/docs/latest/tutorial/debugging-main-process)
[nodejs.org デバッグガイド](https://nodejs.org/ja/docs/guides/debugging-getting-started/)
:::

:::note info
参考
[JavaScriptのデバッグにはdebugger文を使おう](https://qiita.com/a_ya_ka/items/b5160cd7d592931d7507)
[How to debug node js app with breakpoints and everything?](https://stackoverflow.com/questions/11611162)
[Debugger isn't stopped at the debugger; statement when debugging using --inspect](https://github.com/nodejs/node/issues/10457)
:::

#### 秒の画像のプロセス間通信(IPC通信)②-3

確認できたので、debugger; をコメントアウトしておきます。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  // console.log(data);
});

// renderer.jsから61枚のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
  // console.log(data);
- debugger;
+ // debugger;
});
```

## タスクトレイのアイコンに秒を描画

ではタスクトレイのアイコンに秒を描画します。

`main.js`を以下に変更します。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  ipcMain,
  shell,
} = require('electron');

+ const pngNativeImage = [];

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
- // 1/30秒に1回インターバルで現在時刻の秒を確認
- interval = setInterval(() => {
-   const now = new Date();
-   thisSec = now.getSeconds();
-   if (thisSec !== prevSec) {
-     // console.log(thisSec);
-   }
-   prevSec = thisSec;
- }, 33);
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  // console.log(data);
});

// renderer.jsから61枚のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
  // console.log(data);
  // debugger;
+ // 61枚分処理
+ data.forEach((element, index, array) => {
+   // PNG画像のDataURLからnativeImageに生成
+   pngNativeImage[index] = nativeImage.createFromDataURL(array[index]);
+ });
+ // 1/30秒に1回インターバルで現在時刻の秒を確認
+ interval = setInterval(() => {
+   const now = new Date();
+   thisSec = now.getSeconds();
+   if (thisSec !== prevSec) {
+     // console.log(thisSec);
+     // タスクトレイアイコンに現在時刻の秒の画像を設定
+     tray.setImage(pngNativeImage[now.getSeconds()]);
+   }
+   prevSec = thisSec;
+ }, 33);
});
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  ipcMain,
  shell,
} = require('electron');

const pngNativeImage = [];

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // インターバルのタイマーの繰り返し動作の取り消し
  clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromPath('clock.png'));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  // console.log(data);
});

// renderer.jsから61枚のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
  // console.log(data);
  // debugger;
  // 61枚分処理
  data.forEach((element, index, array) => {
    // PNG画像のDataURLからnativeImageに生成
    pngNativeImage[index] = nativeImage.createFromDataURL(array[index]);
  });
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
      // console.log(thisSec);
      // タスクトレイアイコンに現在時刻の秒の画像を設定
      tray.setImage(pngNativeImage[now.getSeconds()]);
    }
    prevSec = thisSec;
  }, 33);
});
```

</details>

実行してみます。
win11sec> `npx electron .`

タスクトレイに秒が描画されました。

:::note info
参考
[Customize the taskbar notification area](https://support.microsoft.com/en-us/windows/e159e8d2-9ac5-b2bd-61c5-bb63c1d437c3#WindowsVersion=Windows_11)
[タスクバーの通知領域をカスタマイズする](https://support.microsoft.com/ja-jp/windows/e159e8d2-9ac5-b2bd-61c5-bb63c1d437c3#WindowsVersion=Windows_11)
[windows11タスクバーコーナーオーバーフローアイコン表示](https://www.google.com/search?q=windows11%E3%82%BF%E3%82%B9%E3%82%AF%E3%83%90%E3%83%BC%E3%82%B3%E3%83%BC%E3%83%8A%E3%83%BC%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E8%A1%A8%E7%A4%BA)
:::

## メインウインドウの非表示

メインウインドウを非表示にしつつ、
メインウインドウの描画をコメントアウトしておきます。
メインのアイコンの設定もコメントアウトしておきます。
タスクトレイのアイコンは無色透明画像にしておきます。
ハローワールドは出力抑制せずそのままにしてあります。
これで完成です。

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// 中略

// Electronの初期化時に実行
app.whenReady().then(() => {
  // 中略
  // タスクトレイのアイコンの作成
- tray = new Tray(nativeImage.createFromPath('clock.png'));
+ tray = new Tray(nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQI12NgYAAAAAMAASDVlMcAAAAASUVORK5CYII='));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
+   // メインウインドウ生成時のウインドウの表示非表示
+   show: false,
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
- mainWindow.setIcon('clock.png');
+ // mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
-  mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
+ // mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

// 以下略
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
- ctx.fillText('00', 128, 222);
+ // ctx.fillText('00', 128, 222);
// 61枚のPNG画像のDataURLの保持用の配列の初期化
const pngDataURL = [];
// 61枚分処理
[...Array(61).keys()].forEach((i) => {
  // キャンバスのコンテキストの全領域を透明色で消去
  ctx.clearRect(0, 0, 256, 256);
  // キャンバスのコンテキストに数値を二桁で描画
  ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
  // toDataURLでPNG画像のDataURLを保持用の配列に格納
  // toDataURLは引数省略の場合はimage/png
  pngDataURL.push(canvas.toDataURL());
});
// htmlにcanvas要素を追加して表示
- document.body.append(canvas);
+ // document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
// preload.jsで設定した関数を使用して、
// main.jsへとメッセージの文字列を送信
window.additionalAPI.sendMsgData('Hello InterProcess Communication');
// preload.jsで設定した関数を使用して、
// main.jsへと61枚のPNG画像のDataURLを送信
window.additionalAPI.sendPngDataURL(pngDataURL);
```

<details>
<summary>:notebook_with_decorative_cover:</summary>

```diff_javascript:main.js
console.log('Hello World ( main.js )');

// Electronのモジュールの読み込み
const {
  app,
  BrowserWindow,
  nativeImage,
  Menu,
  Tray,
  ipcMain,
  shell,
} = require('electron');

const pngNativeImage = [];

let interval = null;
let prevSec = null;
let thisSec = null;

let mainWindow = null;
let tray = null;

// 全てのウィンドウが閉じた時に実行
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// Electronの終了時に実行
app.on('quit', () => {
  // インターバルのタイマーの繰り返し動作の取り消し
  clearInterval(interval);
  // タスクトレイのアイコンの削除
  tray.destroy();
});

// Electronの初期化時に実行
app.whenReady().then(() => {
  // メニューの構築
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Settings',
      click: () => {
        shell.openExternal('ms-settings:taskbar');
      },
    },
    {
      label: 'Quit',
      click: () => {
        app.quit();
      },
    },
  ]);
  // タスクトレイのアイコンの作成
  tray = new Tray(nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQI12NgYAAAAAMAASDVlMcAAAAASUVORK5CYII='));
  // タスクトレイのアイコンのホバーテキストの設定
  tray.setToolTip('windows11秒');
  // タスクトレイのアイコンのコンテキストメニューの設定
  tray.setContextMenu(contextMenu);
  // メインウインドウ関連の初期設定
  mainWindow = new BrowserWindow({
    // メインウインドウ生成時のウインドウの表示非表示
    show: false,
    // メインウインドウ生成時のウインドウの横幅
    width: 1600,
    // メインウインドウ生成時のウインドウの高さ
    height: 900,
    // 上記の横幅と高さがビューポートとしての値としてメインウインドウ生成
    useContentSize: true,
    // ウェブページの機能の設定
    webPreferences: {
      // 他のスクリプトがページで実行される前にロードされるスクリプトを指定
      preload: `${app.getAppPath()}/preload.js`,
    },
  });
  // メインウインドウのアイコンの設定
  // mainWindow.setIcon('clock.png');
  // タスクバーのアイコンにオーバーレイされる画像の設定
  // mainWindow.setOverlayIcon(nativeImage.createFromPath('bell.png'), '');
  // メインウインドウに表示するページの読み込み
  mainWindow.loadFile('index.html');
  // ブラウザのF12のデベロッパーツールの表示
  mainWindow.webContents.openDevTools();
  // ウィンドウが閉じた時に実行
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

// renderer.jsからメッセージの文字列を受信
ipcMain.on('send_msg_data_render_to_main', (event, data) => {
  // console.log(data);
});

// renderer.jsから61枚のPNG画像のDataURLを受信
ipcMain.on('send_png_data_render_to_main', (event, data) => {
  // console.log(data);
  // debugger;
  // 61枚分処理
  data.forEach((element, index, array) => {
    // PNG画像のDataURLからnativeImageに生成
    pngNativeImage[index] = nativeImage.createFromDataURL(array[index]);
  });
  // 1/30秒に1回インターバルで現在時刻の秒を確認
  interval = setInterval(() => {
    const now = new Date();
    thisSec = now.getSeconds();
    if (thisSec !== prevSec) {
      // console.log(thisSec);
      // タスクトレイアイコンに現在時刻の秒の画像を設定
      tray.setImage(pngNativeImage[now.getSeconds()]);
    }
    prevSec = thisSec;
  }, 33);
});
```

```diff_javascript:preload.js
console.log('Hello World ( preload.js )');

const {
  contextBridge,
  ipcRenderer,
} = require('electron');

contextBridge.exposeInMainWorld('additionalAPI', {
  helloContextBridge: () => {
    console.log('Hello Context Bridge');
  },
  sendMsgData: (data) => {
    ipcRenderer.send('send_msg_data_render_to_main', data);
  },
  sendPngDataURL: (data) => {
    ipcRenderer.send('send_png_data_render_to_main', data);
  },
});
```

```diff_javascript:renderer.js
console.log('Hello World ( renderer.js )');
// キャンバスの生成
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
// キャンバスのコンテキストの取得
const ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.font = '200px monospace';
// ctx.fillText('00', 128, 222);
// 61枚のPNG画像のDataURLの保持用の配列の初期化
const pngDataURL = [];
// 61枚分処理
[...Array(61).keys()].forEach((i) => {
  // キャンバスのコンテキストの全領域を透明色で消去
  ctx.clearRect(0, 0, 256, 256);
  // キャンバスのコンテキストに数値を二桁で描画
  ctx.fillText(i.toString().padStart(2, '0'), 128, 222);
  // toDataURLでPNG画像のDataURLを保持用の配列に格納
  // toDataURLは引数省略の場合はimage/png
  pngDataURL.push(canvas.toDataURL());
});
// htmlにcanvas要素を追加して表示
// document.body.append(canvas);
// preload.jsで設定した関数の使用実験
window.additionalAPI.helloContextBridge();
// preload.jsで設定した関数を使用して、
// main.jsへとメッセージの文字列を送信
window.additionalAPI.sendMsgData('Hello InterProcess Communication');
// preload.jsで設定した関数を使用して、
// main.jsへと61枚のPNG画像のDataURLを送信
window.additionalAPI.sendPngDataURL(pngDataURL);
```

</details>

## 実行ファイルの作成

electron-builderを使用して実行ファイル(exe)を作成します。
以下実行します。

win11sec> `npx electron-builder --win --x64 -c.win.target=portable -c.win.icon=clock.png -c.artifactName=${productName}.${ext}`

ビルドに数分掛かりますが完了すると、
distフォルダにexeが出来上がります。

:::note info
参考
[CLIでelectron-builder](https://feo52.hatenablog.jp/entry/2023/01/01/080000)
:::

## おまけ(ESLint)

JavaScriptを書くのであれば、ESLintを使用すると、
コードがコーディング規約に準拠をしているか、および、
コードのインデントや字下げスタイルを確認してくれるので、
文法不備などの記述不備によるエラーを事前確認出来るので有難いですね。
手軽に試すのであれば[こちら](https://qiita.com/feo52/items/bfb802c4753431646c3b)。

:::note info
参考
[JavaScriptをeslintrc使用せずESLintしてみた話](https://qiita.com/feo52/items/bfb802c4753431646c3b)
:::

:::note info
参考
JavaScriptと互換性の高い
[TypeScriptとエコシステム](https://typescriptbook.jp/overview/ecosystem)の情報
[TypeScript Frontend Ecosystem Landscape](https://typescriptbook.jp/assets/files/typescript-ecosystem-map-01fad23076509d150b7395ec107dc4d4.svg)
:::

## あとがき

ノンプログラマーの素人が記述をしたコードです。
狭い利用範囲と少ない利用頻度での確認ですので、
記載内容に間違いや勘違いがあるかもしれません。
上記内容を参照の際は自己責任でお願い致します。

## 参考

https://www.electronjs.org/ja/docs/latest/
https://www.electronjs.org/ja/docs/latest/tutorial/quick-start

https://www.electronjs.org/ja/docs/latest/tutorial/process-model
https://www.electronjs.org/ja/docs/latest/tutorial/context-isolation
https://www.electronjs.org/ja/docs/latest/tutorial/ipc
https://www.electronjs.org/ja/docs/latest/tutorial/examples
https://www.electronjs.org/ja/docs/latest/tutorial/debugging-main-process

https://www.electronjs.org/ja/docs/latest/breaking-changes
https://www.electronjs.org/ja/docs/latest/tutorial/electron-timelines
https://www.electronjs.org/ja/docs/latest/glossary

https://www.electronjs.org/ja/docs/latest/api/app
https://www.electronjs.org/ja/docs/latest/api/browser-window
https://www.electronjs.org/ja/docs/latest/api/ipc-main
https://www.electronjs.org/ja/docs/latest/api/menu
https://www.electronjs.org/ja/docs/latest/api/native-image
https://www.electronjs.org/ja/docs/latest/api/shell
https://www.electronjs.org/ja/docs/latest/api/tray

https://www.electronjs.org/ja/docs/latest/api/context-bridge
https://www.electronjs.org/ja/docs/latest/api/ipc-renderer