Electronで次世代キ○タマを作った

どうも、Electronを触っているけど頑なにAtomエディタは使わない @Quramy です(Vim好きだからね！).

何年も前に一世を風靡した[Antinny](http://ja.wikipedia.org/wiki/Antinny)よろしく、5分毎にデスクトップのキャプチャを取得し、画像をTwitterにuploadするアプリケーションを作成しました.
[Quramy/electron-disclosure](https://github.com/Quramy/electron-disclosure)

このアプリを使って、作成されたツイートが下記です：

<blockquote class="twitter-tweet" lang="ja"><p lang="en" dir="ltr">Captured by <a href="https://t.co/Iiwtinz3f7">https://t.co/Iiwtinz3f7</a> at Sun, 31 May 2015 08:46:49 GMT <a href="http://t.co/jS09qh8cqs">pic.twitter.com/jS09qh8cqs</a></p>&mdash; quramy_dev (@Quramy_dev) <a href="https://twitter.com/Quramy_dev/status/604932029772230657">2015, 5月 31</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

このエントリでは, electron-disclosureを作成する上で利用したElectronのちょっとしたTipsを紹介したいと思います。
なお、Electronの基礎的な使い方については、手前味噌ですが、[このエントリ](http://qiita.com/Quramy/items/a4be32769366cfe55778)を参考にしてください。

# 主要な要素技術

## キャプチャの取得
キャプチャの取得には, WebRTCとCanvasのAPIを利用しています.

`navigator.webkitGetUserMedia` で`<video>`タグ用のStreamを作成し、`<video>`を`<canvas>`に`drawImage`で書き込むことで、デスクトップのキャプチャを実現できます。

コードにすると、下記のようになります。

```html
<html>
  <body>
    ...
    <video id="video" style="display:none"></video>
    ...
  </body>
</html>
```

```js:キャプチャ用の関数
var video = document.getElementById('video');
var streamUrl;
var getImage = function (callback) {
  var canvas = document.createElement('canvs');
  var context = canvas.getContext('2d');
  context.drawImage(video, 0, 0, canvas.width, canvas.height);
  callback(canvas.toDataUrl());
};

navigator.webkitGetUserMedia({
  audio: false,
  video: {
    mandatory: {
      chromeMediaSource: 'screen',
      minWidth: 800,
      maxWidth: 2560,
      minHeight: 600,
      maxHeight: 1440
    }
  }
}, function (stream) {
  var url;
  streamUrl = window.URL.createObjectURL(stream);
  video.src = streamUrl;
  video.play();
}, function (error) {
  console.log(error);
});
```

```js:利用側
getImage(function (url) {
  // <img>タグに画像として読み込む
  $('img').attr(src, url);

  // base64の文字列として取り出して利用する
  var dataString = url.replace('data:image/png;base64,', '');
});
```

なお、上記のコードはWebRTC, CanvasというHTML5の機能にしか依存していないので, Chrome単独でも動きそうなものですが、Chrome34以降では動作しないようです。
というのも、Chrome 34以降ではExstensionのAPIとして[desktopCapture API](https://developer.chrome.com/extensions/desktopCapture)が実装され, `chromeMediaSource`を`screen`に指定するためのflagが利用できなくなったためです。

### `capturePage`はNG.
デスクトップキャプチャに取り組んだ当初は,  BrowserWindowの作成時に`transparent: true`を指定した上で、BrowserWindowに実装されている`capturePage`メソッドを使ってみました。
`capturePage`はその名の如く、BrowserWindowのキャプチャを取得するAPIなので、BrowserWindow自体を透明化&サイズを最大化した上で実行すれば、デスクトップのキャプチャがとれるのでは、という発想です 。

しかし、このプランで実行してみたものの、真っ白い画像がキャプチャされるだけでデスクトップ全体の画像取得には至らなかったため、上述のWebRTCによる方式を採用しています.

Electronは`chrome.devTools.*`以外のchrome APIはほぼ使えないため, 現状では上述のChrome desktopCapture APIは利用できませんが、[issue#1380](https://github.com/atom/electron/issues/1380)を見ると, Chrome desktopCapture APIに直接アクセスできる口を作るproposalが挙がっているため、将来的にはElectronで利用できる日が来るかもしれません。

### デスクトップのサイズ取得
程よいサイズの画像を得るためには、`<canvas>`タグや`<video>`タグに`width`, `height`を適切に設定する必要があります。
ここで便利なのが、Electronのscreenモジュールです。下記のように、ディスプレイのサイズを取得することができます。

```js
var screen = require('screen');
var dispSize = screen.getPrimaryDisplay().size;
console.log(size.width, size.height);
```

## SNS連携(OAuth)
このアプリでは, twitter連携があります。 twitterのREST APIで、キャプチャした画像のUploadやツイートを行っています。
twitterのREST Clientとしては, [node-twitter-api](https://www.npmjs.com/package/node-twitter-api)というNode.js向けの実装を利用しています。
REST APIを叩くためには、OAuthのによる認可処理が必要となってくるわけですが、ユーザの許可後に自ドメインにコールバックさせる部分で、Electronならではな実装をする必要があります。
このあたりの内容については、[ElectronでのOAuth](http://qiita.com/Quramy/items/fc79cad92bb287478076)に別途エントリを上げているので、ここでは詳細は省略します。

### 取得したTokenのキャッシュ
OAuthで取得したTokenは, クライアントに保持しておくようにしておきたいものです。
ファイルの保存には, Node.jsのfs.WriteFileを使えばよいとして、どこに保存したものでしょうか。
Electronでは、`app.getPath('cache')` を利用することで、アプリケーション用のデータ領域を取得できるため、今回はこれを用いています。
`getPath('cache')`は、動作するOS毎に適切なパスを返してくれます:

* Windowsの場合: `%APPDATA%`
* Linuxの場合: `$XDG_CACHE_HOME` または `~/.cache`
* OS Xの場合: `~/Library/Caches`

## デスクトップ通知
今回のアプリケーションには、「twitterへのアップロードを行ったら、デスクトップ通知を行う」という機能を実装しています。
5分に一回キャプチャの取得を行うため、必ずしもElectronのBrowserWindowにフォーカスが当たっているとは限らないからです。

デスクトップ通知はHTML5のNotification APIで実現できます。

使い方は非常にシンプルで、BrowserWindowで下記のようなコードを実行するだけでデスクトップ通知が行えます。

```js
var n = new Notification('タイトル', {
  body: '本文'
});
n.onclick = function () {
  // Notificationのアイコンをクリックした時に発火する処理
};
```

ChromeではNotification APIを利用する場合はユーザの許可を求めるポップアップが表示されますが、Electronの場合、そのような制約を受けることなく通知を利用できます。

## デフォルトのブラウザでURLを開く

Electronの`shell`モジュールの`openExternal`を利用すると, 指定したURLをデフォルトアプリケーションで開くことができます。

下記のように、前述のNotificationと組み合わせることで、「デスクトップ通知をクリックしたらtwitterのWebページをブラウザで開く」のような処理を簡単に実現することができます。

```js
var n = new Notification('click me');
n.onclick = function () {
  var shell = require('shell');
  shell.openExternal('https://twitter.com');
};
```

# 応用例
このエントリのまとめとして、今回作成したアプリの応用例を考えてみました。

## 真面目に使い道を考えてみる
一見、ウィルス紛いのアプリですが、視点を少し変えると、有用なシーンも存在します。

例えば、遠隔地で働く作業者の監視が挙げられます。
もちろん、Upload先をtwitterから独自のAPIに変更する必要はありますが、雇った作業者がきちんと仕事を行っているか、定期的に監視を行うようなツールが作れます。

## Gyazoっぽいアレ
画像のキャプチャ&SNSの連携がElectronで簡単に実現できることが伝わったと思います。
@uiureo 氏の[ElectronでGyazoっぽいUIを実現する](http://qiita.com/uiureo/items/0353e37bcffe94731c6b)で紹介されているUIと組み合わせれば、それこそクロスプラットフォームなGyazoもどきだって作れますね！