From 05737202fac7e17e6177440c247b1e43b37f26f8 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 23 Dec 2021 13:29:02 -0800 Subject: [PATCH] Add a temporary solution to non-Latin keyboards being unusable --- frontend/src/lifetime/input.ts | 352 ++++++++++++++++++++++++++++++++- 1 file changed, 342 insertions(+), 10 deletions(-) diff --git a/frontend/src/lifetime/input.ts b/frontend/src/lifetime/input.ts index f21c74aa..669ae8ac 100644 --- a/frontend/src/lifetime/input.ts +++ b/frontend/src/lifetime/input.ts @@ -37,37 +37,41 @@ export function createInputManager(editor: EditorState, container: HTMLElement, // Don't redirect when a modal is covering the workspace if (dialog.dialogIsVisible()) return false; + const key = getLatinKey(e); + // Don't redirect a fullscreen request - if (e.key.toLowerCase() === "f11" && e.type === "keydown" && !e.repeat) { + if (key === "f11" && e.type === "keydown" && !e.repeat) { e.preventDefault(); fullscreen.toggleFullscreen(); return false; } // Don't redirect a reload request - if (e.key.toLowerCase() === "f5") return false; + if (key === "f5") return false; // Don't redirect debugging tools - if (e.key.toLowerCase() === "f12") return false; - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "c") return false; - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "i") return false; - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "j") return false; + if (key === "f12") return false; + if (e.ctrlKey && e.shiftKey && key === "c") return false; + if (e.ctrlKey && e.shiftKey && key === "i") return false; + if (e.ctrlKey && e.shiftKey && key === "j") return false; // Redirect to the backend return true; }; const onKeyDown = (e: KeyboardEvent) => { + const key = getLatinKey(e); + if (shouldRedirectKeyboardEventToBackend(e)) { e.preventDefault(); const modifiers = makeModifiersBitfield(e); - editor.instance.on_key_down(e.key, modifiers); + editor.instance.on_key_down(key, modifiers); return; } if (dialog.dialogIsVisible()) { - if (e.key === "Escape") dialog.dismissDialog(); - if (e.key === "Enter") { + if (key === "escape") dialog.dismissDialog(); + if (key === "enter") { dialog.submitDialog(); // Prevent the Enter key from acting like a click on the last clicked button, which might reopen the dialog @@ -77,10 +81,12 @@ export function createInputManager(editor: EditorState, container: HTMLElement, }; const onKeyUp = (e: KeyboardEvent) => { + const key = getLatinKey(e); + if (shouldRedirectKeyboardEventToBackend(e)) { e.preventDefault(); const modifiers = makeModifiersBitfield(e); - editor.instance.on_key_up(e.key, modifiers); + editor.instance.on_key_up(key, modifiers); } }; @@ -196,3 +202,329 @@ export type InputManager = ReturnType; export function makeModifiersBitfield(e: WheelEvent | PointerEvent | KeyboardEvent): number { return Number(e.ctrlKey) | (Number(e.shiftKey) << 1) | (Number(e.altKey) << 2); } + +// This function is a naive, temporary solution to allow non-Latin keyboards to fall back on the physical QWERTY layout +function getLatinKey(e: KeyboardEvent): string { + const key = e.key.toLowerCase(); + const isPrintable = isKeyPrintable(e.key); + + // Control (non-printable) characters are handled normally + if (!isPrintable) return key; + + // These non-Latin characters should fall back to the Latin equivalent at the key location + const LAST_LATIN_UNICODE_CHAR = 0x024f; + if (key.length > 1 || key.charCodeAt(0) > LAST_LATIN_UNICODE_CHAR) return e.code.toLowerCase(); + + // Otherwise, ths is a printable Latin character + return e.key.toLowerCase(); +} + +function isKeyPrintable(key: string): boolean { + const allPrintableKeys: string[] = [ + // Modifier + "Alt", + "AltGraph", + "CapsLock", + "Control", + "Fn", + "FnLock", + "Meta", + "NumLock", + "ScrollLock", + "Shift", + "Symbol", + "SymbolLock", + // Legacy modifier + "Hyper", + "Super", + // White space + "Enter", + "Tab", + // Navigation + "ArrowDown", + "ArrowLeft", + "ArrowRight", + "ArrowUp", + "End", + "Home", + "PageDown", + "PageUp", + // Editing + "Backspace", + "Clear", + "Copy", + "CrSel", + "Cut", + "Delete", + "EraseEof", + "ExSel", + "Insert", + "Paste", + "Redo", + "Undo", + // UI + "Accept", + "Again", + "Attn", + "Cancel", + "ContextMenu", + "Escape", + "Execute", + "Find", + "Help", + "Pause", + "Play", + "Props", + "Select", + "ZoomIn", + "ZoomOut", + // Device + "BrightnessDown", + "BrightnessUp", + "Eject", + "LogOff", + "Power", + "PowerOff", + "PrintScreen", + "Hibernate", + "Standby", + "WakeUp", + // IME composition keys + "AllCandidates", + "Alphanumeric", + "CodeInput", + "Compose", + "Convert", + "Dead", + "FinalMode", + "GroupFirst", + "GroupLast", + "GroupNext", + "GroupPrevious", + "ModeChange", + "NextCandidate", + "NonConvert", + "PreviousCandidate", + "Process", + "SingleCandidate", + // Korean-specific + "HangulMode", + "HanjaMode", + "JunjaMode", + // Japanese-specific + "Eisu", + "Hankaku", + "Hiragana", + "HiraganaKatakana", + "KanaMode", + "KanjiMode", + "Katakana", + "Romaji", + "Zenkaku", + "ZenkakuHankaku", + // Common function + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "Soft1", + "Soft2", + "Soft3", + "Soft4", + // Multimedia + "ChannelDown", + "ChannelUp", + "Close", + "MailForward", + "MailReply", + "MailSend", + "MediaClose", + "MediaFastForward", + "MediaPause", + "MediaPlay", + "MediaPlayPause", + "MediaRecord", + "MediaRewind", + "MediaStop", + "MediaTrackNext", + "MediaTrackPrevious", + "New", + "Open", + "Print", + "Save", + "SpellCheck", + // Multimedia numpad + "Key11", + "Key12", + // Audio + "AudioBalanceLeft", + "AudioBalanceRight", + "AudioBassBoostDown", + "AudioBassBoostToggle", + "AudioBassBoostUp", + "AudioFaderFront", + "AudioFaderRear", + "AudioSurroundModeNext", + "AudioTrebleDown", + "AudioTrebleUp", + "AudioVolumeDown", + "AudioVolumeUp", + "AudioVolumeMute", + "MicrophoneToggle", + "MicrophoneVolumeDown", + "MicrophoneVolumeUp", + "MicrophoneVolumeMute", + // Speech + "SpeechCorrectionList", + "SpeechInputToggle", + // Application + "LaunchApplication1", + "LaunchApplication2", + "LaunchCalendar", + "LaunchContacts", + "LaunchMail", + "LaunchMediaPlayer", + "LaunchMusicPlayer", + "LaunchPhone", + "LaunchScreenSaver", + "LaunchSpreadsheet", + "LaunchWebBrowser", + "LaunchWebCam", + "LaunchWordProcessor", + // Browser + "BrowserBack", + "BrowserFavorites", + "BrowserForward", + "BrowserHome", + "BrowserRefresh", + "BrowserSearch", + "BrowserStop", + // Mobile phone + "AppSwitch", + "Call", + "Camera", + "CameraFocus", + "EndCall", + "GoBack", + "GoHome", + "HeadsetHook", + "LastNumberRedial", + "Notification", + "MannerMode", + "VoiceDial", + // TV + "TV", + "TV3DMode", + "TVAntennaCable", + "TVAudioDescription", + "TVAudioDescriptionMixDown", + "TVAudioDescriptionMixUp", + "TVContentsMenu", + "TVDataService", + "TVInput", + "TVInputComponent1", + "TVInputComponent2", + "TVInputComposite1", + "TVInputComposite2", + "TVInputHDMI1", + "TVInputHDMI2", + "TVInputHDMI3", + "TVInputHDMI4", + "TVInputVGA1", + "TVMediaContext", + "TVNetwork", + "TVNumberEntry", + "TVPower", + "TVRadioService", + "TVSatellite", + "TVSatelliteBS", + "TVSatelliteCS", + "TVSatelliteToggle", + "TVTerrestrialAnalog", + "TVTerrestrialDigital", + "TVTimer", + // Media controls + "AVRInput", + "AVRPower", + "ColorF0Red", + "ColorF1Green", + "ColorF2Yellow", + "ColorF3Blue", + "ColorF4Grey", + "ColorF5Brown", + "ClosedCaptionToggle", + "Dimmer", + "DisplaySwap", + "DVR", + "Exit", + "FavoriteClear0", + "FavoriteClear1", + "FavoriteClear2", + "FavoriteClear3", + "FavoriteRecall0", + "FavoriteRecall1", + "FavoriteRecall2", + "FavoriteRecall3", + "FavoriteStore0", + "FavoriteStore1", + "FavoriteStore2", + "FavoriteStore3", + "Guide", + "GuideNextDay", + "GuidePreviousDay", + "Info", + "InstantReplay", + "Link", + "ListProgram", + "LiveContent", + "Lock", + "MediaApps", + "MediaAudioTrack", + "MediaLast", + "MediaSkipBackward", + "MediaSkipForward", + "MediaStepBackward", + "MediaStepForward", + "MediaTopMenu", + "NavigateIn", + "NavigateNext", + "NavigateOut", + "NavigatePrevious", + "NextFavoriteChannel", + "NextUserProfile", + "OnDemand", + "Pairing", + "PinPDown", + "PinPMove", + "PinPToggle", + "PinPUp", + "PlaySpeedDown", + "PlaySpeedReset", + "PlaySpeedUp", + "RandomToggle", + "RcLowBattery", + "RecordSpeedNext", + "RfBypass", + "ScanChannelsToggle", + "ScreenModeNext", + "Settings", + "SplitScreenToggle", + "STBInput", + "STBPower", + "Subtitle", + "Teletext", + "VideoModeNext", + "Wink", + "ZoomToggle", + ]; + + return !allPrintableKeys.includes(key); +}