Skip to content

fix(cursor): send colour pointers as BGRA and hide the pointer with macOS - #219

Open
SferaDev wants to merge 1 commit into
clintcan:mainfrom
SferaDev:fix/cursor-bgra-system-pointers
Open

SferaDev wants to merge 1 commit into
clintcan:mainfrom
SferaDev:fix/cursor-bgra-system-pointers

Conversation

@SferaDev

@SferaDev SferaDev commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Coloured cursors (wait cursor, colour pickers) showed red and blue swapped. The 32 bpp pointer XOR mask is B,G,R,A with the bottom row first, and the encoder sends RGBAPointer.data unchanged, so macrdp now converts to that layout in one place. SkyLight cursors keep their orientation, and the NSCursor fallback is no longer upside down.
Clients also kept drawing the last cursor shape while an app hid it (full-screen video). WindowServer's cursor is then a fully transparent image, so macrdp now sends the hidden pointer for it instead of a transparent bitmap. Typing-obscured cursors leave no readable signal, so that case is not handled.
Tests: new unit tests for the mask layout, the SkyLight pixel conversion and the transparent-cursor check; cargo fmt --check, cargo test --locked --target aarch64-apple-darwin and clippy pass. Not yet checked against a live client; worth a look at the wait cursor and hiding during full-screen video in mstsc or FreeRDP.

@clintcan clintcan left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I tested this live on a Mac mini (macOS 26.5, mirroring a 1080p monitor) over RDP.

Colour and orientation: works. The wait cursor's colours are right now, and the cursor points the right way (the SkyLight path; the NSCursor fallback wasn't exercised). I also checked the layout against the encoder: it still sends RGBAPointer.data unchanged at our current pin. On the e258f6a0 bump, the AND mask built from alpha pairs rows by index, so this layout should work there too (not tested on the bump).

Hiding: doesn't fire. The pointer stayed on screen on the client while typing in TextEdit and during full-screen YouTube. I ran a small read-only probe in the GUI session to see why:

  • CGCursorIsVisible and SkyLight's SLCursorIsVisible both answer from the calling process's point of view. It flipped between 0 and 1 while the pointer was visible and moving, and a plain command-line process reads 0 with the cursor on screen. So it can't tell whether the cursor is hidden.
  • When a video hides the pointer, the global cursor becomes a 1×1 image with every pixel transparent. SLSGetGlobalCursorData returned that six times during the video steps, and a real cursor again as soon as the mouse moved. macrdp already reads this image.
  • Typing gives no signal I could find. The cursor image and SLSCurrentCursorSeed stayed the same through 30 s of typing, and SkyLight exports no getter for the obscured state (only SLSObscureCursor / SLSDisableCursorObscure).

Could you:

  1. Drop cursor_visible() and the CGCursorIsVisible declaration.
  2. In poll_shape, send DisplayUpdate::HidePointer when the cursor image is fully transparent (every alpha 0) instead of sending it as a bitmap. That covers the video case on every client, including ones that ignore a 32 bpp pointer with an empty AND mask.
  3. Drop "typing in a text field" from the docs line and the module comment, since that case isn't detectable.

The colour fix is good to go once that's in. If you'd rather keep this PR to colour only and do the hiding separately, that's fine too.

…acOS

Coloured cursors (the wait cursor, colour pickers) showed red and blue
swapped in every client. A 32 bpp TS_POINTERATTRIBUTE XOR mask is B,G,R,A
with the bottom row first, and the encoder writes RGBAPointer data to the
wire verbatim, but macrdp sent R,G,B,A. Keep the cursor as straight RGBA,
top row first, through scaling and encode it to the spec layout in one
place. SkyLight's buffer is top-down (row 0 holds the arrow's tip), so the
old row flip was what kept it upright on the wire; SkyLight cursors keep
their orientation and the NSCursor fallback, already top-down, is no
longer upside down.

macrdp only ever sent bitmaps, so a client kept drawing the last shape
while an app hid the cursor (full-screen video). WindowServer's cursor is
then a fully transparent image, so send the hidden system pointer for it
instead of a transparent bitmap, which covers clients that ignore a 32 bpp
pointer with an empty AND mask. macOS obscuring the cursor while typing
leaves no signal to read, so that case is not handled.
@SferaDev
SferaDev force-pushed the fix/cursor-bgra-system-pointers branch from 5167d9e to 03bc180 Compare October 29, 2026 10:15
SferaDev added a commit to SferaDev/macrdp that referenced this pull request Oct 29, 2026
It answers from the calling process's point of view and cannot tell whether
the cursor is hidden (found in upstream review of clintcan#219); a hidden cursor is
already caught as a fully transparent image.
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.

2 participants