diff --git a/crates/benilla-app/src/bindings.rs b/crates/benilla-app/src/bindings.rs index 4c9a9fc3c..e5630d90f 100644 --- a/crates/benilla-app/src/bindings.rs +++ b/crates/benilla-app/src/bindings.rs @@ -18,7 +18,7 @@ //! //! Deviation: a press with Super (Cmd) held runs no binding because that way a system chord such as //! Cmd+Tab or Cmd+Q never acts in game. The reference has no such modifier and runs the bare key's -//! binding: the Mac enqueues the key with Command held and skips only its text (`0x8a519`), and +//! binding: the Mac enqueues the key with Command held and skips only its text (`mac:08a51e`), and //! Windows hands `VK_LWIN`/`VK_RWIN` to the layout arm (`0x42da39`) like any key. //! //! Persistence: `benilla-config/bindings/account.txt` and `-.txt` ([`store`]); the diff --git a/crates/benilla-app/src/bindings/chord.rs b/crates/benilla-app/src/bindings/chord.rs index 82617c390..e1dc21a6e 100644 --- a/crates/benilla-app/src/bindings/chord.rs +++ b/crates/benilla-app/src/bindings/chord.rs @@ -107,6 +107,10 @@ impl Chord { pub(crate) fn key_token(k: KeyCode, layout: &LayoutNames) -> Option { match layout.name(k) { // The lookup folds `a`-`z` up (`0x64b419`), so Turkish `VK_OEM_7`'s `i` is `I`'s key. + // + // Deviation: the Z key is named `Z` on a Mac too, because the Mac client uppercases only + // `a`-`y` (`mac:08a3df`) and so names it `z`, which no binding can press; that fault would + // leave a binding to `Z`, the stock `TOGGLESHEATH` key, unreachable there. Some(LayoutName::Char(c)) => Some(KeyName::Char(c.to_ascii_uppercase())), Some(LayoutName::Dropped) => None, None => fixed_name(k), @@ -119,7 +123,9 @@ pub(crate) fn key_token(k: KeyCode, layout: &LayoutNames) -> Option { /// codes the table cannot name, `0x210` and `0x211` (ScrollLock and Pause, and F14 and F15 on a /// Mac). Every other key without a name never reaches the walk: the Windows key table drops it /// where `MapVirtualKeyA` answers 0 (`0x42da39`: the Windows and Menu keys, media keys, a key the -/// layout drops), the Mac client's table `0x5bf320` has -1 for Command and fn. Unlike +/// layout drops), the Mac client's table `mac:5bf320` has -1 for Command and fn, and its modifier handler queues +/// only Shift, Control and Option (`mac:08a06c`, `mac:08a0d3`, `mac:08a13a`), so Caps Lock queues +/// nothing. Unlike /// [`key_token`], which is a binding's, a modifier still gets a down and an up. pub(crate) fn walk_name(k: KeyCode, layout: &LayoutNames) -> Option { if let Some(name) = key_token(k, layout) { @@ -157,23 +163,27 @@ fn fixed_name(k: KeyCode) -> Option { F10 => Word("F10"), F11 => Word("F11"), F12 => Word("F12"), - // On a Mac F13 is print-screen: the Mac client's key table `0x5bf320` maps keycode - // `0x69` to `0x212`, `PRINTSCREEN`, and macOS has no PrintScreen keycode. + // On a Mac F13 is print-screen: the Mac client's key table `mac:5bf320` maps keycode + // `0x69` to `0x212`, which the namer calls `PRINTSCREEN` (`mac:1de30e`), and macOS has no PrintScreen keycode. #[cfg(target_os = "macos")] F13 => Word("PRINTSCREEN"), // `IsValidBindingKeyString` accepts `F` + any digits (`0x846c04`) and the Mac table maps - // keycode `0x6A` to `F16` (`0x30d`). Deviation: the Windows key table stops at `VK_F12` + // keycode `0x6A` to `0x30d`, which the namer calls `F16` (`mac:1de48a`). Deviation: the Windows key table stops at `VK_F12` // and drops later F-keys; they are named here because that way a stored `F13`-`F24` can be // pressed. #[cfg(not(target_os = "macos"))] F13 => Word("F13"), // On a Mac F14 and F15 are ScrollLock and Pause: keycodes `0x6B` and `0x71` map to - // `0x210` and `0x211`, which the namer calls `UNKNOWN`, so they stay unbindable there. + // `0x210` and `0x211`, which the namer calls `UNKNOWN` (`mac:5bf320`, `mac:1de334`), so they stay + // unbindable there. #[cfg(not(target_os = "macos"))] F14 => Word("F14"), #[cfg(not(target_os = "macos"))] F15 => Word("F15"), F16 => Word("F16"), + // The Mac table leaves keycodes 0x40, 0x4f, 0x50 and 0x5a (F17-F20) at -1 (`mac:5bf320`), + // which `KeyTranslate` fills from the layout (`mac:08a3c3`), so the reference's name for + // them depends on the OS; these are benilla's. F17 => Word("F17"), F18 => Word("F18"), F19 => Word("F19"), @@ -218,6 +228,9 @@ fn fixed_name(k: KeyCode) -> Option { PrintScreen => Word("PRINTSCREEN"), // No ScrollLock or Pause: the namer (`0x4b66b0`) calls `0x210`/`0x211` `UNKNOWN`, and // `IsValidBindingKeyString` (`0x4b7890`) has neither name. + // The Mac client queues no Caps Lock key (`mac:08a06c`): its modifier handler tests only + // Shift, Control and Option, though the table `mac:5bf320` maps keycode 0x39 to `0x20e`. + #[cfg(not(target_os = "macos"))] CapsLock => Word("CAPSLOCK"), _ => return None, }) @@ -391,7 +404,6 @@ mod tests { KeyCode::PageUp, KeyCode::PageDown, KeyCode::NumLock, - KeyCode::CapsLock, KeyCode::PrintScreen, KeyCode::Minus, KeyCode::Equal, @@ -462,13 +474,15 @@ mod tests { "PAGEUP", "PAGEDOWN", "NUMLOCK", - "CAPSLOCK", "PRINTSCREEN", "MOUSEWHEELDOWN", "MOUSEWHEELUP", ] .map(str::to_string), ); + // The Mac client queues no Caps Lock key (`mac:08a06c`). + #[cfg(not(target_os = "macos"))] + tokens.push("CAPSLOCK".to_string()); tokens.extend( "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=[]\\;',./`" .chars() @@ -488,6 +502,24 @@ mod tests { } } + /// Caps Lock is a key on Windows and Linux (`CAPSLOCK`); the Mac client's modifier handler + /// queues only Shift, Control and Option (`mac:08a06c`), so it names and binds nothing there. + #[test] + fn caps_lock_is_a_key_only_off_the_mac() { + let us = LayoutNames::default(); + let named = key_token(KeyCode::CapsLock, &us); + let parsed = Chord::parse("CAPSLOCK"); + if cfg!(target_os = "macos") { + assert_eq!((named, parsed), (None, None)); + } else { + assert_eq!(named, Some(KeyName::Word("CAPSLOCK"))); + assert_eq!( + parsed.map(|c| c.key), + Some(BindKey::Key(KeyName::Word("CAPSLOCK"))) + ); + } + } + #[test] fn the_codec_round_trips_every_keyboard_token() { let us = LayoutNames::default(); diff --git a/crates/benilla-app/src/bindings/layout.rs b/crates/benilla-app/src/bindings/layout.rs index 97b52b34b..1ba745443 100644 --- a/crates/benilla-app/src/bindings/layout.rs +++ b/crates/benilla-app/src/bindings/layout.rs @@ -4,8 +4,8 @@ //! outside its fixed table, `VK_A`-`VK_Z` and `VK_OEM_*` among them, to //! `MapVirtualKeyA(vk, MAPVK_VK_TO_CHAR)` (`0x42da39`), which returns `VK_A`-`VK_Z`'s own letter //! whatever the layout types there. The Mac client fills its letter and punctuation keys from the -//! live layout, `KeyTranslate` (`0x8a3c3`) on the current `KCHR` (re-read when it changes, -//! `0x8a345`), and fixes its digits by position (`0x5bf320`). Bevy's `KeyboardInput` carries the +//! live layout, `KeyTranslate` (`mac:08a3c3`) on the current `KCHR` (re-read when it changes, +//! `mac:08a345`), and fixes its digits by position (`mac:5bf320`). Bevy's `KeyboardInput` carries the //! physical key and a `logical_key` with Shift applied, so each platform's name is read off the //! raw winit key messages here, before any `Update` reader. @@ -35,7 +35,7 @@ impl Position { /// The keys a layout names: the letters, the digits, the punctuation, the ISO and JIS extras and /// the keypad comma. Windows sends `VK_OEM_102`, `VK_ABNT_C2` and `VK_SEPARATOR` to the layout arm /// like any `VK_OEM_*` key, and the Mac client's table leaves the ISO key `0x0A` and keypad comma -/// `0x5F` to `KeyTranslate` (`-1` at `0x5bf320`). winit 0.30 cannot deliver those on a Mac, as on +/// `0x5F` to `KeyTranslate` (`-1` at `mac:5bf320`). winit 0.30 cannot deliver those on a Mac, as on /// main: it reports `0x0A` as `Backquote` (whose scancode it gives back as `0x32`) and leaves /// `0x5E` and `0x5F` unidentified, so the `IntlBackslash`, `IntlRo` and `NumpadComma` arms never /// fire there. @@ -114,7 +114,7 @@ impl LayoutNames { /// reported (a headless run) keeps its US name. /// /// Deviation: on the Mac and Linux a key its platform reports no character for keeps its US - /// name too, where the Mac reference drops it (`0x8a4aa`), and a Linux dead key not yet pressed + /// name too, where the Mac reference drops it (`mac:08a4aa`), and a Linux dead key not yet pressed /// bare stays bindable. pub(crate) fn name(&self, key: KeyCode) -> Option { let position = position(key)?; @@ -289,13 +289,13 @@ mod windows { /// /// Deviation: a layout that is not ASCII-capable (Hebrew, Greek, Russian, Arabic, Thai) names its /// keys through the ASCII-capable layout macOS pairs with it, as macOS names its own shortcuts, -/// because the reference reads a non-Roman `KCHR`'s byte as MacRoman (`0x8a40f`), which names no +/// because the reference reads a non-Roman `KCHR`'s byte as MacRoman (`mac:08a40f`), which names no /// real key. /// /// Deviation: a character above U+00FF (Czech `ě`, Polish `ł`, Turkish `ş`) names the key by itself -/// because the reference reads `KeyTranslate`'s byte as MacRoman (`0x8a40f`) and keeps it only if -/// it converts to Latin-1 (`CFStringGetBytes`, `0x8a46b`, stored at `0x8a482`), else leaves the -/// slot `-1`, which `0x8a4aa` drops, so such a key is mislabelled or dropped; named by what it +/// because the reference reads `KeyTranslate`'s byte as MacRoman (`mac:08a40f`) and keeps it only if +/// it converts to Latin-1 (`CFStringGetBytes`, `mac:08a46b`, stored at `mac:08a482`), else leaves the +/// slot `-1`, which `mac:08a4aa` drops, so such a key is mislabelled or dropped; named by what it /// types, it stays bindable and readable. #[cfg(any(target_os = "macos", test))] fn mac_name( diff --git a/crates/benilla-world/src/layout_keys.rs b/crates/benilla-world/src/layout_keys.rs index 2e1c8894d..01e9b3039 100644 --- a/crates/benilla-world/src/layout_keys.rs +++ b/crates/benilla-world/src/layout_keys.rs @@ -9,7 +9,7 @@ //! Off macOS, each keyboard message whose logical key is a named key other than its physical one //! takes that named key's code, before Bevy's input collection, so every reader (the bindings, //! the text boxes, the glue screens, `ButtonInput`) sees the layout's key. The Mac client -//! reads its non-character keys off a fixed table on the physical keycode (`0x5bf320`), so macOS +//! reads its non-character keys off a fixed table on the physical keycode (`mac:5bf320`), so macOS //! keeps them physical. A letter, digit or punctuation key keeps its code too: 1.12 names it by //! the virtual key the layout makes it, a digit as itself (`0x42d81c`) and a letter or punctuation //! key through `MapVirtualKeyA` (`0x42da39`), which the game's key namer reads, not by a named diff --git a/docs/DEVIATIONS.md b/docs/DEVIATIONS.md index 5164c95b7..89d8ed3d9 100644 --- a/docs/DEVIATIONS.md +++ b/docs/DEVIATIONS.md @@ -29,7 +29,7 @@ A setting boots at the reference's default unless it is listed here (`docs/METHO Each entry is the first sentence of a `Deviation:` comment, linking to where it lives. -- [benilla-app](#benilla-app): 254 +- [benilla-app](#benilla-app): 255 - [benilla-assets](#benilla-assets): 6 - [benilla-blp](#benilla-blp): 1 - [benilla-formats](#benilla-formats): 24 @@ -74,14 +74,15 @@ Each entry is the first sentence of a `Deviation:` comment, linking to where it #### bindings -- A press with Super (Cmd) held runs no binding because that way a system chord such as Cmd+Tab or Cmd+Q never acts in game. The reference has no such modifier and runs the bare key's binding: the Mac enqueues the key with Command held and skips only its text (`0x8a519`), and Windows hands `VK_LWIN`/`VK_RWIN` to the layout arm (`0x42da39`) like any key. ([`src/bindings.rs:19`](../crates/benilla-app/src/bindings.rs#L19)) +- A press with Super (Cmd) held runs no binding because that way a system chord such as Cmd+Tab or Cmd+Q never acts in game. The reference has no such modifier and runs the bare key's binding: the Mac enqueues the key with Command held and skips only its text (`mac:08a51e`), and Windows hands `VK_LWIN`/`VK_RWIN` to the layout arm (`0x42da39`) like any key. ([`src/bindings.rs:19`](../crates/benilla-app/src/bindings.rs#L19)) - A trackpad's `Pixel` deltas are carried until they make a line, and step at most once per event (`crate::ui_script::Record::Wheel`). The reference steps on every `WM_MOUSEWHEEL`, whatever its delta (`0x76c1a9`); the carry is kept because a carried trackpad does not zoom or scroll at its event rate. ([`src/bindings.rs:637`](../crates/benilla-app/src/bindings.rs#L637)) - Latches die with the VM that made them, because a latch names a command of that VM's table and releasing it against a new one would run another load's up-half. The reference keeps a held key running through `ReloadUI`; here it re-latches on its next press. The held bits die with it, as the key's Stop would have cleared them. ([`src/bindings.rs:682`](../crates/benilla-app/src/bindings.rs#L682)) -- The Windows key table stops at `VK_F12` and drops later F-keys; they are named here because that way a stored `F13`-`F24` can be pressed. ([`src/bindings/chord.rs:165`](../crates/benilla-app/src/bindings/chord.rs#L165)) -- On the Mac and Linux a key its platform reports no character for keeps its US name too, where the Mac reference drops it (`0x8a4aa`), and a Linux dead key not yet pressed bare stays bindable. ([`src/bindings/layout.rs:116`](../crates/benilla-app/src/bindings/layout.rs#L116)) +- The Z key is named `Z` on a Mac too, because the Mac client uppercases only `a`-`y` (`mac:08a3df`) and so names it `z`, which no binding can press; that fault would leave a binding to `Z`, the stock `TOGGLESHEATH` key, unreachable there. ([`src/bindings/chord.rs:111`](../crates/benilla-app/src/bindings/chord.rs#L111)) +- The Windows key table stops at `VK_F12` and drops later F-keys; they are named here because that way a stored `F13`-`F24` can be pressed. ([`src/bindings/chord.rs:171`](../crates/benilla-app/src/bindings/chord.rs#L171)) +- On the Mac and Linux a key its platform reports no character for keeps its US name too, where the Mac reference drops it (`mac:08a4aa`), and a Linux dead key not yet pressed bare stays bindable. ([`src/bindings/layout.rs:116`](../crates/benilla-app/src/bindings/layout.rs#L116)) - A character above U+00FF names the key by itself because the reference's `MapVirtualKeyA` returns an ANSI code-page byte, which `0x42da4b` drops past `0xff` and the namer's UTF-8 encoder (`0x41abb0`) otherwise reads as Latin-1, so such a key is mislabelled or dropped; named by what it types, it stays bindable and readable. ([`src/bindings/layout.rs:216`](../crates/benilla-app/src/bindings/layout.rs#L216)) -- A layout that is not ASCII-capable (Hebrew, Greek, Russian, Arabic, Thai) names its keys through the ASCII-capable layout macOS pairs with it, as macOS names its own shortcuts, because the reference reads a non-Roman `KCHR`'s byte as MacRoman (`0x8a40f`), which names no real key. ([`src/bindings/layout.rs:290`](../crates/benilla-app/src/bindings/layout.rs#L290)) -- A character above U+00FF (Czech `ě`, Polish `ł`, Turkish `ş`) names the key by itself because the reference reads `KeyTranslate`'s byte as MacRoman (`0x8a40f`) and keeps it only if it converts to Latin-1 (`CFStringGetBytes`, `0x8a46b`, stored at `0x8a482`), else leaves the slot `-1`, which `0x8a4aa` drops, so such a key is mislabelled or dropped; named by what it types, it stays bindable and readable. ([`src/bindings/layout.rs:295`](../crates/benilla-app/src/bindings/layout.rs#L295)) +- A layout that is not ASCII-capable (Hebrew, Greek, Russian, Arabic, Thai) names its keys through the ASCII-capable layout macOS pairs with it, as macOS names its own shortcuts, because the reference reads a non-Roman `KCHR`'s byte as MacRoman (`mac:08a40f`), which names no real key. ([`src/bindings/layout.rs:290`](../crates/benilla-app/src/bindings/layout.rs#L290)) +- A character above U+00FF (Czech `ě`, Polish `ł`, Turkish `ş`) names the key by itself because the reference reads `KeyTranslate`'s byte as MacRoman (`mac:08a40f`) and keeps it only if it converts to Latin-1 (`CFStringGetBytes`, `mac:08a46b`, stored at `mac:08a482`), else leaves the slot `-1`, which `mac:08a4aa` drops, so such a key is mislabelled or dropped; named by what it types, it stays bindable and readable. ([`src/bindings/layout.rs:295`](../crates/benilla-app/src/bindings/layout.rs#L295)) - The reference saves a full snapshot (`bindings-cache.wtf`); a diff is stored because a full snapshot would leave every command added later unbound for a saved file. ([`src/bindings/store.rs:13`](../crates/benilla-app/src/bindings/store.rs#L13)) #### blob_shadow