diff --git a/images/chromium-headful/client/src/utils/guacamole-keyboard.js b/images/chromium-headful/client/src/utils/guacamole-keyboard.js index e15e5198..f1533a03 100644 --- a/images/chromium-headful/client/src/utils/guacamole-keyboard.js +++ b/images/chromium-headful/client/src/utils/guacamole-keyboard.js @@ -298,10 +298,6 @@ Guacamole.Keyboard = function Keyboard(element) { if (this.modifiers.meta && this.keysym !== 0xFFE7 && this.keysym !== 0xFFE8) this.keyupReliable = false; - // We cannot rely on receiving keyup for Caps Lock on certain platforms - else if (this.keysym === 0xFFE5 && quirks.capsLockKeyupUnreliable) - this.keyupReliable = false; - // Determine whether default action for Alt+combinations must be prevented var prevent_alt = !this.modifiers.ctrl && !quirks.altIsTypableOnly; @@ -1135,6 +1131,14 @@ Guacamole.Keyboard = function Keyboard(element) { if (!first) return null; + // macOS sends Caps Lock keydown only when enabling it and keyup only + // when disabling it, so each event is one full press of the key + if (first.keysym === 0xFFE5 && quirks.capsLockKeyupUnreliable) { + first.defaultPrevented = !guac_keyboard.press(0xFFE5); + guac_keyboard.release(0xFFE5); + return eventLog.shift(); + } + // Keydown event if (first instanceof KeydownEvent) { diff --git a/images/chromium-headful/client/tests/guacamole-keyboard.test.ts b/images/chromium-headful/client/tests/guacamole-keyboard.test.ts index 918ed042..0154fe7d 100644 --- a/images/chromium-headful/client/tests/guacamole-keyboard.test.ts +++ b/images/chromium-headful/client/tests/guacamole-keyboard.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from 'bun:test' +import { afterEach, describe, expect, test } from 'bun:test' import createKeyboard from '../src/utils/guacamole-keyboard' @@ -70,3 +70,45 @@ describe('GuacamoleKeyboard repeat handling', () => { expect(events).toEqual(['down:111', 'up:111', 'down:111', 'up:111']) }) }) + +describe('GuacamoleKeyboard Caps Lock handling', () => { + const platform = Object.getOwnPropertyDescriptor(navigator, 'platform')! + + afterEach(() => { + Object.defineProperty(navigator, 'platform', platform) + }) + + function keyboardOn(platformName: string) { + Object.defineProperty(navigator, 'platform', { value: platformName, configurable: true }) + const target = new KeyboardTarget() + const keyboard = createKeyboard(target as unknown as Element) + const events: string[] = [] + keyboard.onkeydown = (key) => { + events.push(`down:${key}`) + return false + } + keyboard.onkeyup = (key) => events.push(`up:${key}`) + return { target, events } + } + + test('forwards each macOS Caps Lock keydown and keyup as a full press', () => { + const { target, events } = keyboardOn('MacIntel') + + target.dispatch('keydown', 'CapsLock', 20) + target.dispatch('keydown', 'a', 65) + target.dispatch('keypress', 'A', 65) + target.dispatch('keyup', 'a', 65) + target.dispatch('keyup', 'CapsLock', 20) + + expect(events).toEqual(['down:65509', 'up:65509', 'down:65', 'up:65', 'down:65509', 'up:65509']) + }) + + test('keeps Caps Lock as a key press on other platforms', () => { + const { target, events } = keyboardOn('Win32') + + target.dispatch('keydown', 'CapsLock', 20) + target.dispatch('keyup', 'CapsLock', 20) + + expect(events).toEqual(['down:65509', 'up:65509']) + }) +})