Skip to content

Fix dock-swipe gestures on macOS 27 - #1918

Draft
SalmonC wants to merge 4 commits into
noah-nuebling:masterfrom
SalmonC:codex/macos27-dock-swipe-fix
Draft

Fix dock-swipe gestures on macOS 27#1918
SalmonC wants to merge 4 commits into
noah-nuebling:masterfrom
SalmonC:codex/macos27-dock-swipe-fix

Conversation

@SalmonC

@SalmonC SalmonC commented Jul 9, 2026

Copy link
Copy Markdown

Summary

  • augment synthetic dock-swipe events on macOS 27 with the raw IOKit HID payload required by Dock
  • anchor modified-drag dock-swipe events to the drag-origin display and end the synthetic stream when the real pointer crosses displays
  • avoid stale delayed end-event replays on the macOS 27 augmented path
  • add a main-display CVDisplayLink fallback for active-display link creation failures
  • document root cause, source, multi-display behavior, and validation in Notes/macOS27DockSwipe.md

Source / attribution

This builds on PR #1895 by @megyland:
#1895

That PR identified the macOS 27 dock-swipe breakage and the need to embed raw HID payload data in serialized CGEvent field 4205, referencing the InstantSpaceSwitcher / FasterSwiper reverse-engineering work.

Root cause

On macOS 27, Dock no longer appears to act on only the public synthetic CGEvent gesture fields for dock swipes. It expects the raw IOKit HID gesture payload inside the serialized event. For modified-drag gestures on multi-display setups, Dock also appears to tie the gesture stream to the payload position/display, so continuing one synthetic stream after the real cursor crosses displays can leave Dock's gesture state stuck.

Validation

  • git diff --cached --check
  • xcodebuild -project "/tmp/mac-mouse-fix/Mouse Fix.xcodeproj" -scheme "App - Release" -configuration Release -derivedDataPath "/tmp/MacMouseFix-pr-DerivedData" CODE_SIGNING_ALLOWED=NO build
  • local signed install tested on macOS 27:
    • single-display Spaces switching
    • dual-display drag with cursor crossing displays mid-drag
    • repeated cross-display drags without losing subsequent gestures
    • cursor visibility/position after release
    • scroll direction and other helper features

megyland and others added 4 commits July 1, 2026 14:23
On macOS 27 (Tahoe beta), the Dock server no longer acts on the public
CGEvent gesture fields for *synthetic* dock-swipe events. As a result the
Mission Control / Spaces / Show-Desktop gestures produced by
TouchSimulator.postDockSwipeEventWithDelta: silently do nothing (noah-nuebling#1892).

The Dock server now requires the raw IOKit HID event payload to be embedded
in the serialized CGEvent (field 4205). This ports the workaround the
InstantSpaceSwitcher / FasterSwiper community worked out
(jurplel/InstantSpaceSwitcher#72): before posting, serialize the event with
CGEventCreateData(), splice in a hand-built IOHID gesture payload (16.16
fixed-point), rebuild with CGEventCreateFromData(), and post that instead.

Gated behind a macOS 27+ check, so behavior on older systems is unchanged.

Verified on macOS 27.0 (26A5353q): with augmentation the Mission Control
(vertical) swipe triggers correctly; the un-augmented path does nothing,
confirming the augmentation is both necessary and sufficient.

Refs: noah-nuebling#1892, jurplel/InstantSpaceSwitcher#72

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zhaoqiman added a commit to zhaoqiman/mac-mouse-fix that referenced this pull request Jul 13, 2026
…ed CGEvent offsets

CGEventSetIOHIDEvent() attaches an IOHIDEvent to a CGEvent by poking the
pointer into the opaque CGEvent/CGSEventRecord struct at hardcoded offsets
(0x18 / 0xd0). Those offsets were never a stable ABI, and the struct layout
shifted on macOS 27, so the pointer ends up at the wrong address and Dock
never sees a real IOHIDEvent - which is why every synthetic dock-swipe
gesture (Spaces, Mission Control, Show Desktop, Launchpad) silently stopped
doing anything. See noah-nuebling#1871 and the pile of duplicates (noah-nuebling#1873, noah-nuebling#1878, noah-nuebling#1887,
noah-nuebling#1891, noah-nuebling#1892, noah-nuebling#1919, ...).

SkyLight.framework is already linked into the Mac Mouse Fix and Mac Mouse
Fix Helper targets, so instead of poking memory ourselves we can just
resolve Apple's own SLEventSetIOHIDEvent with dlsym(RTLD_DEFAULT, ...) and
call that. Using RTLD_DEFAULT instead of dlopen()'ing the framework again
since it's already loaded in-process. Falls back to the old offset writer
if the symbol is ever missing (now with a DDLogWarn so it's not a silent
failure this time).

Tested on macOS 27 - Spaces/Mission Control/Show Desktop click-and-drag
work again.

Same root cause as noah-nuebling#1920, noah-nuebling#1912, noah-nuebling#1916 (also switch to
SLEventSetIOHIDEvent); related to noah-nuebling#1895, noah-nuebling#1918.
zhaoqiman added a commit to zhaoqiman/mac-mouse-fix that referenced this pull request Jul 13, 2026
…ed CGEvent offsets

CGEventSetIOHIDEvent() attaches an IOHIDEvent to a CGEvent by poking the
pointer into the opaque CGEvent/CGSEventRecord struct at hardcoded offsets
(0x18 / 0xd0). Those offsets were never a stable ABI, and the struct layout
shifted on macOS 27, so the pointer ends up at the wrong address and Dock
never sees a real IOHIDEvent - which is why every synthetic dock-swipe
gesture (Spaces, Mission Control, Show Desktop, Launchpad) silently stopped
doing anything. See noah-nuebling#1871 and the pile of duplicates (noah-nuebling#1873, noah-nuebling#1878, noah-nuebling#1887,
noah-nuebling#1891, noah-nuebling#1892, noah-nuebling#1919, ...).

SkyLight.framework is already linked into the Mac Mouse Fix and Mac Mouse
Fix Helper targets, so instead of poking memory ourselves we can just
resolve Apple's own SLEventSetIOHIDEvent with dlsym(RTLD_DEFAULT, ...) and
call that. Using RTLD_DEFAULT instead of dlopen()'ing the framework again
since it's already loaded in-process. Falls back to the old offset writer
if the symbol is ever missing (now with a DDLogWarn so it's not a silent
failure this time).

Tested on macOS 27 - Spaces/Mission Control/Show Desktop click-and-drag
work again.

Same root cause as noah-nuebling#1920, noah-nuebling#1912, noah-nuebling#1916 (also switch to
SLEventSetIOHIDEvent); related to noah-nuebling#1895, noah-nuebling#1918.
zhaoqiman added a commit to zhaoqiman/mac-mouse-fix that referenced this pull request Jul 14, 2026
…ed CGEvent offsets

CGEventSetIOHIDEvent() attaches an IOHIDEvent to a CGEvent by poking the
pointer into the opaque CGEvent/CGSEventRecord struct at hardcoded offsets
(0x18 / 0xd0). Those offsets were never a stable ABI, and the struct layout
shifted on macOS 27, so the pointer ends up at the wrong address and Dock
never sees a real IOHIDEvent - which is why every synthetic dock-swipe
gesture (Spaces, Mission Control, Show Desktop, Launchpad) silently stopped
doing anything. See noah-nuebling#1871 and the pile of duplicates (noah-nuebling#1873, noah-nuebling#1878, noah-nuebling#1887,
noah-nuebling#1891, noah-nuebling#1892, noah-nuebling#1919, ...).

Now prefers Apple's own SLEventSetIOHIDEvent, resolved once via the
project's existing MFLoadSymbol_native(kMFFrameworkSkyLight, ...) helper
(Shared/Utility/PrivateFunctions) instead of a hand-rolled dlsym call.
Also retains the IOHIDEventRef on this path, same as the old offset-writer
did, since SLEventSetIOHIDEvent's retain contract isn't documented and an
extra retain is safer than an under-retain.

The offset-writer is kept as a fallback only for pre-macOS-27, where it
was actually validated - on 27+ it's skipped instead of executed, since
those offsets are already known to be wrong there and running it would
provide no real safety net, just continued exposure to writing an
unvalidated address.

Tested on macOS 27 - Spaces/Mission Control/Show Desktop click-and-drag
all work again after this.

Same root cause as noah-nuebling#1920, noah-nuebling#1912, noah-nuebling#1916 (also switch to
SLEventSetIOHIDEvent); related to noah-nuebling#1895, noah-nuebling#1918.
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