Electronでタスクトレイ常駐のアプリを作る

大企業やその子会社などに入ってしまうと、IT周りは世間の二周半遅れのシステムを使わされるだけでなく、「あれは駄目」「これはするな」という時代遅れの制限が山程あります。結果的にシャドーITを推進して別のセキュリティホールを空けることになるのですが、そこには目が行かないようです。

こういった規制は、あるポイントにだけしか着目しておらず、その規制の結果、送信ミスを招くといったことがあるのもしばしば。そこでフリーソフトが使えないのであれば、作るしかないので、以下のアプリを作ることにしました。今回は、Windows上で開発を行っています。

※今回の一番の肝はxlsxの読み書きとSQLite3のインストールです.

今回作成するアプリケーションの概要

要件事項

  1. タスクトレイに常駐するタイプのアプリケーションを作る
  2. メールアドレス一覧は個人単位でメンテするのではなく、ファイルサーバ上Excelファイルに対して行う
  3. ファイルはファイルサーバ上のXLSXをロードしてリスト化。dialogにてファイルの選択を実装する。
  4. タスクトレイのアイコンを叩くと、小さなウィンドウが表示され、宛先一覧が出てくる
  5. 4.の項目をクリックすると、Outlookが起動して、宛先にはまとめてメアドが挿入される
  6. 送信先一覧を確認できるようにする。
  7. 2.のデータはアプリが持ってるSQLiteのデータベースに格納しそこから取り出す。
  8. 指定時間の間隔で、xlsxファイルから最新データを自動で取得するオートリロード機能をつける。

といった具合。タスクトレイに常駐といっても、メインメニューコンテキストメニューなどは使用せず、バルーンタイプのアプリが表示される形にしたい(設定関係はコンテキストメニューで対応)。今回これをElectronで作ってみることにしました。

今回使用するモジュール等

また、SQLite3はモジュールのビルドが必要であるため、node-gyp が必要です。詳細は、後述とElectronでMySQLへ接続するの「WindowsでKeytarを使う」も参照して、使えるように準備しておいてください。

今回使用するファイル等

今回作成したテストアプリケーション

今回作成したアプリをelectron-packagerにてパッケージにしたものをアップロードしました(Windows10 64bit版)。7-zipで圧縮しています。解凍すると中に「rocketman.exe」があります。これを起動すると

  1. タスクトレイに常駐します。db.sqlite3が無い場合には自動で作成され、テーブルも自動で作成されます。
  2. Ctrl + Mキーいつでもリストを呼び出せるようになります。
  3. レジストリのスタートアップにrocketman.exeが自動登録され、次回起動時には自動的に起動します。
  4. 終了するとCtrl+Mのショートカットキーを解除します。
  5. アップデートで指定のxlsxからメアドリストを取得し、db.sqlite3に格納します。

図:設定画面とトレイのコンテキストメニューの様子

事前準備

追加モジュールのインストール

まずは普通にモジュールをインストールします。一部のモジュールはリビルドが必要なケースがあるので、注意が必要です。プロジェクトフォルダを作り、npm init -ypackage.jsonを作った後に、以下のコマンドでインストールします。

auto-launchモジュールの注意点

このモジュールは、WindowsやmacOS起動時に自動的にアプリを起動するように「レジストリ」や「ログイン項目」に登録してくれる素敵なモジュールです。特に今回のような「トレイ常駐型」アプリの場合、手動でいちいち起動するというのも億劫なので、もってこいなのですが、開発中だけ注意点があります。

開発中にこのモジュールを使ってコードを書き、electron .でデバッグテストをすると、electron本体のexeが自動起動に登録されてしまい、毎回起動時に空のelectronが起動してしまいます。ですので、electron-packagerなどで最終リリース直前にコードを追加して、使うようにすると良いでしょう。

なお、Windowsの場合、レジストリは以下の場所にexe名で登録されます。アプリを手動削除しても、このレジストリのエントリは残りますが、特に問題は起きないので気になるようでしたら、インストーラなどを使って、アンインストール時に該当のエントリーを削除するようにすれば良いです。

図:こんな感じに自動起動が登録される

SQLite3モジュールのインストール

今回はWindows10上で開発を行っていますが、以前のMySQL接続アプリの時と同様に、SQLiteのインストールは面倒な壁があります。以前は、Keytarモジュールのインストールでリビルドする為の環境を構築しました。その後、electron-rebuildを行えば使えましたが、今回のSQLiteはその手順では、Electronでは使えるようになりません。参考になったサイトは以下のサイトです。

また、keytarの事例ですが、electron5.0.0とkeytar4.6.0でのネイティブモジュールビルド環境の整備をまとめましたので、合わせてご覧ください。

electron@5.0.0でkeytar@4.6.0をWindowsで使う2020年版

sqlite3モジュールの際には、以下の問題があります。

  1. keytarモジュールの場合、msvsは2017でnode-gypのビルドを通過する事ができましたが、SQLite3ではmsvsは2015でないと通過ができない。
  2. また、npmで配信されているモジュールではなく、ソースからビルドしなければ動かない。
  3. sqlite3が対応できているのはelectron 3.x系。また、その場合、sqlite3は4.0.2ではビルド通過を確認。
  4. ただし、electron 5.x系以降は、sqlite@5.0.0が正式にElectronサポートをしたので、npm i sqlite@5.0.0にてインストールが可能(ただし、msvsは2015の指定は必要)

ちなみに、前回のMySQL接続アプリ作成時の環境で、コマンドを実行してインストールしようとしたところ、以下のようなエラーが発生した。

このv140というのは、Visual Studio 2015のビルドツールのバージョンで、node-gypのMSVSVersion.pyの228行目付近に書かれているものになります。この値を141に書き換えればビルドできるという情報もありますが、今回は素直に以下のコマンドで、msvs2015を追加インストールする事にしました(Powershellを管理者権限で起動して実行する必要があります)。node-gyppython, electron-rebuildは正しく動作してる環境を前提にしています。

インストール完了まで相当の時間が掛かるので、お茶でも飲みながら待ちましょう。以下のような表示になったら完了です。

続けて、Powershell上で対象のプロジェクトフォルダまで移動し、以下のコマンドを打ってsqlite3をインストールしてみます。targetの3.0.0はelectronのバージョンです。

無事にビルドが完了すると以下のような表示になり、package.jsonに記述が追加されます。これで、sqlite3がelectronで使えるようになりました。

SQLite3ファイルの準備

SQLite3のDBファイルを用意する必要がありますが、今回のような簡易的なアプリであれば、事前に用意せず、Node.jsのプログラム内でファイルを作り、テーブルを作ってしまったほうがてっとり速いです。Windows用にはGUIでSQLiteの管理の出来るアプリケーションがあり、作成したSQLiteのDBファイルの中身を弄る事が可能です。

今回はmaillistテーブルを用意し、ID(Auto Increment)、LISTNAME, USERNAME, ADDRESSの4つのフィールドのみなので、ソースコード内でSQLにて作成しています。一応、前項にて作成した空のファイルをダウンロードできるようにしてあります。他にもグループ名用のgrplistテーブル、NGリスト用のnglistテーブルを用意しています。

図:SQLiteファイルの中身を見てみた

マスターになるExcelファイルの準備について

node.jsのxlsxモジュールは、直接読み書きをする場合、正直言って遅いです。よって、このxlsxファイルはマスターとしてファイルサーバに配置をしておき、その中のデータはSQLite3のファイルの中に格納しておくほうがスピード面では圧倒的に有利です。よって、今回のアプリからは、リストのアップデート時だけxlsxモジュールにて読み書きを実装しています。

今回マスターとなるExcelファイルは非常に単純なもので、シート名(リスト名)とその中にユーザ名とメアド、タイプ(to cc, bccを指定)の列が用意されてるだけのです。リストを増やしたい場合には、既存のシートと同じシートを用意し、シート名を設定します。シート名がリストの名前になるので、注意してください。

※このリストをElectronアプリから設定より指定しておく必要があります。

図:リストを用意して配置しておきましょう

バージョン情報ウィンドウ

今回から、electron-about-windowモジュールを追加し、バージョン情報を出すようにしてみました。詳細はindex.jsのコードを見ていただければわかりますが、こうした小さな心遣いもこの手のアプリケーションにあると無いとでは随分違いますね。このモジュールはpackage.jsonの値も取ってきてくれるので、オプション指定を加えなくてもある程度は、この画面が出来てしまいます。

但し注意点があり、これもまたBrowserWindowなので、このまま閉じるとアプリまで終わってしまいますので、window-all-closedイベントを追加し、何もしない動作を設定しておきました。設定などで「自動起動するかどうか」のチェックボックスに応じて設定などが望ましいかもしれません。

図:バージョンナンバー表記してみた

グローバルショートカットの設定について

今回のアプリケーションはいわば、メルアドランチャーなので、いちいちタスクバーからマウス操作でメニュー出して操作するというのは、面倒です。ということで、electron標準で装備されてるglobalshortcutを利用して、いつでもどこでもCtrl+Mキー(自由に割り当ては可能)でメニューを呼び出し、またメニューウィンドウは、AlwaysOnTopにて最前列表示にしています。

また、クリック後は勝手に消えてくれたほうが良いので、HTML側にフォーカスを失ったらwindow.closeする設定も入れてあります(同時に、ESCを押してもcloseするようにしてあります)。

一方で、グローバルショートカットは、設定したショートカットを起動中はずっとキープしたままになってしまうので、よく使うようなショートカットに割り当てないように注意が必要な点と、アプリケーション終了時には、この設定を解除するようにコードを設定しておいてあげる必要性があります。

NGリストの機能について

今回のアプリでは、取得したメアドグループリストに於いて、表示したくないものはNGリストに入れることが出来るようになっています。そのため、別にNGリストテーブルを用意しており、CREATE VIEWにてクエリを作っています。これはmaillistとnglistのテーブル間で一致するものを除外した「不一致クエリ」で、NGリストテーブルにデータを追加すると、必然的にリストから消えます。

このVIEW(クエリ)は、mailmasterとして作っており、メインメニューから参照されるのはテーブルではなく、このVIEWになります。

図:VIEWはdb.sqlite3作成時に作られます。

多重起動の防止

アプリケーション自体の多重起動防止および、BrowserWindowの多重起動防止も必要になってくるかもしれません。これらは以下のようなコードをindex.jsに加える事で、多重起動防止になります。

アプリの多重起動防止

アプリの多重起動防止は簡単です。以下のコードをindex.jsに追加するだけで実現可能です。

ウィンドウの多重起動防止

こちらのコードは、同じウィンドウが多重で開かないようにするためのもので、例えばセッティングのウィンドウなどが多重で起動するのは好ましくないので、開いてるかチェックし、開いていたらフォーカスをオンにするだけといったシンプルなものです。

ソースコード

メインプロセス側(index.js)

今回のプログラムのバックエンド部分を担当する心臓部です。設定の保存や呼び出し、xlsxファイルからのデータ取得、SQLite3ファイルへのデータの読み書き、オートリロード用のCronJob機能、トレイ表示などを担当。今回はアプリ起動時にはトレイに格納されるだけで、メインウィンドウはありません。

  • アプリ起動時に、db.sqlite3がない場合には、自動で作成し、create tableにてテーブルの作成も行っています。
  • アプリ起動時に、オートリロードに設定値がある場合には、CronJobにて指定の分毎にxlsxからsqliteへデータを同期させるスケジュールを設定しています。
  • process.onにて終了時イベントの補足をし、db.close()するようにしています。
  • app.onのreadyイベントにて、tray化とアイコンの指定、コンテキストメニューの設定をしています。シングルクリックでメインメニュー表示(まだ未実装)、右クリックでコンテキストメニューが表示され、設定やリスト表示、アプリ終了などが出来るようにしています。
  • setwindowopenで設定画面、listwindowopenでメアドリスト表示用の画面を呼び出しています。
  • dialogモジュールはメインプロセス側からダイアログファイル選択画面を表示可能です。
  • getlist関数にて、db.allを使ってsqliteのmaillistテーブルに取得済みメアドをJSON形式でレンダラプロセス側に返しています。
  • updatelist関数にて、xlsxへ接続し、「シート名一覧を取得」「そのシートの有効セル範囲の取得」「1行目を除いたデータを配列で取得しています。
  • updatelist関数で受け取ったデータは、db.runにてinsert intoで1行ずつインサートしています。インサート前にはDELETEで一旦全データ削除も実行しています。
  • updatelist関数の引数が1の時はメッセージ表示、0の時はメッセージ非表示にしています。前者は手動アプデ時に利用し、後者はオートリロード時に利用するためこのようにしています。
  • オートリロードの設定が空の場合には、CronJobは作成されず、自動でデータの取得はされないようになっています。
  • メインのリスト表示はsendList関数です。Ctrl+Mキーでいつでも呼び出せるようにglobalshortcutを設定し、呼び出しています。
  • また、アプリ終了時に設定されたグローバルショートカットを解除するコードも追加しています(process.onのexitにて)
  • sendList関数でmain.htmlを呼び出しています。フレームレスで常に最前面に表示の状態で表示します。
  • getWindowPosition関数にて、ディスプレイ画面の右下の丁度よい位置にメインリストメニューを表示するよう座標を返しています。
  • 起動したら、次回以降自動で起動するようにauto-launchのコードが入っています。デバッグ時にはコメントアウトしましょう。
  • シートの順番どおりに取得できるように、updatelistおよびgetgrpsqliteについては、Promise.allで処理を入れてみました。

insert into、delete fromと、select fromではdbのメソッドが異なるので、注意が必要です。また、select文はselect * from tablenameで取得可能ですが、deleteなどはdelete * from tablenameではエラーになります。SQL文の方言に注意!!

レンダラプロセス側

セッティング画面(setting.html)

データの塊が入ってるxlsxファイルの指定や、自動的にxlsxからデータをリロードしてSQLite3のファイルに入れる時間間隔を指定する設定画面です。このxlsx設定がないとデータを取得できません。

  • setting.html起動時に、メインプロセスからelectron-storeで保存済みデータを取得して、反映しています。
  • ファイルの選択に於いては、メインプロセス側からファイル選択ダイアログを呼び出し、結果をテキストボックスに入力しています。
  • オートリロードは1分〜59分での指定に制限しています。
  • データ保存時には、オートリロードの値は空もしくは1〜59までの数値(正規表現でvalidation)だけを受け入れるようにしています。

図:xlsxファイルの指定とリロード時間の指定

メアドリスト表示画面(list.html)

取得したデータは、db.sqlite3ファイルのmaillistテーブルに格納されます。このデータから、現在取得済みのメールアドレスリストを一覧表示する機能の部分がlist.htmlになります。起動時にSQLite3にアクセスして全データを取得、jQuery DataTablesによって、綺麗に整形したデータを表示します。また、検索機能が素敵なので、リストのリアルタイムフィルターが気に入っています。

  • list.html起動時にipcRenderer.send('async', "keeplist");にて、sqliteのデータを取得しに行っています。
  • データはIPC通信にて、メインプロセス側から「データ件数」と「データの塊(JSON形式)」で取得しています。
  • その後、onList関数にて整形して、jQuery DataTablesで整形して反映しています。

図:取得済みリストの一覧表示

メインメニュー(main.html)

このアプリの一番の機能である「メアドリストメインメニュー」を担当するファイルです。sendListから呼び出されます。コンテキストメニューの「リスト表示」およびCtrl+Mのグローバルショートカットから呼び出せます。また、escキーを押すことで、自動でウィンドウをクローズするようにもしてあります。また、ウィンドウが非アクティブになっても自動でクローズするようにしてあります。

また、NGリストへの登録および、プライベートリストの表示にも追加で対応させました。プライベートリストは赤いパネルで表示され、通常のmaster.xlsxからのデータは青いパネルで表示するように区分けしてあります。プライベートリストはNGリストに未対応なので、xlsxから直接データを削除してもらえればOKです。

今回のBoxデザインはこちらからお借りしました。

  • DIV BOX全体にリンクを反映しているので、クリックするだけでメーラーが起動し、アドレスを自動で入れてくれます。
  • メアドの区切りはセミコロン(;)を利用しています。Outlookがデフォルトだとカンマ区切りがNGな為(設定変えれば済む話ではあるんですが・・・)
  • スクロールバーがダサいので、CSSにてスクロールバーは非表示にしています。ただし、リストが多い場合には非表示でもホイールでスクロールは可能です。
  • メインプロセス側からはシート単位でデータがレンダラプロセス側に送られてくるので、div id=groupの中身を取得して加えながら、innerHTMLで戻してあげてます。瞬時に行われるので、気になることはないと思いますが。
  • Ctrl+Mもしくはアイコンクリックで表示。Escキーもしくは非アクティブで閉じるように設定してあります。
  • プライベートリストは赤いパネル、通常のリストは青いパネルでNGアイコン付になります。

図:こんな感じに表示されます。

NGリスト(nglist.html)

現在、NG登録されているリストを表示します。また、この画面よりNG登録解除も出来るようにしてあります。基本レイアウトはlist.htmlと殆ど同じ形式です。

図:NGリストの様子。NGの解除をクリックで解除可能

スプラッシュスクリーンを付ける

今回のアプリケーションは、タスクトレイ常駐型のアプリケーションであるが故に、起動してもタスクトレイに常駐してるだけで、見た目なにもウィンドウは表示されません。そのため、起動しているのかしていないのかがわかりにくいので、スプラッシュスクリーンを付けてみたいと思います。

app.on('ready',() => {}の中にウィンドウを開くコード書き、setTimeoutで自動的に閉じるだけの簡単な方法です。

メインプロセス側コード

  • 今回のウィンドウはロゴタイトル用なので、フレームレス&背景透過のオプションを指定しておきます。
  • ロードした後、5秒後に自動的にクローズするように、setTimeoutを利用しています。
  • 実際の画像サイズとウィンドウのサイズに注意。画像サイズが大きいとおかしな表示になります。

レンダラ側コード(splash.html)

画像を1枚ロードするだけ。しかも、メインプロセスでフレームレス・背景透明化しているので、利用する画像が透過PNGであれば、綺麗に背景が透けるクールなロゴが表示されるようになります。

図:起動時にアプリのロゴを出すのがスプラッシュスクリーン

実行と結果の様子

electronでタスクトレイ常駐アプリをつくってみた

動画:こんな感じのグループリストが出てくる

関連リンク

Electronでタスクトレイ常駐のアプリを作る” に対して2件のコメントがあります。

  1. さわだ より:

    参考になりました。ありがとうございます。
    ちなみにapp.isPackagedを見れば開発中の起動かパッケージされたものなのかを区別できるようで、これによりauto-launchで登録を行うかどうかを分けることができました。

    1. akanemaru2017 より:

      さわださん

      app.isPackagedで判定できたんですね。これはURL Schemeの設定でも便利でした。
      ありがとうございます

コメントを残す

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

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)