Skip to content

Drive another machine from this one's edge with gliff --control - #105

Open
avillagran wants to merge 11 commits into
omacom:mainfrom
avillagran:feat/edge-control
Open

avillagran wants to merge 11 commits into
omacom:mainfrom
avillagran:feat/edge-control

Conversation

@avillagran

@avillagran avillagran commented Oct 30, 2026 •

Copy link
Copy Markdown

What this adds

One mouse and keyboard for several machines, the way Synergy or Barrier
do it, with no window and no video:

gliff --control left:user@laptop --control right:user@desktop

Push the pointer past this machine's left or right edge and it carries
on across that machine's screen, with the keyboard. Cross back over the
same edge, or press the release hotkey (Shift+Esc, or a chord given to
--release-hotkey), to come back.

How it works

  • hypr-edge (new crate) puts a one-pixel layer surface along the
    chosen outer edges of the layout. When the pointer touches one it
    locks the pointer there, hides it, takes the keyboard, inhibits
    shortcuts and reports relative motion, buttons, the wheel, keys,
    modifiers and the keymap until told to release. Releasing puts the
    pointer back just inside that edge, through a new
    hypr_ipc::move_cursor that speaks both config formats.
  • gliff-server --control SIDE skips the picture: after the usual
    Hello it binds a virtual keyboard and pointer, with the client's
    keymap, to the screen on SIDE of its layout, answers with that
    screen's size, and injects what the client sends until it goes. The
    handshake and the setup error report are the session's own.
  • gliff --control SIDE:HOST keeps one such connection per machine
    over ssh, reconnecting with backoff, and arms that side's edge only
    while the machine answers. The pointer enters the other screen at the
    facing edge, at the same proportional height (or width) where it left
    this one, and comes back at the matching point. A held button keeps the
    pointer on the other machine; keys and buttons still held there are let
    go on every return.
  • The release hotkey is matched by keysym in the local keymap, so it is
    the same keys in any layout. It may name the unshifted symbol
    (shift+escape) or the one the modifiers produce (shift+exclam).
  • The layout in effect and locked modifiers (Caps Lock, Num Lock) go
    with the keyboard: a new KeyboardLocks message carries them, by name,
    on entering and whenever they change, to a server that lists the
    keyboard-locks feature.
  • Each link pings every 2 seconds and is dropped after 6 seconds without
    an answer, or a write that long, so a machine whose network went away
    stops taking the pointer until it is reached again.
  • Edges follow the layout: a screen plugged in farther out, unplugged,
    moved or rescaled moves the edge and where the pointer comes back.

Without --control, nothing changes. The clipboard is not shared this
way yet.

Testing

  • scripts/check.sh passes, with new unit tests for parsing
    SIDE:HOST, matching the hotkey by keysym, entering at the facing edge
    and crossing back, finding the outer screen of each side, releasing
    just inside the edge, and sides facing each other.
  • On Hyprland 0.56, with a nested Hyprland session as the other machine:
    crossing left and right and back, entering at the proportional height,
    typing (ñ, É), clicks, the wheel, Shift+Esc by keysym, the link
    dropping and reconnecting, and a clean exit on SIGTERM.
  • Between three machines on Hyprland, the middle one driving one on its
    left and one on its right.

A new crate puts a one-pixel layer surface along chosen outer edges of the
layout. When the pointer touches one it locks the pointer there, hides it,
takes the keyboard and inhibits shortcuts, and reports relative motion,
buttons, the wheel, keys, modifiers and the keymap until told to release.
Releasing puts the pointer back just inside that edge, through a new
hypr-ipc move_cursor that speaks both config formats.
--control SIDE skips the picture: after the usual Hello it binds a virtual
keyboard and pointer, with the client's keymap, to the screen on SIDE of the
layout, answers with that screen's size, and injects the client's keys,
motion, buttons and wheel until the client goes. The handshake and the
setup error report are the session's own.
gliff --control SIDE:HOST, given once for each machine, runs with no window:
it keeps a gliff-server --control connection to each machine, reconnecting
with backoff, and arms that side's edge only while the machine answers.
Pushing the pointer past an armed edge carries it onto that machine's
screen, entering at the facing edge where it left this one. Moving back
across that edge, or the release hotkey, returns it at the matching point;
a held button keeps the pointer there. Keys and buttons still held are let
go on the other machine on every return. The release hotkey is matched by
keysym in the local keymap, so it means the same keys in any layout.
@greptile-apps

greptile-apps Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge; no new actionable issues remain.

Summary

Adds --control to share a mouse and keyboard across machines over SSH, without video.

  • The pointer can enter a neighboring machine through an edge that follows the screen layout.
  • The controller sends pointer and keyboard input to each configured neighboring machine.
  • The remote server injects a controller’s input into a virtual keyboard and pointer.

Diagram

sequenceDiagram
    participant User
    participant Client as gliff
    participant Server as gliff-server
    User->>Client: Cross an armed edge
    Client->>Server: Send pointer position and keyboard settings
    Client->>Server: Forward mouse and keyboard input
    User->>Client: Cross back or press release hotkey
    Client->>Server: Release held keys and buttons
    Client->>User: Return pointer to local screen
Loading

Reviews (5) · Last reviewed commit: "Move a held edge when the compositor tak..." · Reviewed by Greptile

Comment thread crates/gliff-server/src/control.rs Resolved
Comment thread crates/gliff/src/control.rs Resolved
Comment thread crates/hypr-edge/src/thread.rs Resolved
Comment thread crates/gliff/src/control.rs Resolved
Comment thread crates/gliff/src/control.rs Resolved
The client works out the pointer's extent from the size in HelloAck, but
it got the mode as Hyprland reports it, unrotated, while the server bound
the pointer to the rotated screen. On a portrait screen the pointer could
not reach its bottom. Swap the size for a 90 or 270 degree transform, as
the video session does.
The pointer returned to the edge's screen as it stood when control
started. A screen moved or rescaled meanwhile put it somewhere else,
possibly on the edge again. Read the layout when releasing, and keep the
old one only when it cannot be read.
avillagran added a commit to avillagran/gliff that referenced this pull request Oct 30, 2026
@avillagran

Copy link
Copy Markdown
Author

Two findings from the review of #106, which builds on this branch, were in code from here and are fixed here: ca04948 names a rotated screen's size as it stands in HelloAck (the client's extent was unrotated while the server bound the pointer to the rotated screen), and ea8aa87 reads the screens again before giving the pointer back, so a screen moved or rescaled during control does not misplace it. scripts/check.sh passes.

avillagran added a commit to avillagran/gliff that referenced this pull request Oct 30, 2026
The other machine's keyboard started from a fresh state and followed only
the keys typed into it, so a second layout selected here, or Caps Lock or
Num Lock already on, did not apply there. The client now sends its
layout in effect and locked modifiers, by name, on entering and whenever
they change while captured, in a new KeyboardLocks message. The control
server lists the keyboard-locks feature and applies them to its virtual
keyboard; a server that does not list it is not sent them, and the video
session ignores the message.
Hotkey::parse accepts names such as exclam, but matching looked only at
each key's unshifted symbol, so shift+exclam was accepted and never
matched. A key now matches by its unshifted symbol or by the one it
produces with the modifiers held.
A link whose network went away without closing the connection stayed
up: nothing read had a deadline, so its edge kept taking the pointer to
a machine that was gone, and input queued for a write that never ended.
The link now pings every 2 seconds and is dropped after 6 seconds
without hearing from the other machine, or when a write takes that long;
it then disarms its edge and reconnects as after any drop.
An edge's screen and rectangle were chosen once, so a screen added
farther out left the edge on what was now an inner side, and a moved or
rescaled screen kept the old coordinates. hypr-wl's Outputs now binds
and forgets outputs that come and go, and hypr-edge reads the layout
again after each output's burst, when one goes away, and when the
pointer is given back: an edge whose outermost screen changed is put on
the new one once Wayland has announced it, or after the pointer it holds
is given back. Whether an edge is armed is now kept apart from its
surface, so a surface the compositor closes with its screen is given
again, and events from an edge's previous surface are ignored.
@avillagran

Copy link
Copy Markdown
Author

All five findings are addressed, each in its own commit: ca04948 (rotated screen size), 74e3f89 (layout and locks carried over, behind a keyboard-locks feature), 0ec0c00 (edges follow screens plugged, unplugged or moved), e005c0b (keepalive and write deadline), 35dd2d7 (shifted release symbols). scripts/check.sh passes, with new tests for the locks and the hotkey; the keepalive and the edges following an added and removed screen were also checked live on Hyprland 0.56.

avillagran added a commit to avillagran/gliff that referenced this pull request Oct 30, 2026
avillagran added a commit to avillagran/gliff that referenced this pull request Oct 30, 2026
Keymaps and locks reach a link by separate ways, so a layout selected in
a keymap that had just gained it could arrive first, be dropped by a
server still on the old keymap, and not be sent again. The link now
writes the last locks again right after each keymap.
avillagran added a commit to avillagran/gliff that referenced this pull request Oct 30, 2026
avillagran added a commit to avillagran/gliff that referenced this pull request Oct 30, 2026
@avillagran

Copy link
Copy Markdown
Author

7a7041e fixes a finding from the review of #106 in this branch's code: keymaps and KeyboardLocks reach a link by separate ways, so a newly added layout could be selected before its keymap arrived and be dropped. The link now writes the last locks again right after each keymap. scripts/check.sh passes.

An edge whose screen changed while it held the pointer moved only when
the pointer was given back by a release. When the compositor ended the
capture instead, by leaving the edge or unlocking the pointer, the edge
stayed on the old screen until the next layout change. Losing the
capture now reads the layout again, as a release does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant