From 3029cba01f4e438508c8df91a0ef549243908d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedikt=20R=C3=B6tsch?= Date: Mon, 5 Nov 2018 03:03:52 +0100 Subject: [PATCH] Support global shortcuts that trigger input events (PR #698, Fixes #15) This adds a new flag, allowing the user to define global shortcuts that trigger input events within the main window. That way, I could easily wrap SoundCloud and Deezer to create a native app which reacts on my keyboard media buttons. --- app/src/main.js | 13 +++- docs/api.md | 128 ++++++++++++++++++++++++++++++++++++- src/build/buildApp.js | 1 + src/cli.js | 4 ++ src/options/optionsMain.js | 7 ++ 5 files changed, 149 insertions(+), 4 deletions(-) diff --git a/app/src/main.js b/app/src/main.js index abc6595..7251146 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -1,7 +1,7 @@ import 'source-map-support/register'; import fs from 'fs'; import path from 'path'; -import { app, crashReporter } from 'electron'; +import { app, crashReporter, globalShortcut } from 'electron'; import electronDownload from 'electron-dl'; import createLoginWindow from './components/login/loginWindow'; @@ -122,6 +122,17 @@ if (appArgs.crashReporter) { app.on('ready', () => { mainWindow = createMainWindow(appArgs, app.quit, setDockBadge); createTrayIcon(appArgs, mainWindow); + + // Register global shortcuts + if (appArgs.globalShortcuts) { + appArgs.globalShortcuts.forEach((shortcut) => { + globalShortcut.register(shortcut.key, () => { + shortcut.inputEvents.forEach((inputEvent) => { + mainWindow.webContents.sendInputEvent(inputEvent); + }); + }); + }); + } }); app.on('new-window-for-tab', () => { diff --git a/docs/api.md b/docs/api.md index 7e85777..ffd460f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2,6 +2,7 @@ ## Table of Contents +- [Table of Contents](#table-of-contents) - [Command Line](#command-line) - [Target Url](#target-url) - [[dest]](#dest) @@ -10,10 +11,17 @@ - [[name]](#name) - [[platform]](#platform) - [[arch]](#arch) + - [[app-copyright]](#app-copyright) + - [[app-version]](#app-version) + - [[build-version]](#build-version) - [[electron-version]](#electron-version) - [[no-overwrite]](#no-overwrite) - [[conceal]](#conceal) - [[icon]](#icon) + - [Packaging for Windows](#packaging-for-windows) + - [Packaging for Linux](#packaging-for-linux) + - [Packaging for macOS](#packaging-for-macos) + - [Manually Converting `.icns`](#manually-converting-icns) - [[counter]](#counter) - [[bounce]](#bounce) - [[width]](#width) @@ -29,6 +37,9 @@ - [[user-agent]](#user-agent) - [[honest]](#honest) - [[ignore-certificate]](#ignore-certificate) + - [[disable-gpu]](#disable-gpu) + - [[ignore-gpu-blacklist]](#ignore-gpu-blacklist) + - [[enable-es3-apis]](#enable-es3-apis) - [[insecure]](#insecure) - [[internal-urls]](#internal-urls) - [[flash]](#flash) @@ -42,15 +53,20 @@ - [[verbose]](#verbose) - [[disable-context-menu]](#disable-context-menu) - [[disable-dev-tools]](#disable-dev-tools) - - [[zoom]](#zoom) - [[crash-reporter]](#crash-reporter) + - [[zoom]](#zoom) - [[single-instance]](#single-instance) - [[tray]](#tray) - [[basic-auth-username]](#basic-auth-username) - - [[basic-auth-password]](#basic-auth-username) + - [[processEnvs]](#processenvs) + - [[file-download-options]](#file-download-options) - [[always-on-top]](#always-on-top) - - [[disable-gpu]](#disable-gpu) + - [[global-shortcuts]](#global-shortcuts) - [Programmatic API](#programmatic-api) + - [Addition packaging options for Windows](#addition-packaging-options-for-windows) + - [[version-string]](#version-string) + - [[win32metadata]](#win32metadata) + - [Programmatic API](#programmatic-api) ## Command Line @@ -553,6 +569,112 @@ nativefier --file-download-options '{"saveAs": true}' Enable always on top for the packaged application. +#### [global-shortcuts] + +``` +--global-shortcuts shortcuts.json +``` + +Register global shortcuts which will trigger input events like key presses or pointer events in the application. + +You may define multiple global shortcuts which can trigger a series of input events. It has the following structure: + + +```js +[ + { + // Key is passed as first argument to globalShortcut.register + "key": "CommandOrControl+Shift+Z", + // The input events exactly match the event config in Electron for contents.sendInputEvent(event) + "inputEvents": [ + { + // Available event types: mouseDown, mouseUp, mouseEnter, mouseLeave, contextMenu, mouseWheel, mouseMove, keyDown, keyUp or char + "type": "keyDown", + // Further config depends on your event type. See docs at: https://github.com/electron/electron/blob/master/docs/api/web-contents.md#contentssendinputeventevent + "keyCode": "Space" + } + ] + } +] +``` + +**Important note for using modifier keys:** + +If you want to trigger key events which include a modifier (Ctrl, Shift,...), you need to keyDown the modifier key first, then keyDown the actual key _including_ the modifier key as modifier property and then keyUp both keys again. No idea what this means? See the example for `MediaPreviousTrack` below! + +**For more details, please see the Electron documentation:** + +* List of available keys: https://github.com/electron/electron/blob/master/docs/api/accelerator.md +* Details about how to create input event objects: https://github.com/electron/electron/blob/master/docs/api/web-contents.md#contentssendinputeventevent + +Example `shortcuts.json` for `https://deezer.com` & `https://soundcloud.com` to get your play/pause/previous/next media keys working: +```json +[ + { + "key": "MediaPlayPause", + "inputEvents": [ + { + "type": "keyDown", + "keyCode": "Space" + } + ] + }, + { + "key": "MediaPreviousTrack", + "inputEvents": [ + { + "type": "keyDown", + "keyCode": "Shift" + }, + { + "type": "keyDown", + "keyCode": "Left", + "modifiers": [ + "shift" + ] + }, + { + "type": "keyUp", + "keyCode": "Left", + "modifiers": [ + "shift" + ] + }, + { + "type": "keyUp", + "keyCode": "Shift" + } + ] + }, + { + "key": "MediaNextTrack", + "inputEvents": [ + { + "type": "keyDown", + "keyCode": "Shift" + }, + { + "type": "keyDown", + "keyCode": "Right", + "modifiers": [ + "shift" + ] + }, + { + "type": "keyUp", + "keyCode": "Right", + "modifiers": [ + "shift" + ] + }, + { + "type": "keyUp", + "keyCode": "Shift" + } + ] + } +] +``` ## Programmatic API diff --git a/src/build/buildApp.js b/src/build/buildApp.js index 4478eec..3931266 100644 --- a/src/build/buildApp.js +++ b/src/build/buildApp.js @@ -56,6 +56,7 @@ function selectAppArgs(options) { basicAuthPassword: options.basicAuthPassword, alwaysOnTop: options.alwaysOnTop, titleBarStyle: options.titleBarStyle, + globalShortcuts: options.globalShortcuts, }; } diff --git a/src/cli.js b/src/cli.js index adef4f2..5d9954f 100755 --- a/src/cli.js +++ b/src/cli.js @@ -199,6 +199,10 @@ if (require.main === module) { '--title-bar-style ', "(macOS only) set title bar style ('hidden', 'hiddenInset'). Consider injecting custom CSS (via --inject) for better integration.", ) + .option( + '--global-shortcuts ', + 'JSON file with global shortcut configuration. See https://github.com/jiahaog/nativefier/blob/master/docs/api.md#global-shortcuts', + ) .parse(process.argv); if (!process.argv.slice(2).length) { diff --git a/src/options/optionsMain.js b/src/options/optionsMain.js index 515efc1..59422a0 100644 --- a/src/options/optionsMain.js +++ b/src/options/optionsMain.js @@ -1,3 +1,4 @@ +import fs from 'fs'; import log from 'loglevel'; import inferOs from '../infer/inferOs'; @@ -74,6 +75,7 @@ export default function(inpOptions) { basicAuthPassword: inpOptions.basicAuthPassword || null, alwaysOnTop: inpOptions.alwaysOnTop || false, titleBarStyle: inpOptions.titleBarStyle || null, + globalShortcuts: inpOptions.globalShortcuts || null, }; if (options.verbose) { @@ -117,5 +119,10 @@ export default function(inpOptions) { options.y = inpOptions.y; } + if (options.globalShortcuts) { + const globalShortcutsFileContent = fs.readFileSync(options.globalShortcuts); + options.globalShortcuts = JSON.parse(globalShortcutsFileContent); + } + return asyncConfig(options); }