Electronデスクトップアプリ開発入門(4)
Electron APIデモから学ぶ実装テクニック ― システムとメディア
Electron API Demosで紹介されている、Electronアプリの実装テクニックを紹介。今回はシステムとメディアの実装方法を基礎から説明する。
はじめに
前回は、OSのネイティブ機能へのアクセス、アプリ内のプロセス間通信を解説した。今回は、実行環境の情報取得、クリップボードの操作、メディア機能としてPDF出力と画面キャプチャの取得について解説していく。
なお、今回もElectron API Demosというデモアプリを使う。デモアプリを動かす方法は、前々回の説明を参照してほしい。
APIデモアプリ
システム(SYSTEM)
ここでは、環境情報の取得や、クリップボードの操作、特定のプロトコルを処理してアプリを起動するなど、システムに関連する機能を解説している。
アプリやユーザーのシステム情報を取得する(Get app or system information)
const path = require('path')
const { app, BrowserWindow } = require('electron')
const ipc = require('electron').ipcMain
ipc.on('get-app-path', function(event) {
event.sender.send('got-app-path', app.getAppPath())
})
app.on('ready', function() {
mainWindow = new BrowserWindow({ width: 600, height: 200 });
mainWindow.loadURL(`file://${__dirname}/index.html`);
mainWindow.on('closed', () => {
mainWindow = null;
});
});
|
app
モジュールは、メインプロセスからのみ使えるため、IPCの'get-app-path'チャネルで呼び出されたら、アプリのパスを取得して、そのパスを'got-app-path'チャネルで返している。
リスト1では、app
モジュールで取得したパスをIPCの'got-app-path'チャネルで返している。app
モジュールはメインプロセスからのみ利用可能だが、レンダラープロセスから使いたい場合は、remote
モジュールを経由して、require('electron').remote.app
として呼び出すこともできる。
app
モジュールは、アプリのバージョンやパスなどの情報や、システムで保持している情報にアクセスできる。具体的にどのような機能があるかは、APIリファレンス(英語)のメソッドを中心に参照(日本語)してほしい。
<html>
<body>
<div id="got-app-info"></div>
<div id="got-electron-version"></div>
<div id="got-sys-info"></div>
<div id="got-screen-info"></div>
</body>
<script>
// アプリのパスを取得
const ipc = require('electron').ipcRenderer
ipc.send('get-app-path')
ipc.on('got-app-path', function(event, path) {
const message = `This app is located at: ${path}`
document.getElementById('got-app-info').innerHTML = message
})
// Electronのバージョンを取得
const electronVersion = process.versions.electron
const versionMessage = `This app is using Electron version: ${electronVersion}`
document.getElementById('got-electron-version').innerHTML = versionMessage
// OSのホームディレクトリを取得
const os = require('os')
const homeDir = os.homedir()
const homeDirMessage = `Your system home directory is: ${homeDir}`
document.getElementById('got-sys-info').innerHTML = homeDirMessage
// ディスプレイのサイズを取得
const electronScreen = require('electron').screen
const size = electronScreen.getPrimaryDisplay().size
const screenMessage = `Your screen is: ${size.width}px x ${size.height}px`
document.getElementById('got-screen-info').innerHTML = screenMessage
</script>
</html>
|
アプリのパス、Electronのバージョン、OSのホームディレクトリ、ディスプレイのサイズを取得している。
リスト2の内容を説明していこう。アプリのパスを取得する部分は、IPCでメインプロセスから取得しているのでここでは解説を省略する。
Electronのバージョン取得には、process
オブジェクトが使われている(リスト3)。
const electronVersion = process.versions.electron
const versionMessage = `This app is using Electron version: ${electronVersion}`
document.getElementById('got-electron-version').innerHTML = versionMessage
|
process
オブジェクトのその他の機能はAPIリファレンス(日本語)を参照してほしい。
ホームディレクトリの取得には、Node.jsのos
モジュールを使っている(リスト4)。
const os = require('os')
const homeDir = os.homedir()
const homeDirMessage = `Your system home directory is: ${homeDir}`
document.getElementById('got-sys-info').innerHTML = homeDirMessage
|
画面解像度の取得には、screen
モジュールが使われている(リスト5)。
const electronScreen = require('electron').screen
const size = electronScreen.getPrimaryDisplay().size
const screenMessage = `Your screen is: ${size.width}px x ${size.height}px`
document.getElementById('got-screen-info').innerHTML = screenMessage
|
screen
モジュール(日本語)からはディスプレイサイズだけでなく、カーソルの位置や外部ディスプレイの情報などにアクセスできる。
このコードを実行してみよう。筆者の環境では図1のように表示された。
クリップボード(Clipboard)
クリップボードのコピー&ペーストは当然サポートされている。
const path = require('path')
const { app, BrowserWindow } = require('electron')
app.on('ready', function() {
mainWindow = new BrowserWindow({ width: 300, height: 200 });
mainWindow.loadURL(`file://${__dirname}/index.html`);
mainWindow.on('closed', () => {
mainWindow = null;
});
});
|
リスト6に示したメインプロセス側では、ウィンドウを表示しているだけなので、レンダラープロセス側を見ていこう(リスト7)。
<html>
<body>
<input id="copy-to-input" type="text">
<button id="copy-to">Copy</button>
<button id="paste-to">Paste</button>
<div id="paste-from"></div>
</body>
<script>
const clipboard = require('electron').clipboard
// コピー
const copyBtn = document.getElementById('copy-to')
const copyInput = document.getElementById('copy-to-input')
copyBtn.addEventListener('click', function() {
if (copyInput.value !== '') {
clipboard.writeText(copyInput.value)
}
})
// ペースト
const pasteBtn = document.getElementById('paste-to')
pasteBtn.addEventListener('click', function() {
const message = `Clipboard contents: ${clipboard.readText()}`
document.getElementById('paste-from').innerHTML = message
})
</script>
</html>
|
コピー元のテキストボックス(copy-to-input)、コピーボタン(copy-to)、ペーストボタン(paste-to)、ペースト先(paste-from)があり、コピーとペーストを実装している。
実装内容を解説していこう。
const clipboard = require('electron').clipboard
|
リスト8に示した部分のコードでは、clipboard
モジュールを取得している。
const copyBtn = document.getElementById('copy-to')
const copyInput = document.getElementById('copy-to-input')
copyBtn.addEventListener('click', function() {
if (copyInput.value !== '') {
clipboard.writeText(copyInput.value)
}
})
|
次にリスト9のコードでは、[Copy]ボタンがクリックされると、clipboard
モジュールのwriteText
メソッドで、テキストボックスに入力された文字列をクリップボードに書き込んでいる。
const pasteBtn = document.getElementById('paste-to')
pasteBtn.addEventListener('click', function() {
const message = `Clipboard contents: ${clipboard.readText()}`
document.getElementById('paste-from').innerHTML = message
})
|
リスト10では、[Paste]ボタンがクリックされると、clipboard
モジュールのreadText
メソッドを使ってクリップボードから文字列を読み込んでいる。
テキスト以外の読み書きもできるので、clipboard
モジュールのAPIリファレンス(日本語)を参照してほしい。
プロトコルハンドラーによるアプリの起動(Launch app from protocol handler)
開発したElectronアプリは、特定のプロトコルのURL呼び出しで開くデフォルトのアプリとしても登録できる。例えばリスト12では、サンプルアプリ自体をelectron-api-demos://
プロトコルのデフォルトアプリとして登録している。
const path = require('path')
const { app, BrowserWindow, dialog } = require('electron')
app.setAsDefaultProtocolClient('electron-api-demos')
app.on('open-url', function(event, url) {
dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})
// ブラウザーを起動してページを表示する
const shell = require('electron').shell
const pagePath = path.join('file://', app.getAppPath(), './protocol-link.html')
shell.openExternal(pagePath)
|
app
モジュールのsetAsDefaultProtocolClient
メソッドで独自のプロトコル名を登録して、open-url
イベント(macOSのみ)でプロトコルから開かれたことを検出している。つまり、このサンプルはmacOSでしか正常に動作しないので注意してほしい。
メッセージボックスを表示するには、前々回説明したdialog.showMessageBox
メソッドを使うが、ここでは引数がより簡単なdialog.showErrorBox
メソッドで代用している(「エラー」という意味ではない)。
protocol-link.htmlファイルの内容は後述する。
app.setAsDefaultProtocolClient('electron-api-demos')
|
リスト12では、electron-api-demos://
から始まるURLを開いたときに、このアプリが起動するように登録している。Windowsの場合は、デフォルトで実行ファイルが登録されるが、デバッグ時はElectron.exe
が実行されるため、アプリを起動できない。アプリをパッケージ化してから試す必要がある(※パッケージ化については次回解説する)。Linuxは現時点ではサポートされていない。
app.on('open-url', function(event, url) {
dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})
|
次にリスト13のコードでは、macOSでプロトコルを経由してアプリが起動されると、起動時のURLをパラメーターにopen-url
イベントが呼び出される。起動時のパラメーターはURLをパースして取得すればよい。
const shell = require('electron').shell
const pagePath = path.join('file://', app.getAppPath(), './protocol-link.html')
shell.openExternal(pagePath)
|
リスト14では、ブラウザーを開いて、以下のHTMLファイル(protocol-link.html)を開く。
<html>
<body>
<a class="protocol" href="electron-api-demos://open">
<h3>electron-api-demos://open</h3>
</a>
</body>
</html>
|
アプリで登録したelectron-api-demos://
プロトコルを起動するリンクを表示している。
このコードを実行すると、デフォルトのWebブラウザーが起動し、図2のように表示される。リンクをクリックすると、デフォルトとして登録したアプリが起動することで、リスト13で記述したメッセージボックスが表示される。
このようにアプリ固有のプロトコルを決めることで、アプリ間の連携ができる。
メディア(MEDIA)
ここでは、PDFへのプリントやスクリーンショットなど、メディア機能を解説している。
PDFにプリント(Print to PDF)
Electronでは、Chromiumのプリント機能を使って表示しているコンテンツを印刷できる。リスト16では、表示しているコンテンツをPDF形式にプリントしている。
const fs = require('fs')
const os = require('os')
const path = require('path')
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const app = electron.app
const ipc = electron.ipcMain
const shell = electron.shell
ipc.on('print-to-pdf', function(event) {
const pdfPath = path.join(os.tmpdir(), 'print.pdf')
const win = BrowserWindow.fromWebContents(event.sender)
win.webContents.printToPDF({}, function(error, data) {
if (error) throw error
fs.writeFile(pdfPath, data, function(error) {
if (error) {
throw error
}
shell.openExternal('file://' + pdfPath)
event.sender.send('wrote-pdf', pdfPath)
})
})
})
app.on('ready', function() {
mainWindow = new BrowserWindow({ width: 600, height: 200 });
mainWindow.loadURL(`file://${__dirname}/index.html`);
mainWindow.on('closed', () => {
mainWindow = null;
});
});
|
コードを解説していく。
リスト16を見ると、ipc.on('print-to-pdf', function(event) { …… })
というコードで、IPCで'print-to-pdf'チャネルにメッセージを受信すると、WebコンテンツをPDFにプリントするという処理になっている。そのチャネル受信時に呼ばれる関数内では、以下のコードが記載されている。
const pdfPath = path.join(os.tmpdir(), 'print.pdf')
|
リスト17のコードで、一時ディレクトリのパス名に「print.pdf」というPDFファイル名を連結させた、PDFファイルパスを作成する。
const win = BrowserWindow.fromWebContents(event.sender)
|
リスト18で、呼び出し元レンダラープロセスのBrowserWindow
インスタンスを作成する。
win.webContents.printToPDF({}, function(error, data) {
|
リスト19のコードで、printToPDF
メソッドを呼び出してPDFデータを生成する(=PDFへプリントする)。
if (error) throw error
fs.writeFile(pdfPath, data, function(error) {
|
リスト20で、PDFデータ生成でエラーがあれば例外をスローする。なければ、fs.writeFile
メソッドで、PDFデータ(data)をファイルに書き込む。
shell.openExternal('file://' + pdfPath)
|
確認のため、リスト21のコードで、作成したPDFファイルを開く。
event.sender.send('wrote-pdf', pdfPath)
|
さらにリスト22により、IPCの'wrote-pdf'チャネル経由で、呼び出し元のレンダラープロセスにPDFファイル名を通知する。そのレンダラープロセスのコードは、リスト23のとおりだ。
<html>
<body>
Print to PDF
<button id="print-pdf">Print</button>
<div id="pdf-path"></div>
<script>
const ipc = require('electron').ipcRenderer
const printPDFBtn = document.getElementById('print-pdf')
printPDFBtn.addEventListener('click', function(event) {
ipc.send('print-to-pdf')
})
ipc.on('wrote-pdf', function(event, path) {
const message = `Wrote PDF to: ${path}`
document.getElementById('pdf-path').innerHTML = message
})
</script>
</body>
</html>
|
[Print]ボタンがクリックされると、IPCの'print-to-pdf'チャネルでメッセージ送信している。また、'wrote-pdf'チャネルでメッセージを受信すると、作成されたPDFファイル名を<div id="pdf-path"></div>
要素に表示している。
レンダラープロセス側は、[Print]ボタンが押されたらIPCの'print-to-pdf'チャネルでメッセージ送信して、メインプロセスにPDF作成を依頼している。PDFが作成されたらIPCの'wrote-pdf'チャネルでPDFファイル名が渡されるので、それを表示している。
印刷処理のポイントは、リスト16のwin.webContents.printToPDF
メソッドになるが、そのwebContents
オブジェクトには、レンダラープロセスに表示しているコンテンツに対するメソッドが用意されており、印刷関連以外にもさまざまな機能が提供されているので、ぜひ一通り目を通すことをお勧めする。
スクリーンショットの取得(Take a screenshot)
Electronでは、スクリーンキャプチャのためのdesktopCapturer
モジュールが提供されている。リスト24とリスト25がそのサンプルコードである。
const path = require('path')
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const app = electron.app
app.on('ready', function() {
mainWindow = new BrowserWindow({ width: 300, height: 200 });
mainWindow.loadURL(`file://${__dirname}/index.html`);
mainWindow.on('closed', () => {
mainWindow = null;
});
});
|
<html>
<body>
<button id="screen-shot">Screen shot</button>
<div id="screenshot-path"></div>
<script>
const electron = require('electron')
const desktopCapturer = electron.desktopCapturer
const shell = electron.shell
const fs = require('fs')
const os = require('os')
const path = require('path')
const screenshot = document.getElementById('screen-shot')
const screenshotMsg = document.getElementById('screenshot-path')
screenshot.addEventListener('click', function(event) {
screenshotMsg.textContent = 'Gathering screens...'
let options = {
types: ['screen'],
thumbnailSize: {
width: 1024,
height: 768
}
}
desktopCapturer.getSources(options, function(error, sources) {
if (error) return console.log(error)
sources.forEach(function(source) {
if (source.name === 'Entire screen' || source.name === 'Screen 1') {
const screenshotPath = path.join(os.tmpdir(), 'screenshot.png')
fs.writeFile(screenshotPath, source.thumbnail.toPng(), function(error) {
if (error) return console.log(error)
shell.openExternal('file://' + screenshotPath)
const message = `Saved screenshot to: ${screenshotPath}`
screenshotMsg.textContent = message
})
}
})
})
})
</script>
</body>
</html>
|
それではブロックごとに解説していこう。
screenshot.addEventListener('click', function(event) {
screenshotMsg.textContent = 'Gathering screens...'
let options = {
types: ['screen'],
thumbnailSize: {
width: 1024,
height: 768
}
}
desktopCapturer.getSources(options, function(error, sources) {
if (error) return console.log(error)
|
リスト26のコードでは、[Screen shot]ボタンがクリックされると、オプションを指定して、desktopCapturer
モジュールのgetSources
メソッドを呼び出す。オプションのtypes
には、windowかscreenが指定でき、つまりウィンドウか画面かを選択できる。thumbnailSize
には作成する画像サイズを指定する。
sources.forEach(function(source) {
if (source.name === 'Entire screen' || source.name === 'Screen 1') {
|
リスト27では、パラメーターsources
により、ソース(=画面キャプチャの対象)が列挙されてくるので、全画面(Entire screen)かスクリーン1(Screen 1)のときに、そのソースの画像を処理する(リスト28)。ちなみに、この例ではスクリーン種別が列挙されたが、先ほどのgetSources
メソッドのオプションのtypes
にwindowのみを指定すると、ウィンドウのタイトルが列挙される。
const screenshotPath = path.join(os.tmpdir(), 'screenshot.png')
fs.writeFile(screenshotPath, source.thumbnail.toPng(), function(error) {
|
最後にリスト28で、一時フォルダーに「screenshot.png」という名前でファイルを保存する。
desktopCapturer
のリファレンス(日本語)には、その他にビデオやオーディオのキャプチャについての解説もある。
まとめ
今回は、システム情報として実行環境の情報取得やクリップボードへのアクセス、PDFへの出力方法、デバッグ方法について解説した。次回は最終回として、Electronアプリのデバッグと、製品としてリリースするためのパッケージ作成について解説する。
1. Electronとは? アーキテクチャ/API/インストール方法/初期設定
Windows/macOS/Linuxで実行できるデスクトップアプリをWeb技術で作ろう! Electronの概要から開発を始めて動かすところまでを解説する。
2. Electron APIデモから学ぶ実装テクニック ― ウィンドウ管理とメニュー
Electron API Demosで紹介されている、Electronアプリの実装テクニックを紹介。今回はウィンドウ管理とメニューの実装方法を基礎から説明する。
3. Electron APIデモから学ぶ実装テクニック ― ネイティブUIと通信
Electron API Demosで紹介されている、Electronアプリの実装テクニックを紹介。今回はネイティブUIと通信の実装方法を基礎から説明する。