cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/docs-activation-truth
goq / portal / keyboard-map.mjs
keyboard-map.mjs 3.0K
1// This bounded subset of W3C KeyboardEvent.code values is the Portal/Sigil2// physical-key contract. Codes identify key position rather than the active3// client layout, which is what games, Steam, and Linux evdev consume.4const PHYSICAL_KEY_CODES = new Set([5 ...Array.from({ length: 26 }, (_, index) => `Key${String.fromCharCode(65 + index)}`),6 ...Array.from({ length: 10 }, (_, index) => `Digit${index}`),7 'Minus',8 'Equal',9 'BracketLeft',10 'BracketRight',11 'Backslash',12 'IntlBackslash',13 'IntlRo',14 'IntlYen',15 'Semicolon',16 'Quote',17 'Backquote',18 'Comma',19 'Period',20 'Slash',21 'Enter',22 'Tab',23 'Space',24 'Backspace',25 'Escape',26 'ShiftLeft',27 'ShiftRight',28 'ControlLeft',29 'ControlRight',30 'AltLeft',31 'AltRight',32 'MetaLeft',33 'MetaRight',34 'ArrowUp',35 'ArrowDown',36 'ArrowLeft',37 'ArrowRight',38 'Home',39 'End',40 'PageUp',41 'PageDown',42 'Insert',43 'Delete',44 'PrintScreen',45 ...Array.from({ length: 12 }, (_, index) => `F${index + 1}`),46]);4748const LEGACY_NAMED_KEYS = Object.freeze({49 ArrowUp: 'Up',50 ArrowDown: 'Down',51 ArrowLeft: 'Left',52 ArrowRight: 'Right',53 ' ': 'Space',54 Delete: 'Delete',55 Backspace: 'Backspace',56 Enter: 'Enter',57 Tab: 'Tab',58 Escape: 'Escape',59 Shift: 'Shift',60 Control: 'Control',61 Alt: 'Alt',62 Meta: 'Meta',63 Home: 'Home',64 End: 'End',65 PageUp: 'PageUp',66 PageDown: 'PageDown',67 Insert: 'Insert',68 PrintScreen: 'PrintScreen',69});7071function isAsciiPrintable(value) {72 return value.length === 173 && value.codePointAt(0) >= 0x2074 && value.codePointAt(0) <= 0x7e;75}7677function textValue(event) {78 if (event.ctrlKey || event.altKey || event.metaKey || event.isComposing) return null;79 if (typeof event.key !== 'string') return null;80 const codePoints = Array.from(event.key);81 if (codePoints.length !== 1) return null;82 const codePoint = codePoints[0].codePointAt(0);83 if (codePoint <= 0x1f84 || (codePoint >= 0x7f && codePoint <= 0x9f)85 || (codePoint >= 0xd800 && codePoint <= 0xdfff)) return null;86 return event.key;87}8889export function mapKey(event) {90 if (!event || typeof event !== 'object') return null;91 if (typeof event.code === 'string' && event.code.length > 0) {92 return PHYSICAL_KEY_CODES.has(event.code) ? event.code : null;93 }94 if (typeof event.key !== 'string') return null;95 if (/^F(?:[1-9]|1[0-2])$/.test(event.key)) return event.key;96 return LEGACY_NAMED_KEYS[event.key]97 ?? (isAsciiPrintable(event.key) ? event.key : null);98}99100// Resolve each keydown to exactly one protocol class. Physical keyboard input101// wins whenever it is available. Text is considered only if the host separately102// advertises an operational Text capability, and is never emitted alongside a103// key transition.104export function keyboardInputForEvent(event, capabilities = {}) {105 if (capabilities.keyboard === true) {106 const key = mapKey(event);107 if (key !== null) return { type: 'key', key };108 }109 if (capabilities.text === true) {110 const text = textValue(event);111 if (text !== null) return { type: 'text', text };112 }113 return null;114}