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:

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:

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-hook depends on openlogi-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's Cargo.toml.
  • openlogi-inject is 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-hook captures raw OS input events using platform-specific taps (CGEventTap, evdev, WH_MOUSE_LL) and is part of the agent process.
  • openlogi-inject synthesizes OS-level events from Action objects using execute(), 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.md as 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →