Skip to content

[pull] master from rustdesk:master - #278

Merged
pull[bot] merged 5 commits into
LittleFishYu2008:masterfrom
rustdesk:master
Sep 28, 2026
Merged

pull[bot] merged 5 commits into
LittleFishYu2008:masterfrom
rustdesk:master

Conversation

@pull

@pull pull Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

flusheDData and others added 5 commits September 28, 2026 13:53
…#16345)

* fix(drm): turn a captured frame only when the plane did not rotate it

A compositor rotates an output either in hardware, setting the primary
plane's `rotation` property, or in software, drawing the scanout already
turned. `wl_output` cannot tell the two apart, and `frame_transform`
guessed: 90/270 turned, 180 left alone, which is right for i915 + mutter
(rotate-180 in hardware, scanout upright) and wrong wherever the
compositor drew the scanout turned, where 180 comes out upside down.

Measured before changing it. virtio-gpu (no rotation property, mutter 46):
the scanout dump at 180 is the desktop upside down, at 90 and 270 the
logical desktop turned inside the native mode framebuffer. i915 + mutter
(GNOME 50) at 180: the plane reports rotate-180 and the dump is upright,
identical to the unrotated one. amdgpu + KWin 6 at 180: the plane stays at
rotate-0 and the dump is upside down.

The producer reads `drmtap_plane_rotation()` (libdrmtap 0.5.8, optional
symbol) right after each grab and stamps it on the frame, in the dma-buf
descriptor and in the CPU frame header, both `serde(default)` so an older
producer still parses. A plane that rotated scans out the logical desktop
whatever the output transform says: the compositor programs it from the
CRTC transform, which also folds in the connector's panel orientation that
`wl_output` never carries, so the consumer leaves such a frame alone. A
plane at rotate-0 scanned out what the compositor drew, and the frame is
turned by the whole output transform. `None` (a library before 0.5.8, or
nothing it could read) keeps the previous rule, so nothing changes until
the library says otherwise. The session is still sized from the output
transform: a plane that rotated 90 in hardware hands over the portrait
scanout those dims already name.

* build: pin libdrmtap 0.5.8 (rustdesk-org/libdrmtap 95d4d7454)

The rotation fix reads drmtap_plane_rotation(), added in libdrmtap 0.5.8. The mirror carries that commit since 2026-09-25, so the pin moves there.

* drm: pin the decoding of a frame from a producer older than the plane rotation

The root service and the --server are upgraded separately, so a frame
header or a dma-buf descriptor without `plane_rotation` must still
decode, as None, which keeps the previous rule. The tests built both
messages with the field set to None; this one decodes payloads that omit
it, and one that carries it (greptile on #16345). Serde already reads a
missing Option as None, so what the test pins is the wire name and the
None on absence, for both messages.

Mutation: renaming the field on the wire, in either message, turns it red.

* drm: take the plane rotation right after the grab, not after the copies

`drmtap_plane_rotation()` reads the rotation property of the plane the
last grab came from at the moment it is called. The cpu path asked only
after `DrmReader::grab()` had copied the frame into its buffer and the
producer had copied it again into the message, so on a large frame the
compositor had time to change the plane and the frame went out with the
rotation of the next one; `frame_transform` turns that frame by it with
nothing to absorb the skew (zhou, review of 26-sep).

`DrmReader` now reads the rotation right after a successful
`drmtap_grab_mapped` or `drmtap_grab_desc`, before any copy or release,
and `plane_rotation()` returns that snapshot, so both paths stamp a frame
with the rotation its own grab saw.

Test: a_frame_keeps_the_rotation_its_plane_had_when_it_was_grabbed, in
`plane_rotation_tests` (which CI already runs), drives the reader through
a fake library whose frame release turns the plane: the rotation read at
the grab survives on the cpu grab and on the dma-buf export. Mutations:
no snapshot on the cpu grab, none on the export, and the cpu snapshot
taken after the release, each turn it red.
The uinput virtual mouse RustDesk creates on Linux reported bustype
BUS_USB. libinput's evdev_tag_external_mouse() tags any BUS_USB or
BUS_BLUETOOTH pointer device as an external mouse, so desktop
environments' "disable touchpad when an external mouse is present"
setting suspends the user's real touchpad whenever RustDesk creates
this device, even though nothing is physically plugged in.

Changed the constant to BUS_VIRTUAL (0x06, verified against the
current upstream kernel's include/uapi/linux/input.h), which
correctly identifies this as the software-only device it is.

Fixes #16318.

Signed-off-by: Mohsin Manzoor Bhat <mohsinmanzoor1913@gmail.com>
…ock across the await (#16362)

* drm: apply the uinput range on one thread instead of holding a lock across the await

#16122 serialized the three paths that apply a uinput range in a DRM
session (the layout poll, `update_uinput_resolution` and `check_init`)
with a tokio Mutex held over the awaited `update_mouse_resolution`,
which AGENTS.md rules out ("Do not hold locks across `.await`"; greptile
on #16122). The serialization itself has to stay: the uinput service
keeps the range of whichever request reached it last and each apply
sends it over its own connection, so two overlapping applies can leave
the device on one range while the other one is recorded.

The check, the apply and what it records now run as one job on a
dedicated thread (`run_uinput_apply`), one job at a time in the order
asked, with the runtime of that thread for the IPC and its 3 s timeout.
The async paths await the answer and the display service loop blocks on
it; nothing holds a lock while it waits. The poll publishes its drift
flag inside its job, as it did under the lock. A thread that failed to
start or died is started again for a later job, so one failure does
not stop every later apply (the poll used to build a runtime per attempt
and retry). The drm-off build keeps the code it had.

Tests (in `input_map_tests`, which CI runs):
uinput_applies_run_one_at_a_time_in_the_order_asked,
a_uinput_apply_job_can_bound_its_ipc_with_a_timeout,
a_uinput_apply_thread_that_died_is_started_again. Mutations: every job
on its own thread, the apply runtime without timers, the answer never
sent, a dead thread not started again; each turns a test red. The call sites run IPC and are
covered by reading.

* drm: log a layout poll whose uinput apply job got no answer

The layout poll dropped the result of its job with let _ =, so a poll
whose job was dropped (the apply thread failed to start or went away)
left no trace, while update_uinput_resolution and check_init log it
(greptile and CodeRabbit on #16362). It logs now. The drift flag keeps
what the last poll that ran published; a later poll starts the thread
again and publishes it.

* drm: throttle the uinput apply thread failure logs

The layout poll hands the apply thread a job every 1.5s, so a thread that
cannot start logged two errors per poll for as long as a session lasted.
Keep one line per site every 10 minutes, with the count of the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Sep 28, 2026
@pull pull Bot added the ⤵️ pull label Sep 28, 2026
@pull
pull Bot merged commit 4812a98 into LittleFishYu2008:master Sep 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants