mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-08-23 22:56:41 +00:00
* fix(linux): serve the Wayland login screen the DRM backend was built for The login screen support in #15420 never worked on a real greeter. fufesou found it: the session is refused, and with the refusal commented out the client gets a failed connection instead of a screen. One premise under all of it. `get_values_of_seat0` is `_get_values_of_seat0(.., ignore_gdm_wayland = true)`, so a gdm/sddm Wayland session is skipped by construction and `get_display_server` falls back to x11. That was correct while the portal was the only backend, since the portal cannot serve a greeter at all. The DRM path never talks to the compositor, which is precisely why it can serve one, so the premise stops holding there and every x11-vs-Wayland decision in the tree answers x11 at a login screen. The central change is the memoised `IS_X11`: when it reads x11 and seat0 is a Wayland greeter, answer Wayland. That covers fifteen routing sites at once, and it is under `cfg(feature = "drm")`, so a build without the backend keeps the current answer exactly. `is_x11_for_drm` is the unmemoised form for the two retry loops that must keep asking while a boot is still naming the session, and the memoised accessor is scoped to per-frame callers in the per-session `--server`, which the service only spawns once it has identified the session. Input was the last layer and lived outside all of that. `Enigo` decides x11-vs-Wayland once in `Default::default()`, from the same seat0 lookup, and on "x11" routes every key and mouse event to xdo; with no X server that context is null and libxdo drops them without an error. So the uinput devices were created, the compositor opened them, and nothing was ever written to them. `set_is_x11` is now called where the custom devices are installed, which is only reached once `!is_x11()` is already established. The unit test pins both directions, since a one-directional test passes against the bug. With no compositor reachable, the uinput desktop rect comes from the DRM display list instead: those are the same displays being captured, so the coordinate space matches by construction. Telling the truth about a greeter also makes four compositor-probing paths reachable where the probe cannot answer; all four already treat an empty output list as "nothing to do", so they skip it and 11818 "Could not find wayland compositor" warnings in one session became 1. Tested on an sddm Plasma Wayland greeter, MacBook T2, 2880x1800: the greeter renders, typing from the client enters characters in the password field, a click at an absolute coordinate opens the greeter session combo, the service pre-warm primes in 994 us instead of timing out, and the privileged service maps no EGL during a live capture. Not proven on gdm under Wayland. Known limitations: non-ASCII characters cannot be typed at a greeter, because that path goes through the clipboard and the clipboard here is X11 only; and at a multi-monitor greeter the pointer reaches the first display only, since every DRM output reports origin (0,0) on Wayland and there is no arrangement to derive without the compositor. * fix(linux): a Wayland greeter the DRM backend can serve is not headless fufesou reported the login screen still failing on Ubuntu 24.04 with gdm3, with the client asking for OS credentials to start an X session instead of showing the greeter. Reproduced on a real gdm greeter here. Same premise as the rest of the branch, one more consumer. `DesktopManager::new` reads seat0 through `get_values_of_seat0`, which skips a gdm/sddm Wayland session by construction, so at a greeter it finds no session at all and `get_supported_display_seat0_username` returns None from its empty-username arm. That makes `is_headless()` true, so the service advertises headless and `try_start_desktop` answers `LOGIN_MSG_DESKTOP_SESSION_NOT_READY`. The corrected `IS_X11` does not reach this one: it asks who owns seat0, not which display server is running. So ask again, with the greeter visible, when the DRM backend can capture and inject into it. At query time rather than in `new()`, because the DRM probe has not necessarily settled when the desktop manager is constructed, and the answer would latch for the process lifetime. In a normal session the latched username is a real user and the extra read is skipped. * chore: drop the hbb_common bump, this branch does not need it The bump carried rustdesk/hbb_common#580, the compositor-socket fallback. Nothing here depends on it: the greeter paths in this branch are the ones that run when compositor data is unavailable, which is what the commit before this one states as a known limitation. Keeping the bump would only block the greeter fix behind a review of a separate change, and would import that change's blocking review items into this path. * fix(linux): let the uinput uid gate see the greeter that owns seat0 Input at a real greeter was rejected by our own authorization. Measured on Ubuntu 24.04 with gdm3: the root service logs Rejected unauthorized connection on uinput ipc channel: postfix=_uinput_control, peer_uid=Some(120), active_uid=None and the greeter's `--server` gets ECONNRESET out of `setup_uinput`, so no uinput device is ever created and neither keyboard nor mouse reaches the greeter. uid 120 is gdm, the owner of the only active seat0 session. `active_uid` is None because the uinput authorizer deliberately bypasses the service-loop cache and takes a fresh seat0 lookup, and the fresh read hides a Wayland greeter by construction. The cache-based gates do not have the problem: `Desktop::refresh` fills it through the greeter-visible read, which is also why capture and config sync work at a greeter while input does not. So make the fresh read agree with the cache. It keeps the property the uinput gate wants, a lookup that cannot be stale, and it still compares the peer against the uid of the session that owns seat0 -- which at a greeter is the greeter. * fix: settle the DRM probe before routing login to X11, and read seat0 fresh Two findings from the #15792 review, both verified against the code: - drm_login_screen_seat0_username asked the cached probe, so a client arriving before warm_availability publishes its verdict read "no DRM" and, with allow-linux-headless=Y, try_start_x_session could start Xorg over a live Wayland greeter. Ask the probing form instead, and only after the cheap seat0 read says a Wayland greeter is actually there: a bounded definitive verdict is affordable on a login-time path. - get_supported_display_seat0_username trusted the seat0 values cached in DesktopManager::new(), which go stale across a logout or a fast user switch: a stale non-greeter name skipped the greeter probe and was returned as the supported display owner. Read seat0 fresh on every query; every call site is connection-time, so the extra loginctl read is cheap. Regression-tested on a real sddm Wayland greeter: capture streams the greeter, the RustDesk password dialog is the only prompt, and five typed characters appeared in the greeter password field over uinput with zero "Rejected unauthorized connection" lines in the service log. * fix: ask the greeter compositor for the multi-monitor layout The display arrangement and the pointer mapping were wrong at a multi-monitor login screen, and the mechanism is measured on a two-head virtio VM: DRM has no origins, so every display was advertised at (0,0) (a stacked arrangement on the client), and the uinput range was taken from the union of the DRM modes while the compositor had arranged the outputs side by side. Both came from the same premise, written before the hbb_common socket fallback existed: "a login screen has no compositor to ask". wayland_outputs_askable() skipped the wl_output augmentation at any greeter, and update_uinput_resolution took the DRM union directly. The premise is false now: a greeter runs a compositor, and the socket fallback reaches it with no environment variables, measured answering two outputs at the VM greeter while the old gate was still routing around it. Drop the gate and take the compositor-first path everywhere. Where the fallback cannot answer, the output list comes back empty and both call sites degrade to exactly the old behavior, so a build against an older hbb_common is unchanged. * fix: augment a single display too, and probe the desktop rect off the executor Two follow-ups from the automated re-review ofcd80c3dee, both verified: - augment_with_wayland_geometry skipped the compositor below two DRM displays, but on a multi-GPU host the one connector this service can open may sit at a non-zero origin of the compositor layout, and DRM alone reports (0,0). - the desktop rect for uinput can now block for the socket probe deadline, and update_uinput_resolution runs on current-thread runtimes; move the query into spawn_blocking. The third re-review finding, the warm-up allegedly skipping Wayland greeters, is refuted: warm_availability probes while is_x11_for_drm() is false, which includes a Wayland greeter, and the greeter log of the VM run behindcd80c3deeshows the warm succeeding there. * fix: baseline the layout from the blocking task, and augment a lone output's origin The layout snapshot after the rect lookup still ran on the executor: a failed compositor lookup is not cached, so the snapshot synchronously repeated the whole socket probe there. The baseline is now computed inside the same blocking task, from the snapshot the successful lookup just cached, or omitted when only the raw DRM union was available, which keeps the #15601 remap inactive exactly where origins are unknown. A single compositor output now hands its origin to a single connector: the lone output can sit at a non-zero origin the DRM side cannot see. Scale stays 1 on purpose, matching how a single display is advertised at physical size, and more connectors than the one output stays unaugmented, since the layout-order fallback would plant that origin on a guess. Also refresh the get_primary_index doc that still said augmentation declines below two connectors. * fix: read the DRM probe as a tri-state, and keep pre-auth seat0 checks cache-only is_available() answered false both for a definitive no-DRM verdict and for a probe that had simply not settled (another probe in flight, or a failure still below the disable threshold), and the login-screen decision turned that transient false into no-greeter: try_start_x_session could put Xorg over a live greeter in exactly the window the probe needed. The machinery now answers Available/Unavailable/Unsettled, and only a definitive Unavailable routes the seat toward X11. Connection setup also ran the whole lookup pre-auth: constructing LinuxHeadlessHandle called is_headless() before authentication, holding DESKTOP_MANAGER while loginctl ran and, at a greeter, while the DRM probe waited out its handshake. An unauthenticated peer could occupy a worker for seconds and serialize every other connection on the mutex. is_headless() now answers from a snapshot refreshed off-thread, and the fresh lookup became a free function called with the manager lock released everywhere; the enforcing decisions, get_username and try_start_x_session, still read seat0 fresh. Also drops seat0_display_server, dead since the fresh-read change. * fix: respect RUSTDESK_FORCED_DISPLAY_SERVER over the greeter correction The greeter correction rewired IS_X11 and is_x11_for_drm() to Wayland whenever seat0 looks like a Wayland greeter, including when the operator explicitly forced the display server: get_display_server() kept honoring the override while the DRM routing gates contradicted it, leaving capture and input routing internally inconsistent. The correction now only adjusts the auto-detected answer. * fix: honest pre-auth snapshot, sticky negative verdict, and a complete forced-x11 gate Four defects found by an adversarial review of the two previous commits, all in their new lines: - The empty-snapshot fallback derived headless from the manager's boot-time seat0 read, which is blank at a Wayland greeter (the loginctl wrapper skips greeter sessions), so the first connection of every server process at a greeter answered headless=true, the opposite of the comment on it. No snapshot now answers NOT headless, the snapshot is seeded at start_xdesktop, and the boot-time cache is gone entirely (it had no reader left). - wait_desktop_cm_ready gated on a bool stored at construction, which can lag one seat0 transition behind and skipped the CM-ready wait right after a logout. It re-reads the snapshot at call time. - A settled Unavailable was erased at NEGATIVE_TTL expiry (state to Unknown, failure counter to zero), so a permanently helper-less box reopened the Unsettled window every 30 seconds and the login decision kept adopting a greeter nothing can serve. The verdict now stays Unavailable while an off-thread re-probe re-verifies it: a failed or empty re-probe restamps the no, and only a non-empty list flips it. - The forced-x11 gate only covered IS_X11 and is_x11_for_drm, while the seat0 adoption path still probed DRM and admitted greeter sessions whose capture and input then routed to X11. Greeter adoption now yields to an operator-forced X11, degrading to upstream behavior: the connection is refused at the login screen. * fix: keep the login request path off the probe entirely try_start_desktop runs while handling a LoginRequest, before password validation, and at a Wayland greeter its seat0 lookup reached the probing availability form: an unauthenticated peer could park a worker for the probe deadline. The greeter adoption now reads a cached tri-state that never blocks; when the state is Unknown it kicks the probe off-thread and answers Unsettled, which the login decision treats as a possibly servable greeter until it settles. Settling lives in the startup warm-up, that kick, and the TTL re-verifiers; the blocking form stays for the capture-side callers, where waiting is acceptable. * fix: run the pre-auth desktop start off the executor, guard the refresh flag, trim comments From fufesou's #15792 re-review (no blocking issues) plus a bot pass: - try_start_desktop now runs on spawn_blocking. It executes loginctl, and PAM when a session must start, while handling a LoginRequest before password validation, so a slow logind must not tie up an async request worker; the blocking pool absorbs it. - kick_seat0_refresh releases SEAT0_REFRESH_IN_FLIGHT through an RAII guard, so a panic in the refresh thread cannot freeze is_headless on a stale snapshot for the process lifetime. - drm_can_serve_login_screen stays Available-only, and the reason is now in the code: it is deliberately not symmetric with the seat0 adoption gate. Adoption yields Xorg only on a definitive Unavailable; admission accepts only on a definitive Available; both wait through an unsettled probe. Admitting there would black-screen a client on a helper-less box, so a review suggestion to make them agree is declined. - Trimmed two over-long comments to the repo's three-line rule. * fix(linux): harden DRM login-screen startup Keep unauthenticated headless checks cache-only, bound OS-session startup to one blocking task, and surface JoinError failures. Wire the isolated Wayland probe consumer and update hbb_common plus libdrmtap 0.5.4. * fix(linux): headless refresh state Signed-off-by: fufesou <linlong1266@gmail.com> * fix(linux): keep headless startup state consistent - gate concurrent desktop startup attempts - route CM IPC after refreshing desktop state - avoid blocking seat0 queries in the CM retry loop - preserve newer seat0 snapshots during overlapping refreshes - derive DRM geometry and primary display from one Wayland snapshot Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com> Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com> Co-authored-by: rustdesk <info@rustdesk.com> Co-authored-by: fufesou <linlong1266@gmail.com>
enigo
Cross platform input simulation in Rust!
- Linux (X11) mouse
- Linux (X11) text
- Linux (Wayland) mouse
- Linux (Wayland) text
- MacOS mouse
- MacOS text
- Win mouse
- Win text
- Custom Parser
let mut enigo = Enigo::new();
enigo.mouse_move_to(500, 200);
enigo.mouse_click(MouseButton::Left);
enigo.key_sequence_parse("{+CTRL}a{-CTRL}{+SHIFT}Hello World{-SHIFT}");
for more look at examples
Runtime dependencies
Linux users may have to install libxdo-dev. For example, on Ubuntu:
apt install libxdo-dev
On Arch:
pacman -S xdotool
