zyris-input
Serve keyboard and pointer tools, choose Linux backends, restore portal grants and keep screenshot coordinates aligned.
zyris-input implements input v1 using EnigoInput. It types text, presses chords, moves the
pointer, clicks and scrolls. Input acts on the real desktop; it is not scoped to a single application.
Dependencies and publication status
[dependencies]
zyris = { git = "https://github.com/attacca-cc/zyris-protocol", features = ["caps"] }
zyris-input = { git = "https://github.com/attacca-cc/zyris-protocol" }
zyris-screen = { git = "https://github.com/attacca-cc/zyris-protocol" }This crate explicitly sets publish = false. It needs an enigo fork for output-relative
Wayland motion and libei key handling. A dependency with both version and git can be packaged,
but Cargo removes the git source in the packaged manifest; that would select upstream behavior
instead of the required fork. This is why you must not replace it with a registry dependency or
describe the limitation simply as “Cargo rejects every git dependency.”
For reproducible releases, pin all git Zyris dependencies to the same tested revision and keep
Cargo.lock. These docs follow main; a floating git dependency is not a release guarantee.
Choose the Linux backend
| Feature | Intended session | Requirements / limitations |
|---|---|---|
| Default | X11, or Xwayland where XTEST is honored | libxkbcommon; GNOME/KDE Wayland may silently ignore XTEST. |
wayland | wlroots compositors | Virtual keyboard/pointer protocols, Wayland client library; enables per-output pointer support. |
libei | GNOME/KDE Wayland | RemoteDesktop portal and a user-approved session; current fractional-scaling limitation below. |
For example, opt into GNOME/KDE support with:
zyris-input = { git = "https://github.com/attacca-cc/zyris-protocol", features = ["libei"] }Use wayland instead for a wlroots-only build. Features are additive; they are backend support,
not independent tools announced to the peer. On Windows and macOS the Linux flags do not replace
the existing platform input backend.
sudo apt-get install libxkbcommon-dev
# Also needed when enabling the wayland backend:
sudo apt-get install libwayland-devAdding zyris-screen additionally requires its capture build dependencies.
macOS needs Accessibility permission for the process.
Register screen and input together
use zyris::caps::{InputServer, ScreenCaptureServer};
use zyris::{Node, NodeKind};
use zyris_input::EnigoInput;
use zyris_screen::{HostDisplays, HostScreenCapture};
fn desktop_node() -> zyris::Result<Node> {
let capture = HostScreenCapture::default().with_max_width(1280);
let input = EnigoInput::new(HostDisplays(capture.backend()))?;
Node::builder()
.name("desktop")
.kind(NodeKind::Desktop)
.capability(ScreenCaptureServer(capture))
.capability(InputServer(input))
.build()
}Connect using Connecting. EnigoInput::new opens the host input backend and
can fail during initialization. On a headless host, omit this capability rather than announcing
input that cannot act. Use the same backend for HostDisplays and capture.
An input-only node does not have to depend on zyris-screen: pass any implementation of
zyris::caps::Displays, including a Vec<Display> containing a known static layout. You are then
responsible for keeping that layout consistent with the desktop; a stale layout aims at the wrong pixel.
Tools and coordinates
| Tool | Arguments | Meaning |
|---|---|---|
type_text | text: String | Type text into the focused application. |
key | chord: String | Press a chord such as ctrl+c, ctrl+shift+t or Enter. |
move_to | display: String, x: i32, y: i32 | Move to display-local captured pixels, not global/logical coordinates. |
click | button: MouseButton | Left, right or middle click at the current position. |
scroll | dx: i32, dy: i32 | Horizontal/vertical scroll deltas. |
The typed caller imports Input and obtains InputClient through conn.wait_capability.
Display resolution tries ID before name. An out-of-display point is rejected.
Undo screenshot downscaling and add a crop's region origin before calling move_to; do not
apply scale_factor or add the display's global origin yourself.
Call input from a typed client
The display ID and point must come from the same geometry as your screenshot. This helper moves and clicks only; it deliberately does not type into an application whose focus you have not checked.
use std::time::Duration;
use zyris::caps::{Input, InputClient, MouseButton};
use zyris::Connection;
async fn click_point(conn: &Connection, display: String, x: i32, y: i32) -> zyris::Result<()> {
let input: InputClient = conn.wait_capability(Duration::from_secs(5)).await?;
input.move_to(display, x, y).await?;
input.click(MouseButton::Left).await
}Once the intended editable control has focus, input.type_text("Hello".into()).await? types text,
and input.key("Enter".into()).await? submits it. These are separate actions: typing does not
implicitly press Enter. input.scroll(0, 3).await? sends a vertical scroll delta; validate the
host/backend's resulting movement rather than treating it as a fixed number of pixels.
Restore a libei permission grant
This helper requires the libei feature:
use zyris_input::{settings_for_session, EnigoInput};
use zyris_screen::HostDisplays;
fn input_with_grant(saved_token: Option<String>) -> zyris::Result<(EnigoInput, Option<String>)> {
let settings = settings_for_session(saved_token);
let input = EnigoInput::with_settings(&settings, HostDisplays::default())?;
let token = input.restore_token();
Ok((input, token))
}Save a returned token in protected application storage and pass it on the next startup. Do not log
it or hard-code it in source. Portal restore is subject to the desktop's approval/revocation policy;
it is not a guarantee of permanent unattended access.
settings_for_session also disables the X11 fallback in a libei Wayland session so silently ignored
XTEST calls do not look like a working backend.
Current limitation and safety
The current libei backend does not resolve absolute motion against the device's logical-pixel region, so fractional scaling on GNOME/KDE can put the pointer short of the screenshot position. Do not “fix” this by changing the shared captured-pixel contract; validate or restrict unsupported layouts until the backend is corrected.
Input tools carry the host process's desktop privileges. A filesystem root does not restrict them. Provide an application permission boundary and a user-visible way to stop control; do not expose the tools merely because backend initialization succeeded. Check focus and display state before acting.