feat(gestures): Add mouse gesture support with scroll-wheel combos#922
Open
landn172 wants to merge 5 commits into
Open
feat(gestures): Add mouse gesture support with scroll-wheel combos#922landn172 wants to merge 5 commits into
landn172 wants to merge 5 commits into
Conversation
Hold a configurable trigger button (e.g. middle click) and move the mouse to execute system actions by direction. Supports up/down/left/ right with independent action bindings per gesture. - GestureBinding data model: trigger + 4 directional SystemShortcut actions, threshold, enabled flag - GestureProcessor state machine: idle → pending → active, dominant-axis direction detection with 1.5× diagonal rejection, click replay on threshold miss - MouseInteractionSessionController: gestureMotionHandler callback + setGestureTracking() keep-alive flag - ButtonCore: intercept matching button events before InputProcessor; clearState on disable - Options/Constants: gesture bindings persistence via UserDefaults - PreferencesButtonsViewController: segmented Bindings/Gestures switcher reusing KeyRecorder - GestureTableCellView: programmatic cell with KeyPreview + 4 direction popups - Localizable.xcstrings: bindings, gestures, gestureNone, direction keys (11 languages) - GestureProcessorTests: state machine, direction resolution, Codable round-trip, motion tap hooks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add GestureInputMode enum (.mouseMovement / .scrollWheel) to GestureBinding with backward-compatible Codable (old JSON without key defaults to .mouseMovement) - Add handleScrollEvent() to GestureProcessor: consumes scroll events in both .pending (accumulate delta, resolve direction) and .active (prevent smooth-scrolling pipeline leak) states - Guard handleMotionEvent and startGestureTracking to only run for .mouseMovement bindings - Hook GestureProcessor.handleScrollEvent into ScrollCore.scrollEventCallBack before trackpad check - Fix footer layout: deactivate conflicting storyboard constraints and repin +/- buttons to trailing edge so segmented control doesn't overlap them - Add right-click context menu delete to GestureTableCellView - Add input mode segmented control (Movement / Scroll) to GestureTableCellView with onInputModeChanged callback - Wire updateGestureInputMode() in PreferencesButtonsViewController - Add gestureMouseMovement, gestureScrollWheel, delete localization keys (en/zh-Hans/zh-Hant/ja) - Add 11 new unit tests covering scroll gesture state machine, backward-compatible Codable, and active-state scroll leak prevention Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…irection only Movement (↑↓←→) and Scroll (↑↓) are now separate, simultaneously active configs on the same gesture binding — remove the single-mode toggle entirely. - GestureBinding: remove GestureInputMode enum and inputMode field; add scrollUpAction/scrollDownAction/scrollThreshold fields; manual encode(to:) skips legacy inputMode key; backward-compatible decoder ignores old inputMode - GestureProcessor: independent pendingScrollDY accumulator (separate from motion DX/DY); handleScrollEvent uses scrollAction(for:) and scrollThreshold; handleMotionEvent guards on hasAnyMovementAction; motion tap only started when binding has movement actions; pendingScrollDY reset on all pending→idle transitions - GestureTableCellView: remove inputMode segmented control; show two labeled sections side-by-side (Movement 4-dir, Scroll 2-dir); separate popup/action selectors and callbacks for each section - PreferencesButtonsViewController: replace updateGestureInputMode with updateGestureScrollAction; wire onScrollActionChanged callback; row height 150 - Localizable.xcstrings: rename gestureMouseMovement→gestureMovement, gestureScrollWheel→gestureScroll (section header labels) - GestureProcessorTests: update helpers, replace inputMode tests with threshold/scroll-action tests, add accumulator-independence test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two root causes: 1. Silent event consumption: when scrollDownAction is nil but scrollUpAction is set, hasAnyScrollAction=true caused all scroll-down events to be consumed (returning true) without any action firing. User saw no scrolling and no action — perceived as "scroll conflict". Fix: direction-aware consume. Check binding.scrollAction(for: eventDirection) before accumulating. If nil for that direction, immediately return false (let scroll pass through normally). Reset accumulator on direction change. 2. Smooth-scroll mouse delta: reading scrollWheelEventDeltaAxis1 (integer) gives 0 for continuous/smooth scroll devices. No accumulation → threshold never reached. Fix: prefer scrollWheelEventFixedPtDeltaAxis1 (float fixedPt field, same priority logic as ScrollEvent.initEvent), fall back to integer field. Both pending and active states updated to use the same direction-aware logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hold Previously, after a scroll gesture fired the state became .active and all subsequent scroll events in configured directions were consumed without firing. This caused a 5+ second apparent freeze: the user had to fully release the trigger button before the other direction would work again. Fix: in .active state, scroll gestures now re-fire on every threshold crossing. Hold button + scroll up → fires, scroll down → fires, scroll up → fires again. Directions with no configured action still pass through immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Summary
This PR adds a full mouse gesture system to Mos, allowing users to bind custom actions to directional mouse movements combined with button holds and scroll-wheel inputs.
Features added:
GestureProcessorTestscovers core gesture detection logicKey files:
Mos/InputEvent/GestureBinding.swift— Gesture data model and binding definitionsMos/InputEvent/GestureProcessor.swift— Core gesture detection and direction classificationMos/Windows/PreferencesWindow/ButtonsView/GestureTableCellView.swift— UI for gesture table rowsMos/Options/Options.swift— New gesture-related config keysMosTests/GestureProcessorTests.swift— Unit testsHow to test
ScreenShot
Related issues
#917