fix(KbShortcuts): remove listeners on leave.

Currently we add keyboard listeners on conference join but never remove them. In the cases where we have multiple join events during a call (visitors promotion, breakout rooms), there are multiple keyboard handlers added and the shortcuts are executed multiple times on a single press.
pull/15072/merge jitsi-meet_9763
Hristo Terezov 2 months ago
parent 39c9c24810
commit d9a0423687
  1. 2
      conference.js
  2. 1
      react/features/keyboard-shortcuts/actions.native.ts
  3. 142
      react/features/keyboard-shortcuts/actions.web.ts
  4. 13
      react/features/keyboard-shortcuts/middleware.ts
  5. 1
      tsconfig.native.json

@ -140,7 +140,6 @@ import { openLeaveReasonDialog } from './react/features/conference/actions.web';
import { showDesktopPicker } from './react/features/desktop-picker/actions';
import { appendSuffix } from './react/features/display-name/functions';
import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedback/actions';
import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
import {
@ -2016,7 +2015,6 @@ export default {
APP.UI.initConference();
dispatch(initKeyboardShortcuts());
dispatch(conferenceJoined(room));
const jwt = APP.store.getState()['features/base/jwt'];

@ -1 +0,0 @@
export * from './actions.any';

@ -8,77 +8,101 @@ import { openSettingsDialog } from '../settings/actions.web';
import { SETTINGS_TABS } from '../settings/constants';
import { iAmVisitor } from '../visitors/functions';
import { registerShortcut } from './actions.any';
import { registerShortcut, unregisterShortcut } from './actions.any';
import { areKeyboardShortcutsEnabled, getKeyboardShortcuts } from './functions';
import logger from './logger';
import { getKeyboardKey, getPriorityFocusedElement } from './utils';
export * from './actions.any';
type KeyHandler = ((e: KeyboardEvent) => void) | undefined;
let keyDownHandler: KeyHandler;
let keyUpHandler: KeyHandler;
/**
* Initialise global shortcuts.
* Global shortcuts are shortcuts for features that don't have a button or
* link associated with the action. In other words they represent actions
* triggered _only_ with a shortcut.
*
* @returns {Function}
* @param {Function} dispatch - The redux dispatch function.
* @returns {void}
*/
const initGlobalKeyboardShortcuts = () =>
(dispatch: IStore['dispatch']) => {
batch(() => {
dispatch(registerShortcut({
character: '?',
helpDescription: 'keyboardShortcuts.toggleShortcuts',
handler: () => {
sendAnalytics(createShortcutEvent('help'));
dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
}
}));
function initGlobalKeyboardShortcuts(dispatch: IStore['dispatch']) {
batch(() => {
dispatch(registerShortcut({
character: '?',
helpDescription: 'keyboardShortcuts.toggleShortcuts',
handler: () => {
sendAnalytics(createShortcutEvent('help'));
dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
}
}));
// register SPACE shortcut in two steps to insure visibility of help message
dispatch(registerShortcut({
character: ' ',
helpCharacter: 'SPACE',
helpDescription: 'keyboardShortcuts.pushToTalk',
handler: () => {
// Handled directly on the global handler.
}
}));
// register SPACE shortcut in two steps to insure visibility of help message
dispatch(registerShortcut({
character: ' ',
helpCharacter: 'SPACE',
helpDescription: 'keyboardShortcuts.pushToTalk',
handler: () => {
// Handled directly on the global handler.
}
}));
dispatch(registerShortcut({
character: '0',
helpDescription: 'keyboardShortcuts.focusLocal',
handler: () => {
dispatch(clickOnVideo(0));
}
}));
for (let num = 1; num < 10; num++) {
dispatch(registerShortcut({
character: '0',
helpDescription: 'keyboardShortcuts.focusLocal',
character: `${num}`,
// only show help hint for the first shortcut
helpCharacter: num === 1 ? '1-9' : undefined,
helpDescription: num === 1 ? 'keyboardShortcuts.focusRemote' : undefined,
handler: () => {
dispatch(clickOnVideo(0));
dispatch(clickOnVideo(num));
}
}));
}
});
}
Array(9).fill(1)
.forEach((_, index) => {
const num = index + 1;
dispatch(registerShortcut({
character: `${num}`,
// only show help hint for the first shortcut
helpCharacter: num === 1 ? '1-9' : undefined,
helpDescription: num === 1 ? 'keyboardShortcuts.focusRemote' : undefined,
handler: () => {
dispatch(clickOnVideo(num));
}
}));
});
});
};
/**
* Unregisters global shortcuts.
*
* @param {Function} dispatch - The redux dispatch function.
* @returns {void}
*/
function unregisterGlobalKeyboardShortcuts(dispatch: IStore['dispatch']) {
batch(() => {
dispatch(unregisterShortcut('?'));
// register SPACE shortcut in two steps to insure visibility of help message
dispatch(unregisterShortcut(' '));
dispatch(unregisterShortcut('0'));
for (let num = 1; num < 10; num++) {
dispatch(unregisterShortcut(`${num}`));
}
});
}
/**
* Initializes keyboard shortcuts.
*
* @returns {Function}
*/
export const initKeyboardShortcuts = () =>
(dispatch: IStore['dispatch'], getState: IStore['getState']) => {
dispatch(initGlobalKeyboardShortcuts());
export function initKeyboardShortcuts() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
initGlobalKeyboardShortcuts(dispatch);
const pttDelay = 50;
let pttTimeout: number | undefined;
@ -91,7 +115,7 @@ export const initKeyboardShortcuts = () =>
// chaining at all.
let mutePromise = Promise.resolve();
window.addEventListener('keyup', (e: KeyboardEvent) => {
keyUpHandler = (e: KeyboardEvent) => {
const state = getState();
const enabled = areKeyboardShortcutsEnabled(state);
const shortcuts = getKeyboardShortcuts(state);
@ -115,9 +139,9 @@ export const initKeyboardShortcuts = () =>
if (shortcuts.has(key)) {
shortcuts.get(key)?.handler(e);
}
});
};
window.addEventListener('keydown', (e: KeyboardEvent) => {
keyDownHandler = (e: KeyboardEvent) => {
const state = getState();
const enabled = areKeyboardShortcutsEnabled(state);
@ -137,5 +161,25 @@ export const initKeyboardShortcuts = () =>
} else if (key === 'ESCAPE') {
focusedElement?.blur();
}
});
};
window.addEventListener('keyup', keyUpHandler);
window.addEventListener('keydown', keyDownHandler);
};
}
/**
* Unregisters the global shortcuts and removes the global keyboard listeners.
*
* @returns {Function}
*/
export function disposeKeyboardShortcuts() {
return (dispatch: IStore['dispatch']) => {
// The components that are registering shortcut should take care of unregistering them.
unregisterGlobalKeyboardShortcuts(dispatch);
keyUpHandler && window.removeEventListener('keyup', keyUpHandler);
keyDownHandler && window.removeEventListener('keydown', keyDownHandler);
keyDownHandler = keyUpHandler = undefined;
};
}

@ -1,11 +1,17 @@
import { AnyAction } from 'redux';
import { IStore } from '../app/types';
import { CONFERENCE_JOINED, CONFERENCE_LEFT } from '../base/conference/actionTypes';
import { SET_CONFIG } from '../base/config/actionTypes';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { CAPTURE_EVENTS } from '../remote-control/actionTypes';
import { disableKeyboardShortcuts, enableKeyboardShortcuts } from './actions';
import {
disableKeyboardShortcuts,
disposeKeyboardShortcuts,
enableKeyboardShortcuts,
initKeyboardShortcuts
} from './actions';
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyAction) => {
const { dispatch } = store;
@ -35,6 +41,11 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
return result;
}
case CONFERENCE_JOINED:
dispatch(initKeyboardShortcuts());
break;
case CONFERENCE_LEFT:
dispatch(disposeKeyboardShortcuts());
}
return next(action);

@ -25,6 +25,7 @@
"react/features/e2ee",
"react/features/embed-meeting",
"react/features/face-landmarks",
"react/features/keyboard-shortcuts",
"react/features/no-audio-signal",
"react/features/noise-suppression",
"react/features/old-client-notification",

Loading…
Cancel
Save