How OpenLogi Dispatches Scroll-Wheel Gestures and Handles Per-Direction Bindings
OpenLogi normalizes platform-specific scroll deltas into a unified MouseEvent::Scroll, maps them to directional ButtonId variants, and resolves per-direction bindings through Config::effective_bindings before synthesizing output via the inject layer.
OpenLogi is a cross-platform mouse gesture enhancement tool that intercepts raw hardware events and dispatches custom actions. The scroll wheel gesture dispatch system enables users to bind distinct commands to each scroll direction—vertical (up/down) and horizontal (left/right)—independently. This article examines the implementation details in the AprilNEA/OpenLogi repository, from platform-specific normalization in the hook crate to binding resolution in the agent.
The Dispatch Pipeline: From Hardware to Application
The scroll-wheel dispatch chain follows a strict five-stage pipeline across four crates. First, the hook (openlogi-hook) captures raw OS events and normalizes direction conventions. Second, the IPC contract (openlogi-ipc) transports a MouseEvent::Scroll { delta_x, delta_y } to the agent. Third, the agent maps the signed deltas to specific ButtonId variants. Fourth, the core configuration (openlogi-core) resolves the binding via Config::effective_bindings. Finally, the inject layer (openlogi-inject) synthesizes the OS-level scroll event.
Platform-Specific Normalization in the Hook Layer
Each platform reports scroll deltas with different sign conventions and units. The hook crate unifies these into a consistent representation where positive values indicate "scroll down/right" and negative values indicate "scroll up/left".
Windows Direction Inversion
Windows reports positive WM_MOUSEWHEEL deltas for "scroll up," which contradicts the convention used by macOS and Linux. The helper function wheel_forward_scrolls_up_like_other_platforms in openlogi-hook/src/windows.rs inverts the sign to maintain consistency.
fn wheel_forward_scrolls_up_like_other_platforms(delta: i16) -> i16 {
// Windows reports positive delta for "scroll up", we invert it.
-delta
}
macOS Delta Extraction
On macOS, the hook extracts three possible encodings—pixel, line, and precise deltas—from the native event. The logic in openlogi-hook/src/macos.rs preserves the correct sign convention without inversion, as the native reporting already aligns with the unified schema.
Linux Axis Codes
The Linux implementation reads relative axis codes from the input subsystem. In openlogi-hook/src/linux.rs, the hook monitors REL_WHEEL for vertical movement and REL_HWHEEL for horizontal movement, converting these hardware signals into the standard MouseEvent::Scroll structure.
Mapping Scroll Events to ButtonId Variants
When the agent receives a MouseEvent::Scroll, it converts the signed delta_x and delta_y values into discrete directional identifiers. The ButtonId enum, defined in openlogi-core/src/binding/action.rs, provides four variants for scroll-wheel gestures:
ScrollUp– vertical tick upwards (negativedelta_y)ScrollDown– vertical tick downwards (positivedelta_y)HorizontalScrollLeft– horizontal tick leftwards (negativedelta_x)HorizontalScrollRight– horizontal tick rightwards (positivedelta_x)
The agent performs the mapping using a simple sign check:
fn scroll_to_button_id(delta_x: i8, delta_y: i8) -> ButtonId {
if delta_y > 0 {
ButtonId::ScrollDown
} else if delta_y < 0 {
ButtonId::ScrollUp
} else if delta_x > 0 {
ButtonId::HorizontalScrollRight
} else {
ButtonId::HorizontalScrollLeft
}
}
Resolving Per-Direction Bindings via Config::effective_bindings
User configurations are stored as TOML and expanded at runtime. The Config::effective_bindings method in openlogi-core builds a HashMap<ButtonId, Action> that allows O(1) lookup of the command associated with each scroll direction.
let button = scroll_to_button_id(dx, dy);
let action = config.effective_bindings().get(&button);
If the map contains an entry for the resolved ButtonId, the associated Action (e.g., Action::ScrollUp, Action::HorizontalScrollRight) is dispatched. This architecture ensures that vertical and horizontal directions, as well as positive and negative deltas, are treated as completely independent input events with distinct binding slots.
Synthesizing Output with the Inject Layer
Once the agent resolves the action, the inject crate executes the final step. The post_scroll and post_smooth_scroll functions in openlogi-inject synthesize the OS-level scroll event, posting it to the target application window. This completes the cycle: raw hardware input was intercepted, mapped to a semantic direction, resolved against user configuration, and finally delivered as a synthetic gesture.
// Inject → post the scroll tick
openlogi_inject::post_scroll(ScrollDelta { x: dx, y: dy });
Summary
- Hook normalization: Platform-specific code in
openlogi-hook/src/windows.rs,macos.rs, andlinux.rsunifies scroll direction conventions, including special sign inversion on Windows. - Delta mapping: The agent converts signed
delta_xanddelta_yvalues into four discreteButtonIdvariants (ScrollUp,ScrollDown,HorizontalScrollLeft,HorizontalScrollRight). - Binding resolution:
Config::effective_bindingsinopenlogi-coreprovides a lookup map that supports independent actions for each scroll direction. - Event injection: The
openlogi-injectcrate synthesizes final scroll events viapost_scrollandpost_smooth_scrollto deliver the configured behavior.
Frequently Asked Questions
Why does OpenLogi invert scroll direction on Windows?
Windows natively reports positive deltas for "scroll up," while macOS and Linux use positive deltas for "scroll down." To maintain a consistent cross-platform sign convention, the wheel_forward_scrolls_up_like_other_platforms helper in openlogi-hook/src/windows.rs inverts the Windows value, ensuring the agent receives uniform input regardless of operating system.
How does OpenLogi distinguish between horizontal and vertical scroll events?
The hook layer extracts axis-specific data—REL_WHEEL versus REL_HWHEEL on Linux, or the corresponding native APIs on Windows and macOS—to populate delta_y (vertical) and delta_x (horizontal) fields in MouseEvent::Scroll. The agent then checks these fields in sequence: if delta_y is non-zero, it selects a vertical ButtonId; otherwise, it evaluates delta_x for horizontal scrolling.
Can I bind different actions to scroll up versus scroll down?
Yes. The configuration system treats each direction as a distinct ButtonId variant. By defining separate entries in your TOML configuration for ScrollUp and ScrollDown (or the horizontal equivalents), Config::effective_bindings will map each to a different Action, allowing independent commands for every scroll direction.
What happens if no binding exists for a scroll direction?
If Config::effective_bindings returns None for the resolved ButtonId, the agent takes no action for that event. The scroll wheel behaves as if unmodified, or the event may be passed through depending on the specific runtime configuration, effectively leaving the direction unbound.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →