Difference Between openlogi-hook and openlogi-inject Crates in OpenLogi
openlogi-hook captures raw OS input events while openlogi-inject synthesizes them, together providing OpenLogi with bidirectional control over mouse and keyboard streams while maintaining clean architectural separation.
The OpenLogi project (available at AprilNEA/OpenLogi) implements advanced input remapping through a modular Rust workspace. Understanding the distinction between the openlogi-hook and openlogi-inject crates is essential for developers extending the platform or debugging input handling.
Core Architectural Responsibilities
These crates serve opposite but complementary functions within the OpenLogi architecture.
Input Capture with openlogi-hook
The openlogi-hook crate handles input capture—listening to raw operating system events before they reach applications. It implements low-level event taps on each platform: CGEventTap on macOS, evdev grab with uinput re-injection on Linux, and WH_MOUSE_LL on Windows.
According to the crate overview in openlogi-hook/src/lib.rs, this crate is part of the agent process (openlogi-agent) and serves as the source of raw events flowing to the GUI, hook logic, and DPI-cycle calculations. The hook owns a watchdog that safely disables the tap when Accessibility permissions are revoked, as detailed in openlogi-hook/AGENTS.md.
Event Synthesis with openlogi-inject
The openlogi-inject crate handles input synthesis—translating logical Action objects from the core configuration into native OS events. It dispatches to platform-specific implementations: CGEvent/NSEvent on macOS, uinput/D-Bus on Linux, and SendInput on Windows.
As implemented in openlogi-inject/src/lib.rs, this crate provides a platform-agnostic library used by the core to execute actions. It is imported by both the agent (to send events) and the core (for unit-testable effect simulation), keeping the core schema platform-neutral.
Platform-Specific Implementation Details
How openlogi-hook Taps into the OS
The capture layer isolates OS-specific I/O from the core and UI. Key platform implementations include:
openlogi-hook/src/macos.rs– Contains theCGEventTaplogic for macOS event interceptionopenlogi-hook/src/linux.rs– Implements Linuxevdevgrab anduinputhandlingopenlogi-hook/src/lib.rs– Exposes the publicHookAPI andEventDispositiontype
The primary types include Hook, HookEvent, MouseEvent, KeyEvent, EventDevice, and EventDisposition, which allows the callback to decide whether to pass events through or suppress them.
How openlogi-inject Dispatches Actions
The injection layer translates high-level Action objects into hardware events without requiring real hardware for testing. The architecture includes:
openlogi-inject/src/inject.rs– Contains the coreexecutefunction that dispatches to platform modulesopenlogi-inject/src/lib.rs– Provides crate description and public re-exports- Platform modules (
inject/macos.rs,inject/linux.rs,inject/windows.rs) – Handle actual OS-specific event synthesis
Key types include execute, HeldChord, HeldKey, and HeldOutput, which manage the state of injected key combinations.
Crate Dependencies and Relationships
The dependency relationship is asymmetric:
openlogi-hookdepends onopenlogi-inject– When the hook captures events that trigger synthetic actions (such as a gesture generating a click), it uses the inject crate. This dependency is declared in the hook'sCargo.toml.openlogi-injectis independent – It only requires core types (Action,KeyCombo, etc.) and contains no capture code.
This separation permits unit testing of action-to-event translation without a physical input device, as noted in the comments at the top of openlogi-inject/src/lib.rs.
Practical Usage Examples
Capturing Raw Input Events
To observe mouse movements and keyboard presses without modifying them, use the Hook type with EventDisposition::PassThrough:
use openlogi_hook::{Hook, EventDisposition};
fn main() {
// Start the hook; the closure receives every mouse/keyboard event.
let hook = Hook::start(|event| {
println!("Observed: {event:?}");
// Forward the event unchanged.
EventDisposition::PassThrough
})
.expect("failed to install hook");
// … run your application …
// Clean shutdown.
hook.stop();
}
Source: openlogi-hook/examples/print_events.rs
Synthesizing Keyboard Shortcuts
To inject a keyboard shortcut corresponding to an Action, use the execute function:
use openlogi_core::binding::{Action, KeyCombo};
use openlogi_inject::execute;
// Build a simple Action that presses Cmd+Shift+S (macOS) / Ctrl+Shift+S (Linux/Windows).
let action = Action::KeyPress {
combo: KeyCombo::new()
.with_command(true) // Cmd on macOS, Ctrl on others
.with_shift(true)
.with_key(openlogi_core::binding::KeyboardUsage::S),
};
// Dispatch the action to the OS.
execute(&action).expect("failed to inject event");
Source: openlogi-inject/src/inject.rs
Combining Capture and Injection
You can mix capture and injection to create gesture-based triggers. For example, detecting a drag gesture and synthesizing a click:
use openlogi_hook::{Hook, EventDisposition, MouseEvent};
use openlogi_inject::execute;
use openlogi_core::binding::Action;
let hook = Hook::start(|event| {
match event {
openlogi_hook::HookEvent::Mouse(MouseEvent::Moved { delta_x, delta_y }) => {
// If the user drags rightwards > 100 pixels, synthesize a left click.
if delta_x > 100 {
let click = Action::MouseClick {
button: openlogi_core::binding::ButtonId::Left
};
execute(&click).ok();
}
EventDisposition::PassThrough
}
_ => EventDisposition::PassThrough,
}
}).unwrap();
Sources: openlogi-hook/src/lib.rs and openlogi-inject/src/inject.rs
Summary
openlogi-hookcaptures raw OS input events using platform-specific taps (CGEventTap,evdev,WH_MOUSE_LL) and is part of the agent process.openlogi-injectsynthesizes OS-level events fromActionobjects usingexecute(), enabling both the agent and core to send input without platform-specific knowledge.- The hook crate depends on the inject crate for re-injection scenarios, while inject remains independent for testability.
- Both crates are documented in
docs/DEVELOPMENT.mdas the OS mouse hook and input synthesis components respectively.
Frequently Asked Questions
Can I use openlogi-inject without openlogi-hook?
Yes. The openlogi-inject crate is fully independent and only requires the core types (Action, KeyCombo). You can use it to programmatically control the mouse and keyboard without capturing any input, making it suitable for automation scripts or testing environments that don't need the hook subsystem.
Why are these crates separated instead of combined?
OpenLogi maintains this separation to keep the core logic platform-neutral and to enable safe unit testing. By isolating the injection layer from the capture layer, the core can test action-to-event translation without physical hardware or OS-specific I/O dependencies. The architecture also allows the agent to use both crates while the core uses only inject for simulations.
Which crate handles the DPI cycle logic in OpenLogi?
The DPI-cycle logic resides in the agent process that uses openlogi-hook. While the hook captures the raw events that trigger DPI changes, the actual state management happens in the agent. The openlogi-hook crate provides the raw input data that flows to this logic, as shown in the component diagram in docs/DEVELOPMENT.md.
How do the crates handle platform differences?
Each crate contains platform-specific submodules: macos.rs, linux.rs, and windows.rs. For openlogi-hook, these implement the low-level tap mechanisms (CGEventTap, evdev grab, WH_MOUSE_LL). For openlogi-inject, these implement the synthesis APIs (CGEvent, uinput, SendInput). Both expose a unified public API that hides these platform details from the rest of the OpenLogi workspace.
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 →