iris-gui: send physical key positions, not layout-translated keys. fi… - #73
Merged
Merged
Conversation
techomancer#72 The guest does its own keyboard layout translation — the SGI PROM reads keybd= (setenv keybd de_CH) and IRIX layers X11 keymaps on top. "keybd" appears nowhere in the iris source: iris must feed the guest raw scancodes for physical key positions and let the guest apply the layout. src/ui.rs (the CLI) always did that, via KeyEvent::physical_key. iris-gui did not. It read egui::Event::Key { key, .. } — the *logical* key the host OS already produced from the host's layout — and reverse-mapped it to a KeyCode assuming a US keyboard. The layout therefore got applied twice, and what reached the screen was guest_layout(US_position_labelled_with(host_layout(key_pressed))) which predicts every symptom in the issue exactly. On a German host: pressed egui logical Key sent as guest showed - (at US /) Minus US Minus pos ß Shift-7 = / Slash US Slash pos _ Shift-ß = ? Questionmark US Slash pos _ Shift-, = ; Semicolon US Semicolon Ö Shift-. = : Colon US Semicolon Ö Shift-0 = = Equals US Equal pos ` Z (at US Y) Z US Z pos y It also explains the part of the report that looked self-contradictory — umlauts working while ASCII punctuation broke. egui-winit builds the event as `key: logical_key.or(physical_key)`, and egui::Key has no variant for ä ö ü ß, so those keys fell through to the *physical* key and landed correctly by accident. Only characters egui can name got relocated. Prefer physical_key, which egui has carried on Event::Key all along and which this code was discarding with `..`. The ISO 102nd key (< > |, left of Z on every European keyboard) was a second, independent bug: KeyCode::IntlBackslash had no entry in any scancode set, so it was dead in the CLI too. Added to all three — set 1 0x56, set 2 0x61, set 3 0x13. Set 3 is the one IRIX drives the keyboard in; 0x13 cross-checks against Linux's atkbd_set3_keycode[0x13] == KEY_102ND and lands in the leftmost column between LCtrl 0x11, LShift 0x12 and CapsLock 0x14. Note set 2's 0x61 must not be reused for set 3, where it is Left. egui 0.29 -> 0.35 ----------------- Load-bearing for the above, not cosmetic. 0.29 could not express two things no amount of iris-side code could recover: - Key::IntlBackslash did not exist, and egui::Key had no Less/Greater, so both logical_key and physical_key were None and *no event at all* was emitted for the ISO key. - egui::Modifiers has a single `alt` with no left/right split and modifier keys produced no Key event, so input.rs could only ever send AltLeft. That killed the entire AltGr level on DE/de_CH — @ \ | { } [ ] ~ €. de_CH's \ is AltGr+<, hit by both gaps at once. 0.35 adds Key::IntlBackslash and discrete ShiftLeft/Right, ControlLeft/Right, AltLeft/Right, SuperLeft/Right. pump() now forwards modifiers as real press/release key events and tracks held_mods: Vec<KeyCode>; the egui::Modifiers-diff synthesis is gone, which is also how src/ui.rs has always worked. Port was 53 errors, all mechanical: 41 were ui.close_menu() -> ui.close(). The rest: SidePanel/TopBottomPanel unified into egui::Panel, panels take a &mut Ui instead of a &Context, App::update -> App::ui, ctx.screen_rect() -> ctx.viewport_rect(), Frame::none() -> Frame::new(), Margin::symmetric f32 -> i8, Image::rounding -> corner_radius, push_id needs AsIdSalt (Hash + Debug). The old update() body survives unchanged behind `let ctx = &ui.ctx().clone()`. Two-key release chord --------------------- Now possible because 0.35 distinguishes left from right: hold left Ctrl+Alt (left Option+Cmd on macOS) and press nothing else. It fires on key-up and only if chord_consumed is false — no other key pressed while held — which is what preserves Ctrl+Alt+F11. Ctrl+Alt+Esc stays as an explicit fallback. The UI hint reads input::RELEASE_HINT so it cannot drift from the real chord. winit 0.29 -> 0.30: one winit, so the App Store patch actually covers it ----------------------------------------------------------------------- Found while verifying the above: the private SkyLight symbol was back in the linked binary. nm -u target/release/iris-gui | grep CGS _CGSMainConnectionID _CGSSetWindowBackgroundBlurRadius Pre-existing — the IRIS.app bundle from Jul 2 has it too, and it is unaffected by the egui bump either way. The vendored third_party/ winit-0.30.13 stub was intact and doing its job; the import came from iris's *own* winit 0.29, which [patch.crates-io] never covered because that patch only matches the 0.30.x requirement. Only two crates in the registry declare the symbol, and the binary embedded source paths for both — including winit-0.29.15/src/platform_impl/macos/window.rs, where the call lives. Merely depending on winit 0.29 links its macOS backend; reachability is not enough to strip it. Commenting out `pub mod ui;` and rebuilding does not remove the symbol — winit's macOS backend registers Obj-C classes via declare_class!, which emits #[used] statics that survive dead-stripping. The original patch's assumption, that the 0.29 copy "creates no window in iris-gui, so its blur code is dead-stripped", was wrong. Fixed by removing the second winit rather than adding a second patch: winit 0.29 -> 0.30, glutin 0.31 -> 0.32, glutin-winit 0.4 -> 0.5, raw-window-handle 0.5 -> 0.6. Cheap because KeyCode is byte-identical across winit 0.29/0.30 (ps2.rs and all keyboard code needed no changes) and EventLoop::run(closure) still exists in 0.30, so src/ui.rs did not need an ApplicationHandler rewrite. Eight mechanical edits in ui.rs and headless_gl.rs: WindowBuilder -> WindowAttributes::default(), .with_window_builder() -> .with_window_attributes(), and rwh 0.6 making raw_window_handle() / build_surface_attributes() return Result. Cargo.lock now holds exactly one winit block, and it is the path override. Verified clean on ./scripts/build-macos.sh appstore and on the signed sandboxed bundle, with _CGShieldingWindowLevel (public) still present as a control that nm is reading the binary. Taking winit from git master instead is not an option yet, and fails dangerously: master has the fix behind a private-apple-apis feature but is versioned 0.31.0-beta.2, which does not satisfy egui-winit 0.35's ^0.30.13, so cargo only warns "patch was not used in the crate graph" and silently links the unpatched registry crate. The v0.30.x branch is version-compatible but the fix was never backported there. Documented in rules/macos/appstore-private-api.md. Not covered here: no runtime testing against IRIX — this is verified at compile and symbol level only. flexi0n's "de_CH umlauts do not work" is the one reported symptom the host-side model does not explain and should be re-tested. Still no CI gate for the nm check; that belongs in appstore.yml on main. Five deprecation warnings remain (HasRawWindowHandle, EventLoop::run), left alone deliberately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
danifunker
marked this pull request as ready for review
August 3, 2026 18:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…xes #72
The guest does its own keyboard layout translation — the SGI PROM reads keybd= (setenv keybd de_CH) and IRIX layers X11 keymaps on top. "keybd" appears nowhere in the iris source: iris must feed the guest raw scancodes for physical key positions and let the guest apply the layout.
src/ui.rs (the CLI) always did that, via KeyEvent::physical_key. iris-gui did not. It read egui::Event::Key { key, .. } — the logical key the host OS already produced from the host's layout — and reverse-mapped it to a KeyCode assuming a US keyboard. The layout therefore got applied twice, and what reached the screen was
which predicts every symptom in the issue exactly. On a German host:
It also explains the part of the report that looked self-contradictory — umlauts working while ASCII punctuation broke. egui-winit builds the event as
key: logical_key.or(physical_key), and egui::Key has no variant for ä ö ü ß, so those keys fell through to the physical key and landed correctly by accident. Only characters egui can name got relocated.Prefer physical_key, which egui has carried on Event::Key all along and which this code was discarding with
...The ISO 102nd key (< > |, left of Z on every European keyboard) was a second, independent bug: KeyCode::IntlBackslash had no entry in any scancode set, so it was dead in the CLI too. Added to all three — set 1 0x56, set 2 0x61, set 3 0x13. Set 3 is the one IRIX drives the keyboard in; 0x13 cross-checks against Linux's atkbd_set3_keycode[0x13] == KEY_102ND and lands in the leftmost column between LCtrl 0x11, LShift 0x12 and CapsLock 0x14. Note set 2's 0x61 must not be reused for set 3, where it is Left.
egui 0.29 -> 0.35
Load-bearing for the above, not cosmetic. 0.29 could not express two things no amount of iris-side code could recover:
altwith no left/right split and modifier keys produced no Key event, so input.rs could only ever send AltLeft. That killed the entire AltGr level on DE/de_CH — @ \ | { } [ ] ~ €. de_CH's \ is AltGr+<, hit by both gaps at once.0.35 adds Key::IntlBackslash and discrete ShiftLeft/Right, ControlLeft/Right, AltLeft/Right, SuperLeft/Right. pump() now forwards modifiers as real press/release key events and tracks held_mods: Vec; the egui::Modifiers-diff synthesis is gone, which is also how src/ui.rs has always worked.
Port was 53 errors, all mechanical: 41 were ui.close_menu() -> ui.close(). The rest: SidePanel/TopBottomPanel unified into egui::Panel, panels take a &mut Ui instead of a &Context, App::update -> App::ui, ctx.screen_rect() -> ctx.viewport_rect(), Frame::none() -> Frame::new(), Margin::symmetric f32 -> i8, Image::rounding -> corner_radius, push_id needs AsIdSalt (Hash + Debug). The old update() body survives unchanged behind
let ctx = &ui.ctx().clone().Two-key release chord
Now possible because 0.35 distinguishes left from right: hold left Ctrl+Alt (left Option+Cmd on macOS) and press nothing else. It fires on key-up and only if chord_consumed is false — no other key pressed while held — which is what preserves Ctrl+Alt+F11. Ctrl+Alt+Esc stays as an explicit fallback. The UI hint reads input::RELEASE_HINT so it cannot drift from the real chord.
winit 0.29 -> 0.30: one winit, so the App Store patch actually covers it ----------------------------------------------------------------------- Found while verifying the above: the private SkyLight symbol was back in the linked binary.
Pre-existing — the IRIS.app bundle from Jul 2 has it too, and it is unaffected by the egui bump either way. The vendored third_party/ winit-0.30.13 stub was intact and doing its job; the import came from iris's own winit 0.29, which [patch.crates-io] never covered because that patch only matches the 0.30.x requirement. Only two crates in the registry declare the symbol, and the binary embedded source paths for both — including winit-0.29.15/src/platform_impl/macos/window.rs, where the call lives.
Merely depending on winit 0.29 links its macOS backend; reachability is not enough to strip it. Commenting out
pub mod ui;and rebuilding does not remove the symbol — winit's macOS backend registers Obj-C classes via declare_class!, which emits #[used] statics that survive dead-stripping. The original patch's assumption, that the 0.29 copy "creates no window in iris-gui, so its blur code is dead-stripped", was wrong.Fixed by removing the second winit rather than adding a second patch: winit 0.29 -> 0.30, glutin 0.31 -> 0.32, glutin-winit 0.4 -> 0.5, raw-window-handle 0.5 -> 0.6. Cheap because KeyCode is byte-identical across winit 0.29/0.30 (ps2.rs and all keyboard code needed no changes) and EventLoop::run(closure) still exists in 0.30, so src/ui.rs did not need an ApplicationHandler rewrite. Eight mechanical edits in ui.rs and headless_gl.rs: WindowBuilder -> WindowAttributes::default(), .with_window_builder() -> .with_window_attributes(), and rwh 0.6 making raw_window_handle() / build_surface_attributes() return Result.
Cargo.lock now holds exactly one winit block, and it is the path override. Verified clean on ./scripts/build-macos.sh appstore and on the signed sandboxed bundle, with _CGShieldingWindowLevel (public) still present as a control that nm is reading the binary.
Taking winit from git master instead is not an option yet, and fails dangerously: master has the fix behind a private-apple-apis feature but is versioned 0.31.0-beta.2, which does not satisfy egui-winit 0.35's ^0.30.13, so cargo only warns "patch was not used in the crate graph" and silently links the unpatched registry crate. The v0.30.x branch is version-compatible but the fix was never backported there. Documented in rules/macos/appstore-private-api.md.
Not covered here: no runtime testing against IRIX — this is verified at compile and symbol level only. flexi0n's "de_CH umlauts do not work" is the one reported symptom the host-side model does not explain and should be re-tested. Still no CI gate for the nm check; that belongs in appstore.yml on main. Five deprecation warnings remain (HasRawWindowHandle, EventLoop::run), left alone deliberately.