Electronデスクトップアプリ開発入門(4)

Electronデスクトップアプリ開発入門(4)

Electron APIデモから学ぶ実装テクニック ― システムとメディア

2017年2月27日

Electron API Demosで紹介されている、Electronアプリの実装テクニックを紹介。今回はシステムとメディアの実装方法を基礎から説明する。

尾崎 義尚
  • このエントリーをはてなブックマークに追加

はじめに

 前回は、OSのネイティブ機能へのアクセス、アプリ内のプロセス間通信を解説した。今回は、実行環境の情報取得、クリップボードの操作、メディア機能としてPDF出力と画面キャプチャの取得について解説していく。

 なお、今回もElectron API Demosというデモアプリを使う。デモアプリを動かす方法は、前々回の説明を参照してほしい。

APIデモアプリ

システム(SYSTEM)

 ここでは、環境情報の取得や、クリップボードの操作、特定のプロトコルを処理してアプリを起動するなど、システムに関連する機能を解説している。

▲メニュー項目の一覧に戻る

アプリやユーザーのシステム情報を取得する(Get app or system information)
JavaScript
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;
  });
});
リスト1 アプリのパスを取得している(メインプロセス側: index.js)

appモジュールは、メインプロセスからのみ使えるため、IPCの'get-app-path'チャネルで呼び出されたら、アプリのパスを取得して、そのパスを'got-app-path'チャネルで返している。

 リスト1では、appモジュールで取得したパスをIPCの'got-app-path'チャネルで返している。appモジュールはメインプロセスからのみ利用可能だが、レンダラープロセスから使いたい場合は、remoteモジュールを経由して、require('electron').remote.appとして呼び出すこともできる。

 appモジュールは、アプリのバージョンやパスなどの情報や、システムで保持している情報にアクセスできる。具体的にどのような機能があるかは、APIリファレンス(英語)のメソッドを中心に参照日本語)してほしい。

HTML
<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>
リスト2 システム情報を取得して表示(レンダラープロセス側: index.html)

アプリのパス、Electronのバージョン、OSのホームディレクトリ、ディスプレイのサイズを取得している。

 リスト2の内容を説明していこう。アプリのパスを取得する部分は、IPCでメインプロセスから取得しているのでここでは解説を省略する。

 Electronのバージョン取得には、processオブジェクトが使われている(リスト3)。

JavaScript
const electronVersion = process.versions.electron
const versionMessage = `This app is using Electron version: ${electronVersion}`
document.getElementById('got-electron-version').innerHTML = versionMessage
リスト3 Electronを使用して作成するアプリではメインプロセスとレンダラープロセスが別プロセスで動作するが、processオブジェクトはどちらでも使用できる

 processオブジェクトのその他の機能はAPIリファレンス日本語)を参照してほしい。

 ホームディレクトリの取得には、Node.jsのosモジュールを使っている(リスト4)。

JavaScript
const os = require('os')
const homeDir = os.homedir()
const homeDirMessage = `Your system home directory is: ${homeDir}`
document.getElementById('got-sys-info').innerHTML = homeDirMessage
リスト4 osモジュールを使うと、ディレクトリ情報だけでなく、プラットフォームの種類や、CPU名と使用率、ネットワークの情報などが取得できる

 画面解像度の取得には、screenモジュールが使われている(リスト5)。

JavaScript
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
リスト5 window.screenがDOMプロパティによって予約されているため、ここでは(screenではなく)electronScreenという変数名で受け取っている

 screenモジュール日本語)からはディスプレイサイズだけでなく、カーソルの位置や外部ディスプレイの情報などにアクセスできる。

 このコードを実行してみよう。筆者の環境では図1のように表示された。

図1 システム情報の取得結果

▲メニュー項目の一覧に戻る

クリップボード(Clipboard)

 クリップボードのコピー&ペーストは当然サポートされている。

JavaScript
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 ウィンドウを表示しているだけのコード(メインプロセス側: index.js)

 リスト6に示したメインプロセス側では、ウィンドウを表示しているだけなので、レンダラープロセス側を見ていこう(リスト7)。

HTML
<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>
リスト7 クリップボードの処理(レンダラープロセス側: index.html)

コピー元のテキストボックス(copy-to-input)、コピーボタン(copy-to)、ペーストボタン(paste-to)、ペースト先(paste-from)があり、コピーとペーストを実装している。

 実装内容を解説していこう。

JavaScript
const clipboard = require('electron').clipboard
リスト8 クリップボード処理の部分解説(1)

 リスト8に示した部分のコードでは、clipboardモジュールを取得している。

JavaScript
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 クリップボード処理の部分解説(2)

 次にリスト9のコードでは、[Copy]ボタンがクリックされると、clipboardモジュールのwriteTextメソッドで、テキストボックスに入力された文字列をクリップボードに書き込んでいる。

JavaScript
const pasteBtn = document.getElementById('paste-to')
pasteBtn.addEventListener('click', function() {
  const message = `Clipboard contents: ${clipboard.readText()}`
  document.getElementById('paste-from').innerHTML = message
})
リスト10 クリップボード処理の部分解説(3)

 リスト10では、[Paste]ボタンがクリックされると、clipboardモジュールのreadTextメソッドを使ってクリップボードから文字列を読み込んでいる。

 テキスト以外の読み書きもできるので、clipboardモジュールのAPIリファレンス日本語)を参照してほしい。

▲メニュー項目の一覧に戻る

プロトコルハンドラーによるアプリの起動(Launch app from protocol handler)

 開発したElectronアプリは、特定のプロトコルのURL呼び出しで開くデフォルトのアプリとしても登録できる。例えばリスト12では、サンプルアプリ自体をelectron-api-demos://プロトコルのデフォルトアプリとして登録している。

JavaScript
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)
リスト11 特定のプロトコルに対するデフォルトアプリとして登録(メインプロセス側: index.js)

appモジュールのsetAsDefaultProtocolClientメソッドで独自のプロトコル名を登録して、open-urlイベント(macOSのみ)でプロトコルから開かれたことを検出している。つまり、このサンプルはmacOSでしか正常に動作しないので注意してほしい。
メッセージボックスを表示するには、前々回説明したdialog.showMessageBoxメソッドを使うが、ここでは引数がより簡単なdialog.showErrorBoxメソッドで代用している(「エラー」という意味ではない)。
protocol-link.htmlファイルの内容は後述する。

JavaScript
app.setAsDefaultProtocolClient('electron-api-demos')
リスト12 プロトコル処理の部分解説(1)

 リスト12では、electron-api-demos://から始まるURLを開いたときに、このアプリが起動するように登録している。Windowsの場合は、デフォルトで実行ファイルが登録されるが、デバッグ時はElectron.exeが実行されるため、アプリを起動できない。アプリをパッケージ化してから試す必要がある(パッケージ化については次回解説する)。Linuxは現時点ではサポートされていない。

JavaScript
app.on('open-url', function(event, url) {
  dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})
リスト13 プロトコル処理の部分解説(2)

 次にリスト13のコードでは、macOSでプロトコルを経由してアプリが起動されると、起動時のURLをパラメーターにopen-urlイベントが呼び出される。起動時のパラメーターはURLをパースして取得すればよい。

JavaScript
const shell = require('electron').shell
const pagePath = path.join('file://', app.getAppPath(), './protocol-link.html')
shell.openExternal(pagePath)
リスト14 プロトコル処理の部分解説(3)

 リスト14では、ブラウザーを開いて、以下のHTMLファイル(protocol-link.html)を開く。

HTML
<html>
<body>
  <a class="protocol" href="electron-api-demos://open">
    <h3>electron-api-demos://open</h3>
  </a>
</body>
</html>
リスト15 外部ページ(protocol-link.html)

アプリで登録したelectron-api-demos://プロトコルを起動するリンクを表示している。

 このコードを実行すると、デフォルトのWebブラウザーが起動し、図2のように表示される。リンクをクリックすると、デフォルトとして登録したアプリが起動することで、リスト13で記述したメッセージボックスが表示される。

図2 Webブラウザーのリンクをクリックするとアプリが起動する。

リンクをクリックすると、アプリの起動確認ダイアログが表示されて、許可をするとアプリが起動される。

 このようにアプリ固有のプロトコルを決めることで、アプリ間の連携ができる。

メディア(MEDIA)

 ここでは、PDFへのプリントやスクリーンショットなど、メディア機能を解説している。

▲メニュー項目の一覧に戻る

PDFにプリント(Print to PDF)

 Electronでは、Chromiumのプリント機能を使って表示しているコンテンツを印刷できる。リスト16では、表示しているコンテンツをPDF形式にプリントしている。

JavaScript
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 PDFへのプリント(メインプロセス側: index.js)

 コードを解説していく。

 リスト16を見ると、ipc.on('print-to-pdf', function(event) { …… })というコードで、IPCで'print-to-pdf'チャネルにメッセージを受信すると、WebコンテンツをPDFにプリントするという処理になっている。そのチャネル受信時に呼ばれる関数内では、以下のコードが記載されている。

JavaScript
const pdfPath = path.join(os.tmpdir(), 'print.pdf')
リスト17 PDFプリント処理の部分解説(1)

 リスト17のコードで、一時ディレクトリのパス名に「print.pdf」というPDFファイル名を連結させた、PDFファイルパスを作成する。

JavaScript
const win = BrowserWindow.fromWebContents(event.sender)
リスト18 PDFプリント処理の部分解説(2)

 リスト18で、呼び出し元レンダラープロセスのBrowserWindowインスタンスを作成する。

JavaScript
win.webContents.printToPDF({}, function(error, data) {
リスト19 PDFプリント処理の部分解説(3)

 リスト19のコードで、printToPDFメソッドを呼び出してPDFデータを生成する(=PDFへプリントする)。

JavaScript
if (error) throw error
fs.writeFile(pdfPath, data, function(error) {
リスト20 PDFプリント処理の部分解説(4)

 リスト20で、PDFデータ生成でエラーがあれば例外をスローする。なければ、fs.writeFileメソッドで、PDFデータ(data)をファイルに書き込む。

JavaScript
shell.openExternal('file://' + pdfPath)
リスト21 PDFプリント処理の部分解説(5)

 確認のため、リスト21のコードで、作成したPDFファイルを開く。

JavaScript
event.sender.send('wrote-pdf', pdfPath)
リスト22  PDFプリント処理の部分解説(6)

 さらにリスト22により、IPCの'wrote-pdf'チャネル経由で、呼び出し元のレンダラープロセスにPDFファイル名を通知する。そのレンダラープロセスのコードは、リスト23のとおりだ。

HTML
<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>
リスト23 印刷実行のIPC送信と、印刷結果のIPC受信(レンダラープロセス側: index.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がそのサンプルコードである。

JavaScript
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;
  });
});
リスト24 ウィンドウを表示しているだけのコード(メインプロセス側: index.js)
HTML
<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>
リスト25 [Screen shot]ボタンがクリックされたら、画面キャプチャのデータを取得して、PNG画像ファイルとして一時ディレクトリに保存している

 それではブロックごとに解説していこう。

JavaScript
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 スクリーンキャプチャ処理の部分解説(1)

 リスト26のコードでは、[Screen shot]ボタンがクリックされると、オプションを指定して、desktopCapturerモジュールのgetSourcesメソッドを呼び出す。オプションのtypesには、windowscreenが指定でき、つまりウィンドウか画面かを選択できる。thumbnailSizeには作成する画像サイズを指定する。

JavaScript
sources.forEach(function(source) {
  if (source.name === 'Entire screen' || source.name === 'Screen 1') {
リスト27 スクリーンキャプチャ処理の部分解説(2)

 リスト27では、パラメーターsourcesにより、ソース(=画面キャプチャの対象)が列挙されてくるので、全画面(Entire screen)かスクリーン1(Screen 1)のときに、そのソースの画像を処理する(リスト28)。ちなみに、この例ではスクリーン種別が列挙されたが、先ほどのgetSourcesメソッドのオプションのtypeswindowのみを指定すると、ウィンドウのタイトルが列挙される。

JavaScript
const screenshotPath = path.join(os.tmpdir(), 'screenshot.png')
fs.writeFile(screenshotPath, source.thumbnail.toPng(), function(error) {
リスト28 スクリーンキャプチャ処理の部分解説(3)

 最後にリスト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と通信の実装方法を基礎から説明する。

4. 【現在、表示中】≫ Electron APIデモから学ぶ実装テクニック ― システムとメディア

Electron API Demosで紹介されている、Electronアプリの実装テクニックを紹介。今回はシステムとメディアの実装方法を基礎から説明する。

5. Electronアプリのデバッグと、パッケージ化

本格的にElectronアプリ開発を進める方に向けて、そのデバッグ方法と、製品リリースのためのパッケージ作成の方法について説明する。

サイトからのお知らせ

Twitterでつぶやこう!