Jitsi Push-to-Talk

This commit is contained in:
Andrew Morgan 2018-11-15 19:01:02 +01:00
parent 588030141b
commit 6e71fa5902
5 changed files with 178 additions and 9 deletions

View File

@ -8,7 +8,27 @@
"dependencies": { "dependencies": {
"auto-launch": "^5.0.1", "auto-launch": "^5.0.1",
"electron-window-state": "^4.1.0", "electron-window-state": "^4.1.0",
"iohook": "^0.2.4",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"png-to-ico": "^1.0.2" "png-to-ico": "^1.0.2"
},
"iohook": {
"targets": [
"node-64",
"electron-64"
],
"platforms": [
"win32",
"darwin",
"linux"
],
"arches": [
"x64",
"ia32"
]
},
"cmake-js": {
"runtime": "electron",
"runtimeVersion": "3.0.5"
} }
} }

View File

@ -27,6 +27,7 @@ const argv = require('minimist')(process.argv);
const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol} = require('electron'); const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol} = require('electron');
const AutoLaunch = require('auto-launch'); const AutoLaunch = require('auto-launch');
const path = require('path'); const path = require('path');
const ioHook = require('iohook');
const tray = require('./tray'); const tray = require('./tray');
const vectorMenu = require('./vectormenu'); const vectorMenu = require('./vectormenu');
@ -340,6 +341,13 @@ app.on('ready', () => {
return false; return false;
} }
}); });
mainWindow.on('blur', () => {
// Stop recording keypresses if Riot loses focus
// Used for Push-To-Talk, keypress recording only triggered when setting
// a global shortcut in Settings
mainWindow.webContents.send('window-blurred');
stopListeningKeys();
});
if (process.platform === 'win32') { if (process.platform === 'win32') {
// Handle forward/backward mouse buttons in Windows // Handle forward/backward mouse buttons in Windows
@ -382,6 +390,92 @@ app.on('second-instance', (ev, commandLine, workingDirectory) => {
} }
}); });
// Counter for keybindings we have registered
let ioHookTasks = 0;
// Limit for amount of keybindings that can be
// registered at once.
const keybindingRegistrationLimit = 1;
// Fires when a global keybinding is being registered
ipcMain.on('register-keybinding', function(ev, keybinding) {
// Prevent registering more than the defined limit
if (ioHookTasks >= keybindingRegistrationLimit) {
ioHookTasks = keybindingRegistrationLimit;
return;
}
// Start listening for global keyboard shortcuts
if (ioHookTasks <= 0) {
ioHookTasks = 0;
ioHook.start();
}
ioHookTasks++;
ioHook.registerShortcut(keybinding.code, () => {
ev.sender.send('keybinding-pressed', keybinding.name);
}, () => {
ev.sender.send('keybinding-released', keybinding.name);
});
});
// Fires when a global keybinding is being unregistered
ipcMain.on('unregister-keybinding', function(ev, keybindingCode) {
// Stop listening for global keyboard shortcuts if we're
// unregistering the last one
if (ioHookTasks <= 1) {
ioHook.stop();
}
ioHookTasks--;
ioHook.unregisterShortcutByKeys(keybindingCode);
});
// Tell renderer process what key was pressed
// iohook has its own encoding for keys, so we can't just use a
// listener in the renderer process to register iohook shortcuts
let renderProcessID = null;
const reportKeyEvent = function(keyEvent) {
// "this" is the renderer process because we call this method with .bind()
renderProcessID.sender.send('keypress', {
keydown: keyEvent.type == 'keydown',
keycode: keyEvent.keycode,
});
};
// Fires when listening on all keys
// !!Security note: Ensure iohook is only allowed to listen to keybindings
// when the browser window is in focus, else an XSS could lead to keylogging
// Currently, this is achieved by leveraging browserWindow to act on focus loss
ipcMain.on('start-listening-keys', function(ev, keybindingCode) {
// Start recording keypresses
if (ioHookTasks <= 0) {
ioHookTasks = 0;
ioHook.start();
}
ioHookTasks++;
renderProcessID = ev;
ioHook.on('keydown', reportKeyEvent);
ioHook.on('keyup', reportKeyEvent);
});
const stopListeningKeys = () => {
// Stop recording keypresses
ioHook.off('keydown', reportKeyEvent);
ioHook.off('keyup', reportKeyEvent);
};
ipcMain.on('stop-listening-keys', () => {
if (ioHookTasks <= 1) {
ioHookTasks = 1;
ioHook.stop();
}
ioHookTasks--;
stopListeningKeys();
});
// Set the App User Model ID to match what the squirrel // Set the App User Model ID to match what the squirrel
// installer uses for the shortcut icon. // installer uses for the shortcut icon.
// This makes notifications work on windows 8.1 (and is // This makes notifications work on windows 8.1 (and is

28
package-lock.json generated
View File

@ -6627,12 +6627,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -6647,17 +6649,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -6774,7 +6779,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -6786,6 +6792,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -6800,6 +6807,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -6807,12 +6815,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -6831,6 +6841,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -6911,7 +6922,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -6923,6 +6935,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -7044,6 +7057,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",

View File

@ -17,5 +17,6 @@
"Explore rooms": "Explore rooms", "Explore rooms": "Explore rooms",
"Room Directory": "Room Directory", "Room Directory": "Room Directory",
"Search the room directory": "Search the room directory", "Search the room directory": "Search the room directory",
"Get started with some tips from Riot Bot!": "Get started with some tips from Riot Bot!" "Get started with some tips from Riot Bot!": "Get started with some tips from Riot Bot!",
"Push-to-Talk": "Push-to-Talk"
} }

View File

@ -25,6 +25,7 @@ import Promise from 'bluebird';
import rageshake from 'matrix-react-sdk/lib/rageshake/rageshake'; import rageshake from 'matrix-react-sdk/lib/rageshake/rageshake';
const ipcRenderer = window.ipcRenderer; const ipcRenderer = window.ipcRenderer;
var globalKeybindings = {};
function platformFriendlyName(): string { function platformFriendlyName(): string {
// used to use window.process but the same info is available here // used to use window.process but the same info is available here
@ -99,6 +100,25 @@ export default class ElectronPlatform extends VectorBasePlatform {
this.startUpdateCheck = this.startUpdateCheck.bind(this); this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this); this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
ipcRenderer.on('keybinding-pressed', (event, keybindName) => {
// Prevent holding down a shortcut meaning multiple presses
if (globalKeybindings[keybindName].pressed) {
return;
}
globalKeybindings[keybindName].pressed = true;
// Run the callback
globalKeybindings[keybindName].callback();
});
ipcRenderer.on('keybinding-released', (event, keybindName) => {
// Keybinding is no longer pressed
globalKeybindings[keybindName].pressed = false;
// Run the callback
globalKeybindings[keybindName].releaseCallback();
});
} }
async onUpdateDownloaded(ev, updateInfo) { async onUpdateDownloaded(ev, updateInfo) {
@ -198,6 +218,26 @@ export default class ElectronPlatform extends VectorBasePlatform {
ipcRenderer.send('check_updates'); ipcRenderer.send('check_updates');
} }
addGlobalKeybinding(keybindName: string, keybindCode: string, callback: () => void, releaseCallback: () => void) {
// Add a keybinding that works even when the app is minimized
const keybinding = {name: keybindName, code: keybindCode};
ipcRenderer.send('register-keybinding', keybinding);
globalKeybindings[keybindName] = {callback, releaseCallback};
console.warn("Adding global keybinding:", keybindName, "with code:", keybindCode);
}
removeGlobalKeybinding(keybindName: string, keybindCode: string) {
// Unbind a global keybinding
ipcRenderer.send('unregister-keybinding', keybindCode);
// Remove the callback
delete globalKeybindings[keybindName];
console.warn("Removing global keybinding:", keybindName);
}
installUpdate() { installUpdate() {
// IPC to the main process to install the update, since quitAndInstall // IPC to the main process to install the update, since quitAndInstall
// doesn't fire the before-quit event so the main process needs to know // doesn't fire the before-quit event so the main process needs to know
@ -232,7 +272,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
const ipcCallId = ++this._nextIpcCallId; const ipcCallId = ++this._nextIpcCallId;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._pendingIpcCalls[ipcCallId] = {resolve, reject}; this._pendingIpcCalls[ipcCallId] = {resolve, reject};
window.ipcRenderer.send('ipcCall', {id: ipcCallId, name, args}); ipcRenderer.send('ipcCall', {id: ipcCallId, name, args});
// Maybe add a timeout to these? Probably not necessary. // Maybe add a timeout to these? Probably not necessary.
}); });
} }