Skip to content

macOS input improvements: control groups, Cmd-as-Ctrl, trackpad camera pan - #22

Open
redbeard wants to merge 4 commits into
ammaarreshi:mainfrom
redbeard:macos-input-improvements
Open

macOS input improvements: control groups, Cmd-as-Ctrl, trackpad camera pan#22
redbeard wants to merge 4 commits into
ammaarreshi:mainfrom
redbeard:macos-input-improvements

Conversation

@redbeard

Copy link
Copy Markdown

Summary

Four related macOS input fixes and features, found while chasing why Ctrl+number control groups were dead on Mac:

  • sagepatch: move window snap off Ctrl+1..5 — the SagePatch SDL_PollEvent interposer consumed Ctrl+1..5 for window snapping, silently swallowing the game's control-group hotkeys on every run.sh launch (the dev run script doesn't load SagePatch, which made the bug look intermittent). Snap now lives on Cmd+Option+1..5.
  • macos: Cmd acts as Ctrl — Cmd+1..0 assigns/selects control groups and works for all other Ctrl combos; physical Ctrl unchanged; Linux Super key unaffected.
  • macos: reset keyboard state on focus gain — SDL3 has no DirectInput-style KEY_LOST, so modifier key-ups swallowed while Cmd-Tabbed away left Alt/Ctrl stuck down. Mirrors the Win32 device-loss reset.
  • input: trackpad two-finger scroll pans the camera — precise-delta detection separates trackpad scrolls from wheel notches (wheels keep classic zoom); honors natural scrolling; Option+scroll zooms; pan speed follows the in-game scroll-speed option; UI listbox scrolling unaffected.

Testing

  • Verified on macOS with the deployed ZH build: Cmd+1/Ctrl+1 create groups (log-traced end to end from SDL scancodes to CREATE_TEAM messages), trackpad pan verified in-game in all four directions, mouse-free zoom via Option+scroll, menus still scroll.
  • Both GeneralsXZH and GeneralsX targets build clean; shared Core changes keep the extra wheel-message arguments invisible to consumers that read only the first three.

🤖 Generated with Claude Code

redbeard and others added 4 commits July 25, 2026 13:55
SDL3 has no DirectInput-style KEY_LOST, so modifier key-up events
swallowed while unfocused (Cmd-Tab, Mission Control) left Alt/Ctrl
stuck down and silently broke modifier hotkeys. Mirror the Win32
device-loss path by calling resetKeys() on SDL_EVENT_WINDOW_FOCUS_GAINED,
and guard refreshAltKeys() against focus events arriving before the
message stream exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SagePatch's SDL_PollEvent interposer consumed Ctrl+1..5 for window
snapping, which silently swallowed the game's control-group assignment
hotkeys on every launch that loads the patch. Cmd+Option+number is
unbound in the game (Cmd now acts as Ctrl in-game, so bare Cmd+number
must stay free for control groups too).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cmd+1..0 now assigns/uses control groups just like Ctrl+1..0, and
Cmd works for every other Ctrl combo (force attack, etc). The
physical Ctrl key keeps working unchanged. macOS only: on Linux the
GUI scancode is the desktop's Super key and stays unmapped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Trackpad scrolls are distinguished from mouse-wheel notches by their
precise fractional deltas and horizontal axis (with a 300ms latch for
mid-gesture whole-number ticks), un-flipped per SDL's natural-scrolling
direction flag, and carried as two extra arguments on the existing
MSG_RAW_MOUSE_WHEEL message so UI listbox scrolling keeps working.
LookAtXlat pans via userScrollBy using the same magnitude convention
and speed options as the other scroll modes; Alt/Option + scroll zooms,
since a trackpad has no discrete wheel, and real mouse wheels keep the
classic zoom. Horizontal-only pan events skip the window manager so it
does not misread zero vertical spin as a wheel-down.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
andyfangdz added a commit to andyfangdz/Generals-Mac-iOS-iPad that referenced this pull request Aug 4, 2026
SDL3 has no DirectInput-style KEY_LOST, so a modifier key-up that
happens while the window is unfocused - Cmd-Tab, Mission Control, any
app switch with a modifier held - never reaches the engine. Alt or Ctrl
stays latched down afterwards and silently breaks every modifier hotkey,
control groups included, until the key is pressed and released again.

Reset keyboard state when focus returns, mirroring what the Win32 device
loss path already does. Both games poll the same way, so both get it.

resetKeys() calls refreshAltKeys(), which posts to TheMessageStream, and
a focus event can land before that exists - so guard it. Without the
guard this fix would trade a stuck modifier for a null dereference
during startup.

Found by redbeard, ammaarreshi PR ammaarreshi#22.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andyfangdz added a commit to andyfangdz/Generals-Mac-iOS-iPad that referenced this pull request Aug 6, 2026
A trackpad had only the wheel's job: two fingers zoomed, and there was
no way to push the camera around without going to the screen edge or the
keyboard. Now two fingers pan, so the map follows the fingers. A
trackpad has no detent to zoom with, so Alt/Option + scroll takes that
job, and a real wheel is untouched and still zooms.

The two cannot be mistaken for each other, because they are not being
told apart - the gesture is simply reassigned per device. What is being
told apart is the device: a trackpad reports precise fractional deltas
and a horizontal axis, a wheel ticks in whole notches on Y alone, and a
latch keeps the middle of a gesture from flipping back when a delta
lands on a whole number. A high resolution mouse that reports the same
way will pan rather than zoom; that is the known edge of the heuristic.

Synthetic events are excluded outright, which redbeard's original does
not do. The iOS touch translator turns a pinch into a wheel event, so it
flows through exactly this code. It emits whole steps with no horizontal
component and so classifies correctly on the arithmetic alone - but only
by coincidence, and the latch could still catch a pinch right after a
Magic Keyboard trackpad scroll and pan instead of zoom. SDL_TOUCH_MOUSEID
states that the event did not come from a pointing device, so iPad pinch
is safe by construction rather than by luck.

Pan deltas ride along as two extra arguments on MSG_RAW_MOUSE_WHEEL,
after the existing three, so anything reading only the first three is
unaffected - which is how UI listbox scrolling keeps working. A sideways
pan carries no vertical spin, and the window translator would read that
zero as a wheel-down, so it lets those through to the camera instead.

From redbeard, ammaarreshi PR ammaarreshi#22. Ported by hand rather than
cherry-picked: the upstream merge unified LookAtXlat and WindowXlat into
Core, so the logic lives once now instead of once per game. The one
regression that review had flagged - taking the un-flipped value for
wheelPos, inverting wheel zoom under natural scrolling - turned out to
be moot, since this tree already un-flips there.

Magnitude follows the other scroll modes: 120 * keyboardScrollFactor,
scaled by the user's scroll speed options.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@eadra

eadra commented Aug 17, 2026

Copy link
Copy Markdown

Confirming the diagnosis and the snap rebind on macOS (Apple Silicon, macos-vulkan preset, Zero Hour).

We hit exactly the symptom described — control groups 1-5 dead, 6-0 fine — and tracked it to the same cause: the SDL_PollEvent interposer returning true for Ctrl+1..5, so the game never received the events. Confirmed it only reproduces when SagePatch is actually injected, which matches your note about run.sh vs. the dev script and explains the "intermittent" feel.

Cherry-picked 0a2e4f9 (snap rebind) on its own. Builds clean, the runtime banner reports Cmd+Option+1..5 window position, and Ctrl+1 assign/recall works in a live skirmish. Cmd+Option+1..5 hasn't collided with anything in the game so far in our testing.

Scope caveat so this isn't read as more than it is: we took only that one commit, so this is not a signal on the other three (Cmd-as-Ctrl, focus-gain keyboard reset, trackpad camera pan) — those are untested on our side.

Thanks for chasing it down — the root cause writeup made this a five-minute fix on our end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants