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": {
"auto-launch": "^5.0.1",
"electron-window-state": "^4.1.0",
"iohook": "^0.2.4",
"minimist": "^1.2.0",
"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 AutoLaunch = require('auto-launch');
const path = require('path');
const ioHook = require('iohook');
const tray = require('./tray');
const vectorMenu = require('./vectormenu');
@ -340,6 +341,13 @@ app.on('ready', () => {
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') {
// 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
// installer uses for the shortcut icon.
// This makes notifications work on windows 8.1 (and is

28
package-lock.json generated
View File

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

View File

@ -17,5 +17,6 @@
"Explore rooms": "Explore rooms",
"Room Directory": "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';
const ipcRenderer = window.ipcRenderer;
var globalKeybindings = {};
function platformFriendlyName(): string {
// 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.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) {
@ -198,6 +218,26 @@ export default class ElectronPlatform extends VectorBasePlatform {
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() {
// 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
@ -232,7 +272,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
const ipcCallId = ++this._nextIpcCallId;
return new Promise((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.
});
}