Skip to content

Commit

Permalink
Merge pull request #118 from anoadragon453/anoa/shortcut_up
Browse files Browse the repository at this point in the history
Add ability to listen for when a keyboard shortcut has been released
  • Loading branch information
Djiit authored Nov 2, 2018
2 parents c4779bd + 705afc3 commit bcd5677
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 7 deletions.
14 changes: 12 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,26 @@ You can register global shortcuts.
When a shortcut is caught, keyup/keydown events still emit events. It means, that if you register a keyup AND shortcut for `ALT+T`, both events will be emited.
:::

### registerShortcut(keys, callback)
### registerShortcut(keys, callback, releaseCallback?)

In next example we register CTRL+F7 shortcut (in MacOS, for other OS, keycodes can be some different).
In the next example we register CTRL+F7 shortcut (in MacOS. For other OSes, the keycodes could be different).

```js
const id = ioHook.registerShortcut([29, 65], (keys) => {
console.log('Shortcut called with keys:', keys)
});
```

We can also specify a callback to run when our shortcut has been released by specifying a third function argument.

```js
const id = ioHook.registerShortcut([29, 65], (keys) => {
console.log('Shortcut called with keys:', keys)
}, (keys) => {
console.log('Shortcut has been released!')
});
```

### unregisterShortcut(shortcutId)

You can unregister shortcut by using shortcutId returned by `registerShortcut()`.
Expand Down
5 changes: 3 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ declare class IOHook extends EventEmitter {
/**
* Register global shortcut. When all keys in keys array pressed, callback will be called
* @param {Array<string|number>} keys Array of keycodes
* @param {Function} callback Callback for call when shortcut pressed
* @param {Function} callback Callback for when shortcut pressed
* @param {Function} [releaseCallback] Callback for when shortcut released
* @return {number} ShortcutId for unregister
*/
registerShortcut(keys: Array<string|number>, callback: Function): number
registerShortcut(keys: Array<string|number>, callback: Function, releaseCallback?: Function): number

/**
* Unregister shortcut by ShortcutId
Expand Down
60 changes: 57 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class IOHook extends EventEmitter {
super();
this.active = false;
this.shortcuts = [];
this.activatedShortcuts = [];

this.lastKeydownShift = false;
this.lastKeydownAlt = false;
Expand Down Expand Up @@ -65,17 +66,19 @@ class IOHook extends EventEmitter {
/**
* Register global shortcut. When all keys in keys array pressed, callback will be called
* @param {Array} keys Array of keycodes
* @param {Function} callback Callback for call when shortcut pressed
* @param {Function} callback Callback for when shortcut pressed
* @param {Function} [releaseCallback] Callback for when shortcut has been released
* @return {number} ShortcutId for unregister
*/
registerShortcut(keys, callback) {
registerShortcut(keys, callback, releaseCallback) {
let shortcut = {};
let shortcutId = Date.now() + Math.random();
keys.forEach(keyCode => {
shortcut[keyCode] = false;
})
shortcut.id = shortcutId;
shortcut.callback = callback;
shortcut.releaseCallback = releaseCallback;
this.shortcuts.push(shortcut);
return shortcutId;
}
Expand Down Expand Up @@ -254,31 +257,82 @@ class IOHook extends EventEmitter {
if (this.active === false) {
return;
}

// Keep track of shortcuts that are currently active
let activatedShortcuts = this.activatedShortcuts;

if (event.type === 'keydown') {
this.shortcuts.forEach(shortcut => {
if (shortcut[event.keycode] !== undefined) {
// Mark this key as currently being pressed
shortcut[event.keycode] = true;

let keysTmpArray = [];
let callme = true;

// Iterate through each keyboard key in this shortcut
Object.keys(shortcut).forEach(key => {
if (key === 'callback' || key === 'id') return;
if (key === 'callback' || key === 'releaseCallback' || key === 'id') return;

// If one of the keys aren't pressed...
if (shortcut[key] === false) {
// Don't call the callback and empty our temp tracking array
callme = false;
keysTmpArray.splice(0, keysTmpArray.length);

return;
}

// Otherwise, this key is being pressed.
// Add it to the array of keyboard keys we will send as an argument
// to our callback
keysTmpArray.push(key);
});
if (callme) {
shortcut.callback(keysTmpArray);

// Add this shortcut from our activate shortcuts array if not
// already activated
if (activatedShortcuts.indexOf(shortcut) === -1) {
activatedShortcuts.push(shortcut);
}
}
}
});
} else if (event.type === 'keyup') {
// Mark this key as currently not being pressed in all of our shortcuts
this.shortcuts.forEach(shortcut => {
if (shortcut[event.keycode] !== undefined) shortcut[event.keycode] = false;
});

// Check if any of our currently pressed shortcuts have been released
// "released" means that all of the keys that the shortcut defines are no
// longer being pressed
this.activatedShortcuts.forEach(shortcut => {
if (shortcut[event.keycode] === undefined) return;

let shortcutReleased = true;
let keysTmpArray = [];
Object.keys(shortcut).forEach(key => {
if (key === 'callback' || key === 'releaseCallback' || key === 'id') return;
keysTmpArray.push(key)

// If any key is true, and thus still pressed, the shortcut is still
// being held
if (shortcut[key]) {
shortcutReleased = false;
}
});

if (shortcutReleased) {
// Call the released function handler
shortcut.releaseCallback(keysTmpArray);

// Remove this shortcut from our activate shortcuts array
const index = this.activatedShortcuts.indexOf(shortcut);
if (index !== -1) this.activatedShortcuts.splice(index, 1);
}
});
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions test/specs/keyboard.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,22 @@ describe('Keyboard events', () => {
robot.keyToggle('command', 'up');
}, 50);
});

it('runs a callback when a shortcut has been released', (done) => {
expect.assertions(2);

let shortcut = [42, 30];

ioHook.registerShortcut(shortcut, keys => {
expect(shortcut.sort()).toEqual(keys.sort());
}, keys => {
expect(shortcut.sort()).toEqual(keys.sort());
});

setTimeout(() => { // Make sure ioHook starts before anything gets typed
robot.keyToggle('shift', 'down');
robot.keyTap('a');
robot.keyToggle('shift', 'up');
}, 50);
});
});

0 comments on commit bcd5677

Please sign in to comment.