Use physical keys+layout for shortcuts
This commit is contained in:
parent
b6cfe0933d
commit
3e31a7405b
5 changed files with 50 additions and 6 deletions
|
@ -32,6 +32,7 @@ import { filter } from './util/iterables';
|
||||||
import { isNotNil } from './util/isNotNil';
|
import { isNotNil } from './util/isNotNil';
|
||||||
import { senderCertificateService } from './services/senderCertificate';
|
import { senderCertificateService } from './services/senderCertificate';
|
||||||
import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher';
|
import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher';
|
||||||
|
import * as KeyboardLayout from './services/keyboardLayout';
|
||||||
import { routineProfileRefresh } from './routineProfileRefresh';
|
import { routineProfileRefresh } from './routineProfileRefresh';
|
||||||
import { isMoreRecentThan, isOlderThan, toDayMillis } from './util/timestamp';
|
import { isMoreRecentThan, isOlderThan, toDayMillis } from './util/timestamp';
|
||||||
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
|
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
|
||||||
|
@ -142,6 +143,8 @@ export async function cleanupSessionResets(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startApp(): Promise<void> {
|
export async function startApp(): Promise<void> {
|
||||||
|
await KeyboardLayout.initialize();
|
||||||
|
|
||||||
window.Whisper.events = window._.clone(window.Backbone.Events);
|
window.Whisper.events = window._.clone(window.Backbone.Events);
|
||||||
window.Signal.Util.MessageController.install();
|
window.Signal.Util.MessageController.install();
|
||||||
window.Signal.conversationControllerStart();
|
window.Signal.conversationControllerStart();
|
||||||
|
@ -1145,7 +1148,7 @@ export async function startApp(): Promise<void> {
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
const { ctrlKey, key, metaKey, shiftKey } = event;
|
const { ctrlKey, metaKey, shiftKey } = event;
|
||||||
|
|
||||||
const commandKey = window.platform === 'darwin' && metaKey;
|
const commandKey = window.platform === 'darwin' && metaKey;
|
||||||
const controlKey = window.platform !== 'darwin' && ctrlKey;
|
const controlKey = window.platform !== 'darwin' && ctrlKey;
|
||||||
|
@ -1155,6 +1158,8 @@ export async function startApp(): Promise<void> {
|
||||||
const selectedId = state.conversations.selectedConversationId;
|
const selectedId = state.conversations.selectedConversationId;
|
||||||
const conversation = window.ConversationController.get(selectedId);
|
const conversation = window.ConversationController.get(selectedId);
|
||||||
|
|
||||||
|
const key = KeyboardLayout.lookup(event);
|
||||||
|
|
||||||
// NAVIGATION
|
// NAVIGATION
|
||||||
|
|
||||||
// Show keyboard shortcuts - handled by Electron-managed keyboard shortcuts
|
// Show keyboard shortcuts - handled by Electron-managed keyboard shortcuts
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { GroupCallRemoteParticipants } from './GroupCallRemoteParticipants';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
import { NeedsScreenRecordingPermissionsModal } from './NeedsScreenRecordingPermissionsModal';
|
import { NeedsScreenRecordingPermissionsModal } from './NeedsScreenRecordingPermissionsModal';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||||
import { useActivateSpeakerViewOnPresenting } from '../hooks/useActivateSpeakerViewOnPresenting';
|
import { useActivateSpeakerViewOnPresenting } from '../hooks/useActivateSpeakerViewOnPresenting';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
@ -205,10 +206,12 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
const handleKeyDown = (event: KeyboardEvent): void => {
|
const handleKeyDown = (event: KeyboardEvent): void => {
|
||||||
let eventHandled = false;
|
let eventHandled = false;
|
||||||
|
|
||||||
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
|
const key = KeyboardLayout.lookup(event);
|
||||||
|
|
||||||
|
if (event.shiftKey && (key === 'V' || key === 'v')) {
|
||||||
toggleVideo();
|
toggleVideo();
|
||||||
eventHandled = true;
|
eventHandled = true;
|
||||||
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
|
} else if (event.shiftKey && (key === 'M' || key === 'm')) {
|
||||||
toggleAudio();
|
toggleAudio();
|
||||||
eventHandled = true;
|
eventHandled = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
} from './CallingLobbyJoinButton';
|
} from './CallingLobbyJoinButton';
|
||||||
import { AvatarColorType } from '../types/Colors';
|
import { AvatarColorType } from '../types/Colors';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
|
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||||
import { ConversationType } from '../state/ducks/conversations';
|
import { ConversationType } from '../state/ducks/conversations';
|
||||||
import { isConversationTooBigToRing } from '../conversations/isConversationTooBigToRing';
|
import { isConversationTooBigToRing } from '../conversations/isConversationTooBigToRing';
|
||||||
|
|
||||||
|
@ -116,10 +117,11 @@ export const CallingLobby = ({
|
||||||
function handleKeyDown(event: KeyboardEvent): void {
|
function handleKeyDown(event: KeyboardEvent): void {
|
||||||
let eventHandled = false;
|
let eventHandled = false;
|
||||||
|
|
||||||
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
|
const key = KeyboardLayout.lookup(event);
|
||||||
|
if (event.shiftKey && (key === 'V' || key === 'v')) {
|
||||||
toggleVideo();
|
toggleVideo();
|
||||||
eventHandled = true;
|
eventHandled = true;
|
||||||
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
|
} else if (event.shiftKey && (key === 'M' || key === 'm')) {
|
||||||
toggleAudio();
|
toggleAudio();
|
||||||
eventHandled = true;
|
eventHandled = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ import { emojiToData } from '../emoji/lib';
|
||||||
import type { SmartReactionPicker } from '../../state/smart/ReactionPicker';
|
import type { SmartReactionPicker } from '../../state/smart/ReactionPicker';
|
||||||
import { getCustomColorStyle } from '../../util/getCustomColorStyle';
|
import { getCustomColorStyle } from '../../util/getCustomColorStyle';
|
||||||
import { offsetDistanceModifier } from '../../util/popperUtil';
|
import { offsetDistanceModifier } from '../../util/popperUtil';
|
||||||
|
import * as KeyboardLayout from '../../services/keyboardLayout';
|
||||||
|
|
||||||
type Trigger = {
|
type Trigger = {
|
||||||
handleContextClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
handleContextClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
|
@ -2283,8 +2284,10 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
// Do not allow reactions to error messages
|
// Do not allow reactions to error messages
|
||||||
const { canReply } = this.props;
|
const { canReply } = this.props;
|
||||||
|
|
||||||
|
const key = KeyboardLayout.lookup(event.nativeEvent);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(event.key === 'E' || event.key === 'e') &&
|
(key === 'E' || key === 'e') &&
|
||||||
(event.metaKey || event.ctrlKey) &&
|
(event.metaKey || event.ctrlKey) &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
canReply
|
canReply
|
||||||
|
|
31
ts/services/keyboardLayout.ts
Normal file
31
ts/services/keyboardLayout.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { strictAssert } from '../util/assert';
|
||||||
|
|
||||||
|
type LayoutMapType = { get(code: string): string | undefined };
|
||||||
|
|
||||||
|
let layoutMap: LayoutMapType | undefined;
|
||||||
|
|
||||||
|
export async function initialize(): Promise<void> {
|
||||||
|
strictAssert(layoutMap === undefined, 'keyboardLayout already initialized');
|
||||||
|
|
||||||
|
const experimentalNavigator = (window.navigator as unknown) as {
|
||||||
|
keyboard: { getLayoutMap(): Promise<LayoutMapType> };
|
||||||
|
};
|
||||||
|
|
||||||
|
strictAssert(
|
||||||
|
typeof experimentalNavigator.keyboard?.getLayoutMap === 'function',
|
||||||
|
'No support for getLayoutMap'
|
||||||
|
);
|
||||||
|
|
||||||
|
layoutMap = await experimentalNavigator.keyboard.getLayoutMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lookup({
|
||||||
|
code,
|
||||||
|
key,
|
||||||
|
}: Pick<KeyboardEvent, 'code' | 'key'>): string | undefined {
|
||||||
|
strictAssert(layoutMap !== undefined, 'keyboardLayout not initialized');
|
||||||
|
return layoutMap.get(code) ?? key;
|
||||||
|
}
|
Loading…
Reference in a new issue