The XDG portal handshake is four sequential requests, and every way it can end
badly -- the user declining, nobody answering, the portal being absent or
ending it or dying mid-handshake, the stream list coming back empty -- left
`request_remote_desktop` through one `bail!` carrying one string.
`map_err_scrap` then guessed a cause by looking for "dbus" or "pipewire" in
that string. Since that string always mentions "PipeWire library", a decline
and a three-minute timeout both came out as "Wayland requires higher version of
linux distro. Please try X11 desktop or change your OS." On Ubuntu 21+, where
the mapping passes the text through untouched, they came out as raw English
pointing at an unrelated GitHub issue.
The response code and the D-Bus error were in hand at the moment of failure and
were being dropped: `handle_response` collapsed all of it into one
`AtomicBool`. Record it instead, tagged with the stage that produced it, and
let the app side look the tag up. `map_err_scrap` gains one leading branch;
anything untagged -- which is everything the capture loop reports -- takes the
existing path unchanged.
The tag decides between five new keys and the existing `xdp-portal-unavailable`:
- A decline, nobody answering, the portal ending the request with its response
code, an empty stream list, and a missing GStreamer element each get their
own message. Sentence-case English, so a peer that has never heard of them
falls back to the key and still reads as a sentence rather than showing a
slug like `x11 expected`.
- Everything else is the portal failing to deliver, which is what
`xdp-portal-unavailable` already says -- it is already translated everywhere
and carries the one remedy a user can act on, `systemctl --user restart
xdg-desktop-portal`. The D-Bus error name and message go to the log.
- The Ubuntu-before-21 branch keeps every outcome that says something about the
machine and yields the two that say what a person did.
`fill_displays` needs the tag resolved at its own call site: it opens a second
portal session for cursor-based display disambiguation, and its error returns
straight up `check_init` without passing through `map_err_scrap`, so a tag
would otherwise reach the peer verbatim.
Two existing paths change, both necessarily:
- `check_init` no longer wraps `Capturer::new` in `with_context`. The peer is
shown `format!("{}", err)` (connection.rs), which renders only the outermost
layer, so that context was replacing the mapped code with "Failed to create
capturer for display 0".
- The `std::process::exit(-1)` on libdbus' no-reply text is now reached only by
the capture loop, which is what that self-heal was written for. A no-reply
during the handshake is tagged and reported instead. It is worth saying
plainly what that branch did before: the portal proxy has a one-second
timeout, so a portal slow to activate could take the whole service down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
* add the base crate and repoint the moved modules at it
`libs/base` (crate `base`) takes the parts of hbb_common that only this app
uses: `fs`, `platform`, `keyboard`, `message.proto`, and 145 of the 177
`config::keys` constants. hbb_common keeps what the server names, and the 32
keys it reads itself are re-exported from `base::config::keys` so call sites
still see the full set through one path.
Sources move verbatim. The only edits inside them are `crate::` prefixes that
now have to say `hbb_common::`; `keyboard.rs` and `platform/windows.rs` are
byte-identical. The crate stays on edition 2018, the edition the moved code was
written under. `log`, `lazy_static` and `anyhow` become direct dependencies so
the bare paths in that code resolve exactly as before, and its winapi features
are spelled out rather than left to feature unification.
Two call sites outside Rust and Cargo had to follow the move: the Android
protobuf source dir, which still pointed at hbb_common/protos for message.proto,
and the three AGENTS.md entries that named hbb_common for options, protos and
file transfer.
`scrap`'s `drm` feature now forwards to `base/wayland_probe`. Left pointing at
hbb_common it would still have compiled, silently dropping the Wayland
socket-probe fallback, so that forward is verified by a build with and without
the feature.
`config::keys` carries a test asserting its names stay disjoint from the ones
hbb_common kept: the glob re-export and the local constants share a namespace,
and Rust prefers the local item silently, so a name added to both sides would
otherwise let client and server disagree with no diagnostic.
Verified: macOS and Linux, debug and release, `--all-targets`; the 177 key
constants diffed name-for-name and value-for-value; the generated protobuf types
compared before and after; every `#[cfg]` gate on a moved import checked against
its original; and every file that was `rustfmt`-clean before this change still
is, compared against master file by file. Windows is checked by inspection only
-- it cannot be compiled here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
* one `use` per crate, and write the rule down
`fs.rs` came out of the move with two ungated `use hbb_common::` statements,
because the original single `use crate::{...}` had to give up `message_proto`
to the new crate and the rest was left in a second block. Fold it back into one.
A scan of the whole tree for the same shape finds nothing else: every other file
with more than one top-level `use base::` or `use hbb_common::` is split by a
`#[cfg]` that does not cover the whole block, or by `pub use` next to `use`.
Those are the cases that cannot merge, so AGENTS.md now states both the rule and
the exemption.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`request_remote_desktop` and its response handlers call the portal method
first and only then subscribe to the resulting Request's `Response` signal,
using the object path returned by the call. The comment above
`create_session` already describes why that is wrong:
> To avoid a race condition between the caller subscribing to the signal
> after receiving the reply for the method call and the signal getting
> emitted, a convention for Request object paths has been established that
> allows the caller to subscribe to the signal before making the method
> call.
The code then does the opposite of what the comment says. When the portal
emits `Response` before our match rule is installed, the signal is dropped
and the flow stalls: `request_remote_desktop` spins its 3-minute wait loop
and gives up, so the user sees the screen picker again (or a failure) even
when a valid restore token would have restored the session silently.
Build the request path from our unique bus name plus the `handle_token` we
pass in the call arguments, per the Request documentation, and subscribe
before calling. Applied to all five portal calls: CreateSession,
SelectSources (both the ScreenCast and post-SelectDevices paths),
SelectDevices, and Start. The `handle_token` values are unchanged; they are
now named locals so the path and the argument cannot drift apart.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
On Wayland compositors whose xdg-desktop-portal backend exposes screencast
frames as DMA-BUF buffers — notably xdg-desktop-portal-cosmic 0.1.0 on
Pop!_OS 24.04 / COSMIC — inbound screen capture fails. PipeWireRecorder
links pipewiresrc directly to an appsink whose caps only accept
video/x-raw BGRx/RGBx in system memory. That format set is too narrow for
the portal's buffer-type / modifier negotiation, which collapses with:
pw.link: negotiating -> error no more output formats (-22)
gstpipewiresrc: stream error: no more output formats
gstbasesrc: streaming stopped, reason not-negotiated (-4)
ERROR src/server/wayland.rs: Failed scrap Element failed to change its state
Inserting a videoconvert element between pipewiresrc and appsink widens
the negotiable format set to any system-memory video/x-raw format, giving
the portal room to settle on a format it can deliver via its SHM path.
videoconvert then converts to the BGRx/RGBx the appsink expects.
Verified on Pop!_OS 24.04 / COSMIC with gst-launch, before and after:
# fails (current behaviour):
gst-launch-1.0 pipewiresrc path=N ! video/x-raw,format=BGRx ! fakesink
# works (with this change):
gst-launch-1.0 pipewiresrc path=N ! videoconvert ! video/x-raw,format=BGRx ! fakesink
After the change, inbound connections capture and stream the desktop
normally and the "Failed scrap" error no longer occurs.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add Wayland multi-monitor screen capture functionality
* fix wayland capture issues by reverting to CapturerPtr, the problem was that calling Display::all in get_capturer_for_display was dropping the pipewire capturer and causing the video to freeze.
* If running as AppImage or flatpak, ignore the 'multiple' argument
* Comment out warning log with unclear purpose Comment out warning log with unclear purpose
---------
Co-authored-by: fufesou <13586388+fufesou@users.noreply.github.com>
We cannot store restore_token if the server is not running.
Because `org.freedesktop.portal.RemoteDesktop` does not support persist
mode for now.
Signed-off-by: fufesou <shuanglongchen@yeah.net>