feat: wire up accessibility switch keys for user port, ADC, and fire buttons#565
feat: wire up accessibility switch keys for user port, ADC, and fire buttons#565mattgodbolt merged 4 commits intomainfrom
Conversation
…buttons The existing user port key mapping (keys 1-8 → &FE60 bits 0-7) was implemented using a local emuKeyHandlers map that was never connected to the keyboard system — so it never actually worked. Fix this and extend the feature: - Remove dead emuKeyHandlers code; register handlers via keyboard.registerKeyHandler() after the keyboard is initialised - Keys 1-8 (K1-K8) continue to map to user port bits 0-7 (active low) - Function keys F1-F8 are added as an additional set of switch inputs (more ergonomic for accessibility use) - F1/K1 and F2/K2 also set the joystick fire buttons on the System VIA (PB4/PB5), so ADVAL(-1) and ADVAL(-2) respond to the switch state - Add KeyboardSwitchSource (src/keyboard-switch-source.js) — an AnalogueSource that deflects ADC channels 0-3 to 0x0000 while a switch is pressed, then delegates to the gamepad source otherwise; installed as the default source for all four ADC channels so ADVAL(1)-ADVAL(4) also respond Closes #160 🤖 Generated by LLM (Claude, via OpenClaw)
- keyboard.js keyDown() now checks registered handlers *before* calling
sysvia.keyDown(); if a handler fires the key is not also forwarded to
the emulated machine, so Alt+key handlers cleanly own their keys.
(keyUp always forwards to the BBC to avoid sticky keys — a keyUp for a
key the BBC never received is harmless.)
- Switch key handlers changed from noMod to { alt: true }: Alt+1–8 and
Alt+F1–F8 trigger switches, restoring the original 2018 intent.
Normal number keys and BBC function keys are now completely unaffected.
Add two keyboard unit tests: - Registered Alt-key handler fires without forwarding to BBC (sysvia.keyDown not called) - Unhandled keys still reach sysvia.keyDown as normal Also add the missing expect(sysvia.keyDown).not.toHaveBeenCalled() assertion to the existing Alt-handler registration test.
There was a problem hiding this comment.
Pull request overview
This PR restores and enhances accessibility switch support for the BBC Micro emulator by fixing dead code from 2018 and adding comprehensive input mapping. It addresses issue #160 by properly wiring keyboard switch handlers to the user port, ADC channels, and joystick fire buttons, enabling disabled users to access switch-interface software.
Changes:
- Fixed keyboard event handler ordering to prevent key leakage when handlers are registered
- Replaced orphaned
emuKeyHandlersmap with properly registered Alt+1-8 and Alt+F1-F8 switch handlers - Added
KeyboardSwitchSourceto map switch presses to ADC channels for analogue port compatibility
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/keyboard.js | Reordered keyDown logic to check registered handlers before forwarding to BBC, preventing handled keys from leaking through |
| src/main.js | Removed dead switch handler code, added proper keyboard handler registration for Alt+1-8/F1-F8, and wired switches to user port, ADC channels, and fire buttons |
| src/keyboard-switch-source.js | New AnalogueSource implementation that deflects ADC channels to 0x0000 when switches are pressed, delegating to gamepad otherwise |
| tests/unit/test-keyboard.js | Added test assertions verifying handlers suppress sysvia.keyDown and unhandled keys still reach it |
Comments suppressed due to low confidence (1)
src/keyboard-switch-source.js:41
- The new KeyboardSwitchSource class lacks unit tests. Following the codebase convention where similar AnalogueSource implementations like GamepadSource (test-gamepad-source.js) and MouseJoystickSource (test-mouse-joystick-source.js) have dedicated test files, this class should have a corresponding test-keyboard-switch-source.js file. Tests should verify: 1) getValue returns 0x0000 when a switch is pressed, 2) getValue delegates to the fallback source when no switch is pressed, 3) setSwitch correctly updates switch state for valid indices (0-3), and 4) setSwitch ignores invalid indices.
import { AnalogueSource } from "./analogue-source.js";
/**
* An AnalogueSource that maps accessibility switch keys to ADC channels.
*
* When switch N is pressed, channel N returns 0x0000 (full deflection).
* When not pressed, the call is delegated to the wrapped fallback source
* (typically the gamepad source).
*
* Switches 0 and 1 additionally map to the joystick fire buttons (PB4/PB5
* on the System VIA), so ADVAL(-1) / ADVAL(-2) reflect switch state too.
*/
export class KeyboardSwitchSource extends AnalogueSource {
/**
* @param {AnalogueSource} fallback - Source to delegate to when no switch is pressed
*/
constructor(fallback) {
super();
this.fallback = fallback;
this._switchValues = new Array(4).fill(null); // null = not pressed
}
/**
* Activate or deactivate a switch.
* @param {number} n - Switch index (0-3)
* @param {boolean} pressed
*/
setSwitch(n, pressed) {
if (n >= 0 && n < 4) {
this._switchValues[n] = pressed ? 0x0000 : null;
}
}
/** @override */
getValue(channel) {
if (channel >= 0 && channel < 4 && this._switchValues[channel] !== null) {
return this._switchValues[channel];
}
return this.fallback ? this.fallback.getValue(channel) : 0x8000;
}
}
On real Brilliant Computing hardware both the switch interface box and the special-ed joystick connect to the User Port via ribbon cable — they do not touch the analogue port (ADC) or the System VIA fire buttons (PB4/PB5), which belong to the standard analogue joystick connector. Coupling Alt+1/Alt+F1 to the fire button (PB4) caused accessibility software (e.g. Thurrock Care) to trigger an unwanted 'select current item' action whenever a switch was pressed. Remove KeyboardSwitchSource and all ADC/fire-button coupling from the switch handler. Switch keys now only toggle switchState, which is read via userPort.read() as &FE60. This matches the real hardware behaviour.
Code archaeology & headless testing resultsI downloaded both test disc images and ran them headlessly via Thurrock Care (DISC000.ssd) — detokenised BASIC + headless runEvery program on the disc polls both K%=INKEY(0): face%=?&FE60: UNTIL K%=-1 AND face%=255
Running the disc headlessly and capturing the screen confirms the control scheme directly from the UI:
No Joystick Games 1 (DISC013.ssd) — detokenised BASIC + headless runThe menu immediately asks:
The DEF FNinput
IF Z%=1 THEN =?&FE60
ELSE =255 + (ADVAL(1)<1000) + (ADVAL(1)>65000)*2 + (ADVAL(2)>65000)*4 + (ADVAL(2)<1000)*8USER PORT mode ( The game menu says: "Press the Space Bar or Switch when the game you want is highlighted" — it's a scanning interface, cycling through options automatically; any switch press selects. The reported lockup is explained by these two startup guards in JMENU: 880 UNTIL ?&FE60 = 255 ← waits for user port to clear
910 UNTIL ADVAL(1) IN 1000..65000 AND ADVAL(2) IN 1000..65000 AND (ADVAL(0)AND3)<>1With this PR:
Both lockups are resolved without needing any ADVAL/fire-button coupling from the switch keys. The earlier version of this PR coupled Alt+1 → PB4 fire button, which caused Thurrock Care to trigger an unwanted "select" action. Removing that coupling was correct. Summary
The PR implementation — Alt+1–8 / Alt+F1–F8 setting only (I'm Molty, an AI assistant acting on behalf of @mattgodbolt) |
|
@oneswitch FYI some behind the scenes research stuff :) |
|
Ah magic. I'll bury into this properly ASAP, but I can see already it's going to bring those things back to life. Thanks so much! |
|
Please let me know whatever we can do to get this working well fo your use cases! |
Fixes #160.
What was wrong
The existing user port key mapping (added 2018, commit c77e85b) was dead code. It set up handlers in a local
emuKeyHandlersmap that was never connected to the keyboard system after the 2025 keyboard refactor (commit 64786cf), so pressing any key never actually updated&FE60.Additionally,
keyboard.jscalledsysvia.keyDown()before checking registered handlers, so any handled key also leaked through to the emulated machine.Hardware archaeology
From the Brilliant Computing catalogue (the original supplier referenced in the issue):
Both devices affect only
?&FE60. The analogue port (ADC) and System VIA fire buttons (PB4/PB5) belong to the standard analogue joystick connector — a completely separate physical port. Real switch hardware never touches them.An earlier version of this PR also deflected ADC channels and triggered fire buttons alongside user port bits, following a commenter's ideal spec. Testing with Thurrock Care showed this was wrong — coupling Alt+1 to PB4 caused an unwanted "select current item" action to fire every time a switch was pressed.
Changes
src/keyboard.jskeyDown(); if one fires, return early without callingsysvia.keyDown(). Handled keys no longer leak through to the BBC.keyUp()still always forwards tosysvia.keyUp()to avoid sticky keys.src/main.jsemuKeyHandlersmap and deadswitchKeyloopkeyboard.registerKeyHandler()after keyboard initialisationtests/unit/test-keyboard.jssysvia.keyDownwas not calledsysvia.keyDown; unhandled keys still reach itSwitch mapping
?&FE60when pressedTesting
257 unit tests pass including 2 new keyboard tests. Manual testing with the test discs from the issue:
(I'm Molty, an AI assistant acting on behalf of @mattgodbolt)