Skip to content

Instantly share code, notes, and snippets.

@mrfabbri
Created February 4, 2015 23:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrfabbri/3a32baf9c5eb5a446a48 to your computer and use it in GitHub Desktop.
Save mrfabbri/3a32baf9c5eb5a446a48 to your computer and use it in GitHub Desktop.
App wide shortcuts in NW.js in "userland" [PoC]
"use strict";
var gui = require("nw.gui");
var AppShortcut = require("./appshortcut")(gui).AppShortcut;
var keycode = require('keycode');
var win = gui.Window.get();
if (process.platform === "darwin") {
var nativeMenuBar = new gui.Menu({type: "menubar"});
nativeMenuBar.createMacBuiltin("My App");
win.menu = nativeMenuBar;
}
function start() {
var shortcut = new AppShortcut({
key: "c",
onKeyDown: function (event) {
console.log("keydown:", event);
document.getElementById("transcript").innerHTML +=
"<div> keydown: " + keycode(event.keyCode) + "</div>";
},
onKeyUp: function (event) {
console.log("keyup:", event);
document.getElementById("transcript").innerHTML +=
"<div> keyup: " + keycode(event.keyCode) + "</div>";
}
});
shortcut.start();
new AppShortcut({
key: "o",
onKeyDown: gui.Window.open.bind(null, "nw://blank")
}).start();
};
window.onload = start;
/**
* NW.js application wide key events module.
*
* USAGE:
* ````JavaScript
* var AppShortcut = require("./appshortcut")(gui).AppShortcut;
* var shortcut = new AppShortcut({ key: "cmd+c", onKeyDown: someAction });
* ````
*
* NOTE: Must be required at the beginning of the main NW.js app file, as it
* instruments the Window API object to listen to key events from new windows.
*
* @module appshortcut
*/
"use strict";
var keycode = require("keycode");
// NW.js native gui API
var gui;
/* Maps holding the keyboard shortcuts actions, per event type. */
var shortcuts = {
keydown: new Map(),
keyup: new Map(),
keypress: new Map()
};
/* Hash a keyboard shortcut, keycode+modifiers */
function _keyHash(options) {
return options.keyCode + "" +
(options.altKey ? "alt" : "") +
(options.ctrlKey ? "ctrl" : "") +
(options.metaKey ? "meta" : "") +
(options.shiftKey ? "shift" : "");
}
/* trigger matching shortcut on keydown */
function _keyDown(event) {
if (!event.repeat) { // discard repeated keydown
var shortcut = shortcuts.keydown.get(_keyHash(event));
if (shortcut && shortcut.isActive()) { shortcut.onKeyDown(event); }
}
}
/* trigger matching shortcut on keyup */
function _keyUp(event) {
if (!event.repeat) { // discard repeated keypress
var shortcut = shortcuts.keyup.get(_keyHash(event));
if (shortcut && shortcut.isActive()) { shortcut.onKeyUp(event); }
}
}
/* trigger matching shortcut on keypressed */
function _keyPressed(event) {
var shortcut = shortcuts.keydown.get(_keyHash(event));
if (shortcut && shortcut.isActive()) { shortcut.onKeyPressed(event); }
}
function _addListeners() {
// add listeners to the current window
(function () {
var _win = gui.Window.get();
_win.window.addEventListener("keydown", _keyDown);
_win.window.addEventListener("keyup", _keyUp);
_win.window.addEventListener("keypress", _keyPressed);
})();
// add listeners to all future windows
var _open = gui.Window.open;
gui.Window.open = function open(url, options) {
var _win = _open(url, options);
setTimeout(function () {
// otherwise nwjs is "Unable to get render view in GetWindowObject"
_win.window.addEventListener("keydown", _keyDown);
_win.window.addEventListener("keyup", _keyUp);
_win.window.addEventListener("keypress", _keyPressed);
}, 10);
return _win;
};
var _window_open = window.open;
window.open = function open() {
var _win = _window_open.apply(window, arguments);
_win.addEventListener("keydown", _keyDown);
_win.addEventListener("keyup", _keyUp);
_win.addEventListener("keypress", _keyPressed);
return _win;
}
}
/* Is `key` a string representation of a modifier? */
function _isModifier(key) {
return /alt|ctrl|meta|shift/.test(key);
}
/* Is `keyCode` a normal a key? */
function _isKey(keyCode) {
return (65 <= keyCode && keyCode <= 90) || // a..z
(48 <= keyCode && keyCode <= 58); // 0..9
}
function _parseKeyCombination(combination) {
var keys = combination.toLowerCase().split("+");
var res = {};
keys.forEach(function (key){
var keyCode = keycode(key);
if (_isModifier(key)) {
res[key + "Key"] = true;
}
if (_isKey(keyCode)) {
res.keyCode = keyCode;
}
});
if (!res.keyCode) {
throw new Error(combination + " is not a valid key");
}
return res;
}
/**
* Create an application wide shortcut. The `options` argument can
* have the folling attributes:
* - `key`: single key or combination that activates the shortcut.
* - `onKeyDown`: event handler called whenever a `keyup` event for the given
* `key` combination is generated in any window of the application.
* - `onKeyUp`: event handler called whenever a `keypress` event for the given
* `key` combination is generated in any window of the application.
* - `onKeyPressed`: event handler called whenever a `keypress` event for the given
* `key` combination is generated in any window of the application.
* @constructor
* @param {Object} options - shortcut configuration options.
* @returns {AppShortcut} shortcut - the (active) application wide shortcut.
* @throws {MissingKeyException} - if no `key` attribute is present in the `options`
* @throws {TypeError} - if any of the handlers is not a `function`
* argument.
*/
function AppShortcut(options) {
if (!options.key) { throw new Error("missing required argument attribute key"); }
this.key = options.key;
var parsed = _parseKeyCombination(options.key);
for(var k in Object.keys(parsed)) { this[k] = parsed[k]; }
var keyHash = _keyHash(parsed);
if (typeof options.onKeyDown === "function") {
this.onKeyDown = options.onKeyDown;
shortcuts.keydown.set(keyHash, this);
}
if (typeof options.onKeyUp === "function") {
this.onKeyUp = options.onKeyUp;
shortcuts.keyup.set(keyHash, this);
}
if (typeof options.onKeyPress === "function") {
this.onKeyPress = options.onKeyPress;
shortcuts.keypress.set(keyHash, this);
}
this.active = false;
}
/**
* Returns the status, `true` for active, `false` for inactive of the application
* wide shortcut.
*/
AppShortcut.prototype.isActive = function isActive() {
return this.active;
};
/**
* Activate the application wide shortcut.
*/
AppShortcut.prototype.start = function start() {
this.active = true;
};
/**
* Deactivate the application wide shortcut.
*/
AppShortcut.prototype.stop = function stop() {
this.active = false;
};
module.exports = function (nwgui) {
gui = nwgui;
_addListeners();
return {
AppShortcut: AppShortcut
};
};
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" charset="utf-8" src="./app.js"></script>
</head>
<body>
<div>Press "c" to trigger shortcut.</div>
<div>Press "o" to open a new window (the shortcut will be active also in the new window).</div>
<div id="transcript"></div>
</body>
</html>
{
"name": "appshortcut",
"main": "index.html",
"description": "appshortcut test app",
"version": "0.0.1",
"window": {
"show": true,
"frame": true,
"toolbar": false,
"width": 600,
"height": 400,
"position": "center"
},
"dependencies": {
"keycode": "~1.0.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment