Commits

226565d8d51955ce866d68e34388b90c6f29a0df
Switch branches/tags
All users
All time
January 2026
Su Mo Tu We Th Fr Sa
28 29 30 31 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
1 2 3 4 5 6 7

Commits on January 4, 2026

  1. feat(cli): add control, connection, and daemon management commands
    CLI Expansion (Sprint 6.5):
    - switch: transfer control by direction or machine name
    - return: return control to this machine
    - release: force release input capture (recovery)
    - barrier on/off: prevent cursor from leaving screen
    - disconnect/reconnect: manage peer connections
    - config show: display current configuration
    - reload: hot-reload config (stub, not yet implemented)
    - shutdown: graceful daemon shutdown
    - logs: show daemon logs (needs file logging setup)
    
    Protocol changes:
    - Add SwitchTarget enum (Direction | MachineName)
    - Add IpcRequest variants for all new commands
    - Add IpcResponse::Ok, Config, Logs variants
    
    Daemon handlers:
    - Implement all new IPC request handlers
    - Add barrier_enabled and shutdown_requested state flags
    - Support IPC-triggered graceful shutdown
    mfwolffe committed
  2. feat(cli): implement status, peers, and ping commands
    - Add IPC client to CLI with Unix socket communication
    - Implement `status` command with uptime, state, connected peers
    - Implement `peers` command with colored status indicators
    - Implement `ping` command with latency measurement
    - Add JSON output support (--json flag) for scripting
    - Extend protocol with uptime_secs, machine_name, PeerInfo fields
    - Add PingPeer IPC request with timeout handling in daemon
    mfwolffe committed
  3. Fix recovery mode: remove timeout, add edge check, do movefocus
    Recovery mode improvements:
    - Remove arbitrary timeout, exit when Super key is released instead
    - Add same at_edge check as IPC Move before initiating transfer
    - When not at edge, do movefocus ourselves (libinput dropped the keypress)
    
    The key insight is that libinput's stale state causes it to DROP the
    keypress entirely, so we must handle it ourselves in all cases - either
    initiating transfer (at edge) or doing movefocus (not at edge).
    espadonne committed
  4. Use uinput to send synthetic key-ups after grab release
    After releasing the evdev grab, libinput has stale keyboard state - it
    thinks the arrow key used to initiate the original transfer is still
    pressed because it never saw the release event (it happened during the
    grab).
    
    New approach: Use evdev's uinput module to create a temporary virtual
    keyboard device and send key-up events for all arrow keys. This gives
    libinput fresh key-up events from a real evdev device, which should
    clear the stale state.
    
    Also removed the ineffective "recovery mode" that was trying to detect
    keybinds directly but couldn't because the triggering keypress happened
    before we entered recovery mode.
    espadonne committed
  5. Fix first keypress eaten: recovery mode intercepts keybind via evdev
    After control returns to the local machine, libinput has stale keyboard
    state from before the evdev grab (thinks keys are still pressed when
    they're not). This causes the first keypress to be eaten.
    
    Solution: Instead of immediately releasing the evdev grab, enter a
    "recovery mode" that:
    1. Keeps the evdev grab active for up to 500ms
    2. Queries physical key state to detect if Super is already held
    3. Monitors evdev events for the Super+Arrow keybind pattern
    4. If detected, triggers the transfer ourselves via hyprkvm move
    5. If timeout expires without keybind, releases the grab normally
    
    This bypasses libinput's stale state entirely by reading input directly
    from evdev and triggering the transfer via IPC.
    
    Also cleaned up evdev_grab.rs by removing non-functional send_events()
    calls (you can't write to physical evdev devices from userspace).
    espadonne committed
  6. Fix capture-side: query actual key state and send smart events
    Previous fix blindly sent key-UPs for all modifiers, which broke the
    case where user is still holding Super - they'd have to release and
    re-press it.
    
    Now we query the actual physical key state after ungrab:
    - Arrow keys: if NOT pressed, send UP (clear stale state)
    - Modifiers: if STILL pressed, send UP+DOWN (give fresh edge)
    
    This ensures:
    1. Stale arrow key state is cleared
    2. Held modifiers get a fresh edge so keybindings fire
    3. Released modifiers stay released
    espadonne committed
  7. Fix capture-side keypress eaten: synthesize key-ups via evdev after ungrab
    The virtual keyboard key-ups weren't working because Hyprland tracks
    physical and virtual keyboards separately. The physical keyboard state
    was stale (Super+Right still pressed from before the grab).
    
    Now we synthesize key-up events directly on the evdev device AFTER
    ungrabbing. These events go through libinput and update Hyprland's
    physical keyboard state, so subsequent presses are fresh edges that
    trigger keybindings.
    espadonne committed
  8. Fix capture-side keypress eaten: inject key-ups BEFORE releasing grab
    The key insight: while evdev grab is active, Hyprland only sees our
    virtual keyboard. If we inject key-ups while the grab is active,
    Hyprland's keyboard state becomes "no keys pressed". Then when we
    release the grab and Hyprland starts seeing the physical keyboard
    again, new keypresses are fresh edges that trigger keybindings.
    
    Previously we were injecting key-ups AFTER releasing the grab. By that
    point Hyprland already saw the physical keyboard with stale state
    (Super still pressed from before the grab started), so our virtual
    key-ups didn't help - the physical state took precedence.
    espadonne committed
  9. Fix StopCapture: use reset_all_keys() to properly clear modifier state
    The previous StopCapture fix only sent key() events for modifiers but
    didn't send the modifiers(0,0,0,0) event that explicitly clears the
    modifier mask. The injection side's reset_all_keys() does both, plus
    flushes the connection.
    
    Now StopCapture uses reset_all_keys() to match the injection side fix.
    espadonne committed
  10. Fix first keypress eaten: release modifiers on StopCapture (capture side)
    The previous fix addressed the injection side (remote machine). This
    completes the fix for the capture side (local machine initiating transfer).
    
    When a transfer is initiated via keybinding (e.g., Super+Right), Hyprland
    sees the modifier and arrow key DOWN events, but then evdev grabs the
    input before Hyprland sees the corresponding UP events. After the grab
    releases, Hyprland still thinks those keys are pressed.
    
    Now StopCapture injects key-up events for both the arrow key and all
    common modifiers (Meta, Shift, Ctrl, Alt) to reset Hyprland's state.
    espadonne committed
  11. Fix first keypress eaten: release ALL pressed keys on StopInjection
    The previous fix only released modifiers. The actual issue was that
    the arrow key that triggers the return (e.g., LEFT) is injected DOWN
    but never UP - the StopInjection happens immediately after the
    keybinding fires, before the key-up event arrives.
    
    On the next transfer, Hyprland still thinks that arrow key is pressed,
    so the first press is seen as a repeat and the keybinding doesn't fire.
    
    Fix: Track all pressed keys in a HashSet and release them all when
    stopping injection, not just modifiers.
    espadonne committed
  12. Fix first keypress eaten: reset modifier state on StopInjection
    Root cause: When injection stops, the ModifierTracker still has
    modifiers marked as pressed (e.g., Super from the synthetic keydown).
    When the next transfer starts, we send another Super DOWN, but
    Hyprland sees this as a repeat since Super was never released.
    The first keypress isn't recognized as a fresh key combo.
    
    Fix: Add reset_modifiers() to VirtualKeyboard that releases all
    pressed modifiers and resets internal state. Call it in StopInjection
    so each injection session starts with clean modifier state.
    espadonne committed
  13. Add debug logging for first-keypress-eaten investigation
    - Add RAW EVDEV logging to see kernel-level key events
    - Drain pending events before grabbing to start fresh
    - Drain stale events from channel after stopping grab
    espadonne committed
  14. Send synthetic Super key-down when starting input capture
    When a transfer is initiated via Super+Arrow, the Super key is already
    held before the evdev grab starts. This means the grabber never sees
    the initial Super key-down event. Send a synthetic Super key-down as
    the first event when starting capture so the destination machine knows
    Super is pressed.
    espadonne committed
  15. Add debug logging and attempt arrow key-up fix for eaten keypress
    - Add DEBUG logging for key capture (CAPTURE KeyDown/KeyUp)
    - Add DEBUG logging for key injection (INJECT KEY with modifier state)
    - Remove synthetic modifier key-ups that corrupted compositor state
    - Inject arrow key-up for the direction used to initiate transfer
      when stopping capture (attempt to fix stuck key state)
    
    Keyboard return debugging still in progress - first keypress in
    return direction is still being eaten after control transfer.
    espadonne committed
  16. Fix edge barrier positioning and add bounce-back cooldown
    - Add MonitorInfo struct to pass Hyprland monitor positions to edge capture
    - Match Wayland outputs to Hyprland monitors by size, considering scaling
      (handles 1.5x and 2.0x scale factors for monitors like 4K at 1.5x)
    - Add multiple roundtrips during initialization to ensure output info
    - Add 500ms cooldown after receiving Leave to prevent bounce-back loops
      where control immediately transfers back after returning
    - Add barrier creation logging for debugging
    
    Keyboard return debugging still in progress (first keypress issue).
    espadonne committed
  17. Revert "Fix first return keypress being eaten"
    This reverts commit 9a563f54edb08acc987b229903bf1e463c4a8e33.
    espadonne committed
  18. Fix first return keypress being eaten
    When in ReceivedControl state, check for return-to-source BEFORE
    doing the at_edge check. The edge detection can fail due to
    hyprland query timing, causing the first keypress to fall through
    to local movefocus instead of returning control.
    
    Now: ReceivedControl + direction matches source = always return control
    espadonne committed
  19. Reset modifier keys after releasing input grab
    When control returns from remote machine, the local compositor never
    saw key-up events for modifiers that were released while input was
    grabbed. This caused "stuck" modifier state where the compositor
    thought SUPER was still pressed, preventing keybinds from working.
    
    Fix: send synthetic key-up events for all modifier keys (Shift, Ctrl,
    Alt, Super) via virtual keyboard after stopping the input grab.
    
    Also adds debug logging to IPC server for troubleshooting.
    mfwolffe committed
  20. mfwolffe committed
  21. mfwolffe committed
  22. Fix keyboard return: require edge detection before returning control
    Only return control when BOTH conditions are met:
    1. At edge (edge monitor + edge window)
    2. In ReceivedControl state from that direction
    
    Previously, ReceivedControl check happened before edge detection,
    causing every move in the "from" direction to return control.
    mfwolffe committed
  23. Handle keyboard-based return in ReceivedControl state
    When in ReceivedControl state and user presses the key to move in the
    direction control came from, return control to source machine instead
    of doing local movefocus or edge detection.
    
    This enables returning control via keyboard (SUPER+Arrow) from the
    receiving machine back to the source.
    mfwolffe committed
  24. Fix keyboard navigation: proper edge detection and movefocus
    1. Edge detection now checks monitor AND window position:
       - On edge monitor (no monitor in that direction)
       - On edge window (no window further in that direction)
    
    2. Fixed bug where movefocus was not called when at edge but no peer
       connected. Now movefocus is always executed unless we're transferring.
    
    3. Only initiate transfer when BOTH at edge AND have connected peer.
    mfwolffe committed
  25. Implement keyboard-based workspace navigation switching
    Add IPC mechanism between CLI and daemon so `hyprkvm move <direction>`
    can trigger machine transfers when at screen edges.
    
    - Add Unix socket IPC server at $XDG_RUNTIME_DIR/hyprkvm.sock
    - Daemon handles Move requests by checking cursor position
    - If cursor at edge with connected peer, initiates transfer
    - Otherwise returns DoLocalMove for normal hyprctl movefocus
    - CLI gracefully falls back to local move if daemon not running
    
    This enables the key HyprKVM feature: using SUPER+Arrow keys to
    seamlessly switch between machines when at workspace boundaries.
    mfwolffe committed
  26. Fix modifier keys not working in virtual keyboard injection
    When pressing modifier keys (Shift, Ctrl, Alt, Super), the key event
    was sent but the XKB modifier state was never updated. This caused
    the compositor to not recognize modifiers as held.
    
    Added ModifierTracker to track pressed modifier keys and send
    keyboard.modifiers() calls with the appropriate XKB modifier masks
    after each modifier key press/release.
    mfwolffe committed
  27. Add motion event coalescing to prevent drift
    - Evdev grabber: accumulate motion deltas before sending
    - Main loop: coalesce motion events from queue into single message
    - Combines REL_X/REL_Y into single motion event
    - Dramatically reduces message count during fast mouse movement
    espadonne committed
  28. Further latency improvements
    - Evdev grabber: 1ms → 100μs poll sleep
    - Edge dwell: 150ms → 50ms for faster trigger
    - Flush Wayland connection immediately after each input event
    espadonne committed