サーバレス練習帳

着眼大局着手小局

Electronでデスクトップウィジェットを作ってみる?

では、次の記事を参考にデスクトップウィジェットを作ってみます。
qiita.com
私はWindows1064で開発しております。

【1】いつも通り準備から

C:\node>mkdir elewidget
C:\node>cd elewidget
C:\node\elewidget>npm init -y
C:\node\elewidget>npm i -D electron --proxy http://proxy01.xxxx:18080
C:\node\elewidget>npm i -D  electron-packager --proxy http://proxy01.xxxx:18080
C:\node\elewidget>npx electron-packager src hc200515 --platform=win32 --arch=x64 --overwrite --icon=src/icon.ico --proxy http://proxy01.xxxx:18080

まずは、ここまでササーッとやりましょう。
electron画面が出れば成功です。

※上記Qiitaと少し手順が違うかもしれませんが、私が過去に勉強したコチラの記事と同じ手順としています。
serverless.hateblo.jp

【2】Hello Worldやってみましょう!
src内に、3つのファイルを作ります。
package.json

{
  "main": "main.js"
}

index.html

<HTML><H1>HELLO WORLD !</H1></HTML>

main.js

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

var mainWindow = null;

app.on("window-all-closed", function() {
    if (process.platform != "darwin") {
        app.quit();
    }
});

app.on("ready", function() {
    mainWindow = new BrowserWindow({
        // ウィンドウ作成時のオプション
    });

    // index.html を開く
    mainWindow.loadURL("file://" + __dirname + "/index.html");

    mainWindow.on("closed", function() {
        mainWindow = null;
    });
});

※前述のQiitaからloadURLの書き方とReqquireの書き方を変えています。
※ちなみに、requireって、node.jsにおけるimportみたいなものだと思っています。(多分)
※”TypeError:mainWindow.loadurl is not a function”というエラーが出た場合、loadUrlをloadURL(大文字)に変更するとエラーが出なくなりました。(次のリンク参照)
https://sutaba-mac.site/electron-loadurl-name-changed/

さぁ、ここまで書けたら、まずはハローワールドの実行です。

C:\node\elewidget>npx electron src

f:id:urbanplanner:20200503114345p:plain

できましたね★

ちなみにエレクトロンでは、起動中のアプリでCtrl + Shift + iを押すと画面のソースコードが表示されます。

【2】時計を作ろう!
但し、これは前述Qiitaそのままのプログラムでやってみたいと思います。
index.htmlの中身の書き換えと、新たにclock.jsを作る感じです。

本題と異なるが、次のような書き方をして時計アプリって実装するのだなと思って、ちょっと感銘を受けていました。

    // 次の「0ミリ秒」に実行されるよう、次の描画処理を予約
    var delay = 1000 - new Date().getMilliseconds();

【3】透過ウインドウ対応
CSSを書きます。(後述)

【4】タスクトレイ常駐
さて、ここまでのスクリプトを記述します。
package.json
⇒ 【2】の通り

index.html

<meta charset="utf-8">
<link href="./clock.css" rel="stylesheet">
<title>clock</title>

<div id="digital_clock"><!-- ここに時刻が入る --></div>

<script src="./clock.js"></script>

main.js

const { app, Menu, BrowserWindow,Tray,nativeImage} = require('electron');

var mainWindow = null;

app.on("window-all-closed", function() {
    if (process.platform != "darwin") {
        app.quit();
    }
});

app.on("ready", function() {
    mainWindow = new BrowserWindow({
        // ウィンドウ作成時のオプション
        "width": 180,
        "height": 70,
        "webPreferences": {"nodeIntegration": true},
        "transparent": true,    // ウィンドウの背景を透過
        "frame": false,     // 枠の無いウィンドウ
        "resizable": false,  // ウィンドウのリサイズを禁止 // アプリ起動時にウィンドウを表
        "show": false,          // アプリ起動時にウィンドウを表示しない
        "skipTaskbar": true,    // タスクバーに表示しない
        "fullscreen": false,
    });
    
    // index.html を開く
    mainWindow.loadURL("file://" + __dirname + "/index.html");

    mainWindow.on("close", function(event) {
        event.preventDefault();
        mainWindow.hide();
    });

    mainWindow.on("closed", function() {
        mainWindow = null;
    });

    // タスクトレイに格納
    var trayIcon = new Tray(nativeImage.createFromPath(__dirname + "/icon.ico"));

    // タスクトレイに右クリックメニューを追加
    var contextMenu = Menu.buildFromTemplate([
        { label: "表示", click: function () { mainWindow.show(); } },
        { label: "終了", click: function () { mainWindow.close(); } }
    ]);
    trayIcon.setContextMenu(contextMenu);

    // タスクトレイのツールチップをアプリ名に
    trayIcon.setToolTip("Clock!");

    // タスクトレイが左クリックされた場合、アプリのウィンドウをアクティブに
    trayIcon.on("clicked", function () {
        mainWindow.focus();
    });
});

※参考にしたQiitaではskip-taskbarとなっていたがそれでは私の環境では期待通りに動かなかった。skipTaskbarとしたら期待通りにタスクバーからアイコンが消えた。


clock.js

var remote = require('electron').remote
// ウィンドウを開く
openWindow();

// 時計の描画処理をスタート
clock();

function openWindow () {
    // ウィンドウのオブジェクトを取得
    var win = remote.getCurrentWindow();

    // ウィンドウ位置を復元
    if (localStorage.getItem("windowPosition")) {
        var pos = JSON.parse(localStorage.getItem("windowPosition"));
        win.setPosition(pos[0], pos[1]);
    }

    // クローズ時にウィンドウ位置を保存
    win.on("close", function() {
        localStorage.setItem("windowPosition", JSON.stringify(win.getPosition()));
    });

    // ウィンドウをhideにしておく
    //win.show();
    win.hide();

}

function clock () {
    // 現在日時を取得
    var d = new Date();

    // デジタル時計を更新
    updateDigitalClock(d);

    // 次の「0ミリ秒」に実行されるよう、次の描画処理を予約
    var delay = 1000 - new Date().getMilliseconds();
    setTimeout(clock, delay);
}

function updateDigitalClock (d) {
    var AA_str = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    var YY = d.getFullYear().toString().slice(-2);
    var MM = d.getMonth() + 1;
    var DD = d.getDate();
    var AA = d.getDay();
    var hh = d.getHours();
    var mm = d.getMinutes();
    var ss = d.getSeconds();

    // 桁あわせ
    if(MM < 10) { MM = "0" + MM; }
    if(DD < 10) { DD = "0" + DD; }
    if(hh < 10) { hh = "0" + hh; }
    if(mm < 10) { mm = "0" + mm; }
    if(ss < 10) { ss = "0" + ss; }

    var text = YY + '/' + MM + '/' + DD + ' (' + AA_str[AA] + ')<br>' + hh + ':' + mm + ':' + ss
    document.getElementById("digital_clock").innerHTML = text;
}

clock.css

@import url(http://fonts.googleapis.com/css?family=Iceland);

body {
    overflow: hidden;
    margin: 0;
    padding: 0;
    border: 5px solid rgb(42, 42, 42);
    background-color: rgba(24, 24, 24, .7);
    box-shadow: 0 0 8px 3px #000 inset;

    -webkit-app-region: drag;
    -webkit-user-select: none;
}

#digital_clock {
    font-family: "Iceland";
    font-size: 25px;
    line-height: 22px;
    margin-top: 9px;
    text-align: center;
    color: #fff;
    text-shadow: 1px 1px 3px #000;
}

Electronでレンダラープロセスrequireができない場合について次のリンクに説明があるので、読んでおきます。
メインプロセスとレンダラープロセスという2つについて理解しておいた方が良さそうです。

メインプロセスであるmain.jsからレンダラープロセスを作る際に「"nodeIntegration": true」を書いておかないと、index.htmlから呼び出されたclock.js内でremoteをrequireできませんでした。ので、「"nodeIntegration": true」を追加しています。

次のリンクによると、古いバージョンのエレクトロンではデフォルトが「"nodeIntegration": true」だったそうですが、新しいバージョンではデフォルトが「"nodeIntegration": false」となっているそうです。これはXSS対策なのでそうです。なるほどね、確かにこのWidgetの記事は2015年のものだった。
https://qiita.com/nomuyoshi/items/9091abd9dc3b05c85f44

メインウインドウの閉じるボタンを押したときに閉じさせずに隠すだけの処理は、こちらを参考にしました。
https://taku-o.hatenablog.jp/entry/2019/02/05/224111


動きました!

【6】exeを作る
さぁ、最後にexeを作ってみましょう!

npx electron-packager src trial0507 --platform=win32 --arch=x64 --overwrite --icon=src/icon.ico