Electronアプリで、アプリアイコンにドロップされたファイルを処理する
Finder上のアプリアイコンにファイルをドロップされた時、
Dock上のアプリアイコンにファイルをドロップされた時、
Electron製のアプリで、そのファイルを受け取って処理する機能の実装方法です。
(Macの話だったり、私製アプリのコードの断片が入ってたり、
Typescriptだったり、パラメータが雑だったりするけど、うまく読み替えて欲しい。)
アプリのデータの持ち方にもよりますけど、
・アプリが起動中にファイルをドロップする
・アプリが起動していない時にファイルをドロップする
で、処理方法を変える必要があると思う。
Info.plistでアプリがファイルを受け取れるように設定する
- Mac環境の話
- ファイルをアプリアイコンに落とした時に反応するように、アプリのInfo.plistを更新します。
- 次のようなplistファイルを作って、アプリのInfo.plistに組み込みます。
- この設定を入れないと、ファイルをドロップしても、アプリが反応しない。
- extend.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDocumentTypes</key> <array> <dict> <key>CFBundleTypeName</key> <string>All Files</string> <key>LSHandlerRank</key> <string>Owner</string> <key>LSItemContentTypes</key> <array> <string>public.text</string> <string>public.data</string> <string>public.content</string> </array> </dict> </array> </dict> </plist>
- このextend.plistを、electron-packagerコマンド実行時に組み込んで、Info.plistにパラメータを追加します。
- extend-infoで指定します。
electron-packager . YourAppName --platform=darwin --arch=x64 --extend-info=extend.plist
ファイルドロップのイベントを受け取る
- "will-finish-launching"のタイミングで、"open-file"イベントをハンドリングする処理を登録します。(ここより前で拾うとマズイのかな?試さないけど)
- main processのjavascript内に実装します。
- preventDefault()した後に、ファイルをハンドリングする処理を入れます。
import {app} from 'electron'; // receive drop file to app icon event let launchArgs = null; app.on('will-finish-launching', () => { app.on('open-file', (event, filePath) => { event.preventDefault(); if (myApp.mainWindow) { myApp.mainWindow.webContents.send('dropTextFile', filePath); } else { launchArgs = { filePath: filePath }; } }); }); ...(略)...
アプリアイコンにファイルをドロップする際、
- アプリが起動済みである場合と、
- アプリがまだ起動していない場合がありますよね。
アプリが起動済みで、BrowserWindowのインスタンスが既に作られているのであれば、
このようなファイルが渡されたよ、とイベントを飛ばせば事足ります。
if (myApp.mainWindow) { myApp.mainWindow.webContents.send('dropTextFile', filePath); }
BrowserWindowインスタンス初期化時に、ファイルの情報を渡す
- 起動していないアプリに、ファイルがドロップされた場合は、BrowserWindowのインスタンスはまだ作られていません。
- ウィンドウのインスタンスが出来てから、イベントを投げて、ファイルの情報を渡してあげましょう。
- (何らかの変数越しにパラメータを渡しても良いですけれど)
function showMainWindow(launchArgs): void { const myApp = this; let _launchArgs = launchArgs; mainWindow = new BrowserWindow({ width: 800, height: 800, x: x, y: y, show: false, // show at did-finish-load event }); mainWindow.loadURL(`file://${__dirname}/contents-main.html`); // main window event mainWindow.webContents.on('did-finish-load', () => { // receive drop file to app icon event if (_launchArgs && _launchArgs.filePath) { const filePath = _launchArgs.filePath; _launchArgs = null; // for window reload mainWindow.webContents.send('dropTextFile', filePath); } // show mainWindow.show(); mainWindow.focus(); });
- "did-finish-load"のタイミングで、BrowserWindowインスタンスにイベントを投げています。
- ドロップされたファイルの情報をクリアしておかないと、BrowserWindowを再読込した時に、また読み込まれてしまいますよ。
// main window event mainWindow.webContents.on('did-finish-load', () => { // receive drop file to app icon event if (_launchArgs && _launchArgs.filePath) { const filePath = _launchArgs.filePath; _launchArgs = null; // リロードされた時のために変数の中身を消しておく mainWindow.webContents.send('dropTextFile', filePath); } // show mainWindow.show(); mainWindow.focus(); });
renderer processに渡されたファイルを読み込む
- renderer processで渡されたファイルを読み込みます。
- 起動済みのアプリにイベントを投げられた場合、起動前のアプリにイベントを投げられた場合で、どちらも同じインターフェイスで処理出来ると良いですね。
- ここではipcRendererを使って処理しています。
// dropTextFile event ipcRenderer.on('dropTextFile', (event, filePath: string) => { fs.readFile(filePath, 'utf-8', (err: Error, data: string) => { if (err) { MessageService.error('テキストファイルを読み込めませんでした。', err); return; } const win = remote.getCurrentWindow(); win.focus(); $scope.yinput.source = data; $timeout(() => { $scope.$apply(); }); }); });
- アプリアイコンにファイルを落とした時は、Electronアプリにはフォーカスが当たっていません。
- このままでは、Electronアプリの画面のデータが更新されない、などの問題が起きます。
- focus()を実行して、ウィンドウにフォーカスを当てましょう。
var remote = require('electron').remote; const win = remote.getCurrentWindow(); win.focus();
makeSingleInstance
- アプリが複数立ち上がっていると、うまく動かない場合があると思います。
- (そういう記述を見た。いちいち検証しないよ。私のアプリではシングルインスタンスを有効にしないしね。)
- makeSingleInstance()を実行して、アプリを複数起動できないようにして対処します。
app.makeSingleInstance((argv: string[], workingDirectory: string) => {});
- 最新のバージョンでは、makeSingleInstance()は廃止され、代わりにrequestSingleInstanceLock()を使うことになりました。
- https://electronjs.org/docs/api/app
const locked = app.requestSingleInstanceLock()
おわり
おわりだよ