Skip to content

KDE Plasma: a KWin publisher of org.displayxr.WindowGeometry1 #1928

Open

Description

@danielcamposramos

Following discussion #1903.

Goal: DisplayXR apps work on KDE Plasma unchanged.
KWin serves the same org.displayxr.WindowGeometry name and the same three interfaces as the GNOME extension (version 12):

  • WindowGeometry1: GetWindows and WindowsChanged, schema v1 with monitor, moving, lattice_drop, move_sync, capture_excluded, layout_mode and device_scale.
  • WindowPlacement1: MoveWindow, GetPlacementCapabilities, SetDragLattice, SetDragLatticeAt, ClearDragLattice, BeginPointerDrag, EndPointerDrag, EnableMoveSync, and the signals DragLatticeNeeded and DragLatticeDone.
  • CaptureExclusion1: Exclude, Release and GetState, for the caller's own process only.

Where in KWin: a KWin plugin.
It starts in our KWin fork, ships in Sparky Stereo OS, and goes to KDE once it works.
The drag table is applied where KWin already snaps an interactively moved window, before the position is applied.
Move sync holds the window's scene item at the position its buffer was woven for, reading your 1x1 tag subsurface, with the same 100 ms and 6-frame stall fallback.

How we test:

  • sim_display with SIM_DISPLAY_OUTPUT=interlaced, in a headless KWin with a virtual output;
  • output scales 1, 1.25, 1.5, 1.6667 and 2;
  • the audits of the spec's §8.4 and §9.7: frames on the table, and frames painted where they were woven.

Questions:

  1. The spec asks every publisher to provide, conflict with and replace displayxr-window-geometry-publisher.
    GNOME Shell and KWin never run in the same session, so the bus name never has two owners.
    But that Conflicts would stop a GNOME publisher and a KWin publisher from being installed together on a system that has both desktops.
    Would you prefer a virtual name per compositor, or a Conflicts only between publishers for the same compositor?
  2. Is a native KWin plugin acceptable as the publisher, given the spec's wording around the extension?
  3. Should the first version target the extension's version 12 in full, or start with geometry, placement and the drag table, and add move sync next?
  4. KWin's layout is always logical, with a fractional scale per output.
    We would publish layout_mode: "logical" and a device_scale per output.
    Does that match what the consumer expects?

This issue was written with AI assistance; I direct and verify the work.

Activity

  1. dfattal commented on Oct 11, 2026

    @dfattal
    Collaborator

    Thanks, Daniel, clear questions.

    1. Packaging. You're right, that Conflicts is too broad; it dates from when GNOME was the only publisher. Let's split it: a per-compositor virtual name carries Provides/Conflicts/Replaces (displayxr-window-geometry-publisher-gnome, displayxr-window-geometry-publisher-kwin), and every publisher also Provides the generic displayxr-window-geometry-publisher without conflicting on it. Consumers keep depending on the generic name. We'll update §4 and the runtime .deb, which ships the GNOME extension today.

    2. A native KWin plugin is fine. The fixed identity is the bus name, object paths, interfaces and schema; the extension UUID is a GNOME packaging detail. The rule that carries over is "no reverse dependency": the plugin must not link to or call DisplayXR or any vendor stack, so any package can ship it and any runtime can consume it.

    3. Start with geometry, placement, the drag table and CaptureExclusion1; move sync next. Consumers discover features through the GetPlacementCapabilities bits, not a version number, so publish only the bits you implement and the runtime falls back on the rest. Move sync is the preferred mechanism where it's available (redraw at every move; otherwise constrain to the lattice), so it's the next step rather than an extra. Correcting before apply is better than what the GNOME extension can do; DragLatticeDone stats from both would let us compare the two directly.

    4. Yes, with two rules. Always send both fields: without layout_mode the consumer falls back to querying Mutter's DisplayConfig, which doesn't exist on Plasma. And device_scale must be KWin's real fractional output scale (device px per logical px), not the integer wl_output.scale; that's the 1.6667-vs-2 trap in §1.1. One more case worth a test: X11 apps under Xwayland, where Plasma's legacy-application scaling setting decides whether an X11 window reaches the panel 1:1.

    On your item 3: please propose the 1:1 field against the shared schema rather than as a KWin extra, so the GNOME publisher can carry it too.

  2. danielcamposramos commented on Oct 11, 2026

    @danielcamposramos
    Author

    Thank you, David: all four answers taken, and the packaging split is already in (4c1ad09, #1929).
    The KWin plugin follows them: no link to DisplayXR or any vendor stack, both fields always sent with KWin's real fractional scale, only the capability bits it implements, the Xwayland scaling case in its tests, and the 1:1 field proposed against the shared schema in its own issue.
    We will come back here when geometry, placement, the drag table and capture exclusion are ready for your panel.

  3. danielcamposramos commented on Oct 12, 2026

    @danielcamposramos
    Author

    The publisher is ready for your panel.
    Geometry, placement, the drag table applied before KWin applies a position, and capture exclusion are in our KWin fork: https://invent.kde.org/danielcamposramos/kwin/-/tree/stereo3d (commit cd61456540, all eight CI jobs green).
    It is packaged as displayxr-window-geometry-publisher-kwin 4:6.7.4-2+stereo3d18.1 in the Sparky Stereo OS repository, with a matching KWin, and a ready root file system with both, for the VKMS bench or your own tests, is at https://repo.sparkyos.org/bench/kwin-root/ (with the sources of every package we changed).
    Capability bits are 0 and 1; move sync is next.
    Right-button moves need no pointer drag on KWin: it ends an xdg_toplevel.move on the release of the button whose serial started it, so bit 2 stays clear and your app's own path works unchanged.

    Our rig at 150 % and 200 %, in the columns of your §8.7 and §8.8 tables (device px, plain nearest → your choice rule):

    scale drag correction rms / max sideways rms sideways max travel /100 px largest jump lead/lag max
    200 % 20° 2.93 / 5.10 → 3.06 / 5.49 1.12 → 0.57 2.48 → 1.88 50 → 12 3.4 → 1.9 5.04 → 5.49
    200 % horizontal 2.81 / 4.00 0 0 0 0 4.00
    150 % 20° 2.65 / 5.30 → 2.79 / 5.75 0.98 → 0.44 2.37 → 1.41 51 → 9 3.6 → 2.2 5.19 → 5.75
    150 % horizontal 2.59 / 4.50 0 0 0 0 4.50

    Our simulated display interlaces in columns only, so a straight drag has no sideways error to remove, and the 20° rows are the ones to compare: they move the way yours do in §8.8.
    Across 45 drags at five scales from 100 % to 200 %, 0 frames were painted off the table inside its coverage, and every drag ended on the table.

    Every entry is painted on the pixel your table chose, at 125 % and 166.67 % too.
    KWin snaps a client's window geometry to the pixel grid, so your 58 px title bar puts the content 73 device px below the frame at 125 % (58.4 logical px), and a whole frame position would leave the content one row off the table's pixel.
    So KWin puts each entry's frame where the content lands on round((buffer + entry - monitor) * scale).
    Held on 60 entries at five scales, your table's pixel, the consumer's conversion, the origin the runtime wove for and KWin's paint were the same pixel every time.

    Two questions from the same finding:

    • At rest, the integer wire. A client-decorated window KWin placed fractionally can sit on a device row no whole value names: at 166.67 % the surface is painted on row 319, while the published buffer of 191 converts to 318. Would you rather add a field with the exact fractional rect (for example buffer_f, read only by consumers that know it), or have the consumer read the number as a double? The second changes what today's consumers do with a fractional value, so publishers would keep sending whole numbers to older ones.
    • MoveWindow at drop. Your drop-time snap moves the frame by the logical move it chose for the buffer. On KWin at 125 % and 166.67 %, a whole frame position can leave a client-decorated buffer one row off. May a publisher place the frame so the buffer lands on round((buffer + move - monitor) * s), as the drag table now does? A drop on the table needs no snap, so this only touches drops off it.

    One small thing in displayxr-common: DxrWlPlacement::get_own_geometry finds keys by string search, and it dereferenced a null pointer when a publisher sent the keys in sorted order. Parsing the JSON would make it safe for any publisher. We now send the keys in the GNOME extension's order, so it does not affect us.

    When you have a moment, a run on your 3840x2160 lenticular panel at scale 2, and at 125 % if you can, would tell us what the simulated display cannot.

  4. dfattal commented on Oct 12, 2026

    @dfattal
    Collaborator

    Thanks, Daniel. This is a thorough first drop. Zero off-table frames across 45 drags, and checking paint against weave origin entry by entry, is exactly the evidence we wanted.

    The parser crash is fixed: DisplayXR/displayxr-common#76 (merged). get_own_geometry now reads the payload by its structure, so key order, whitespace and nested objects no longer matter, and a sorted-key payload is in its tests. It reaches apps with the next displayxr-common release and the repins that follow; after that you can send keys in whatever order KWin likes.

    At rest, the integer wire: neither, please. Publish the pixels. Both options keep the consumer converting logical to device pixels and guessing the compositor's rounding, and your 166.67 % case shows the guess can't be right for every compositor. So the field we'd like is the rect the compositor actually paints the buffer at:

    • buffer_device: [x, y, w, h] per window, in device pixels, relative to the top-left of the window's monitor. Integers, since device pixels are whole by definition.
    • A consumer that finds it uses it directly and skips the conversion. Without it, today's round((buffer - monitor) * scale) path stays, so older consumers and publishers are unaffected.
    • w/h against the buffer's own size is also your 1:1 test. Please fold this into the 1:1-field issue you're opening against the shared schema. The GNOME publisher can carry it too, since Mutter knows where it places the actor.

    MoveWindow at drop: yes, and it's needed, not just allowed. The runtime judges a drop-snap landing by the content's device position, not the frame's. A whole-pixel frame that leaves the buffer one row off is logged as a miss. So placing the frame so the buffer lands on round((buffer + move - monitor) * s) is what the check expects. We'll write that into §7.1: x/y still name the frame, and the publisher may adjust it so the buffer lands where the move puts it. With buffer_device the target becomes exact by construction.

    Right-button moves: good to know KWin ends the move on the right button's release. Leaving bit 2 clear is correct.

    Panel run: yes. The DS1 here is 3840×2160 lenticular. It runs Ubuntu 26.04 with GNOME, so first we'll check whether your KWin packages install next to Ubuntu's Plasma or whether we boot your root filesystem. Then we'll run your audits at 200 % and 125 % and post the numbers here in the same columns.

  5. danielcamposramos commented on Oct 12, 2026

    @danielcamposramos
    Author

    Thank you, David, and thanks for #76.

    buffer_device: yes.
    KWin knows the device rect it paints each surface at, so the publisher will send it.
    I'll open the shared-schema issue with buffer_device and the 1:1 test together, as you suggest.

    MoveWindow at drop: understood.
    The publisher will place the frame so the buffer lands on round((buffer + move − monitor) × s), matching §7.1.
    Both come in our next drop, together with move sync, measured the same way.

    For the DS1: our KWin packages are built for Debian testing (Plasma 6.7.4 on Qt 6.11.2), so they won't install next to Ubuntu 26.04's Plasma.
    The root file system at https://repo.sparkyos.org/bench/kwin-root/ carries the whole stack with the publisher, and the sources of every package we changed.
    Sparky Stereo OS itself is still in development, so there is no installable image yet.

  6. danielcamposramos commented on Oct 12, 2026

    @danielcamposramos
    Author

    Thank you, David, for the quick and clear answers.
    get_own_geometry: thank you for displayxr-common#76.
    buffer_device: our KWin publisher sends it now, taken from where KWin really paints the main surface.
    At 100, 125, 150, 166.67 and 200 %, with three windows at six whole and fractional positions each, it equalled the painted rect in the capture 90 times out of 90, while the conversion of buffer was one pixel off in 45 of them.
    The shared-schema issue, with the 1:1 test folded in, is #1991.
    MoveWindow at the drop: thank you for #1985; KWin does what it now says: x and y still name the frame, and KWin places the frame so the buffer lands on round((buffer + move - monitor) * scale).
    On a window whose surface sits off whole logical positions inside its frame, 40 calls out of 40 landed on that pixel at the five scales.
    Your runtime's drop snaps logged LANDED 26 times out of 26, and every drop was painted where it was woven for.
    Move sync: in, with capability bits 3 and 4, so the capabilities now read 27.
    Our audit reads your origin stamp from every frame KWin's screen cast sends.
    Across six drag types at five scales, 9,219 frames on the final head, none painted off the origin it was woven for.
    With the app stopped for 670 ms, one timeout and one re-sync per drag, as in your section 9.7.
    With the tag never mapped, or toggled every 300 ms, the hold behaves as your section 9.9 describes.
    Right button: agreed, bit 2 stays clear.
    One thing we noticed: displayxr-common refuses a right-button content drag on KWin ("button 3 does not move the window here"), because wl_drag_env() counts any publisher with the drag table as mutter, while KWin's own move grab ends on that button's release.
    Would deciding mutter from gtk_shell1 alone suit you?
    Where it is: our KWin fork's stereo3d branch (1d07ceb1c1), packaged as KWin 4:6.7.4-2+stereo3d18.2, and the root file system at https://repo.sparkyos.org/bench/kwin-root/ now carries it.
    DS1: whatever your team finds simplest there is fine with us, and we are glad to answer any question.

  7. dfattal commented on Oct 13, 2026

    @dfattal
    Collaborator

    Thanks, Daniel. LANDED 26/26, plus 9,219 move-sync frames each painted at the origin it was woven for, is what we hoped to see.

    Right-button drag on KWin: yes, and you're right that it's our bug. wl_drag_env() treated any publisher offering the drag table as proof the compositor was mutter. That held only while the GNOME extension was the only publisher. Mutter advertises gtk_shell1 to every client, so that alone now decides it, and on KWin a secondary-button content drag goes to xdg_toplevel.move. The fix is DisplayXR/displayxr-common#78, with the KWin case in its tests. It reaches apps through the same release-and-repin path as #76.

    buffer_device: the runtime consumer is #2019, and I've answered your two wording questions on #1991.

    DS1: your root filesystem is the route. The panel here is shared with other work, so we'll schedule the boot and post the numbers here in your columns.

  8. danielcamposramos commented on Oct 13, 2026

    @danielcamposramos
    Author

    Thank you, David.

    Right-button drag: thank you for displayxr-common#78.
    We will run it against our KWin publisher on the same rig and post the result here.

    buffer_device: thank you for the answers on #1991 and for #2019.
    We will run #2019 on the same rig at 100, 125, 150, 166.67 and 200 %, with the same three windows, and post the numbers here.

    DS1: thank you.
    Before your boot we will uplift the installation in the root file system at https://repo.sparkyos.org/bench/kwin-root/ to our latest KWin and the rest of our current stack.
    We will nudge you here when the new image is up.

  9. danielcamposramos commented on Oct 13, 2026

    @danielcamposramos
    Author

    The new root is up: https://repo.sparkyos.org/bench/kwin-root/
    It carries our KWin 4:6.7.4-2+stereo3d20 with the publisher, Mesa 26.1.6-1+stereo3d10, libkscreen +stereo3d7 and the phone-headset stack (KPipeWire, KRdp, our protocols); README.md lists every change, and the 2026-10-12 root stays under previous/2026-10-12/.
    Like the previous one, it has no kernel and no boot loader; if the DS1 boot needs them inside the root, tell us which and we will add them.

    Both PRs ran on that root: headless KWin at 3840x2160 device px, the runtime at main 0929dd3, cube_handle_vk_linux on displayxr-common v2.32.0, sim_display interlaced (period 5), and the same three windows as before (the red "bd" with its surface 10 logical px above the frame, the blue "bn", and the cube with DXR_WL_ORIGIN_STAMP=1).
    Each run has a control: the same build with the change taken out.

    #2019, with the frames at six logical positions, fractional ones included:

    scale buffer_device = painted old conversion differs from painted cube woven for the painted pixel control: buffer_device ignored
    100 % 18/18 0/18 6/6 6/6
    125 % 18/18 9/18 6/6 4/6
    150 % 18/18 9/18 6/6 3/6
    166.67 % 18/18 15/18 6/6 1/6
    200 % 18/18 12/18 6/6 2/6

    In the control, all 14 misses landed exactly on the conversion: at 150 %, position 200.4,201.6, the frame is painted at 301,1139 and the conversion gives 302,1139.
    The runtime logged "content device rect is the publisher's buffer_device" in every #2019 run and in none of the control runs.

    #78, with a right press on the cube, a drag of +100,+50 logical in ten steps, the release, then the pointer moved +300,+200 with no button:

    • at all five scales the window follows the drag (+100,+50, moving true), stops on the right-button release (moving false), and stays put while the pointer moves on;
    • after the drop, buffer_device, the painted pixel and the woven origin agree;
    • with 86ee0a08b7 reverted, the frame never moves and the cube warns that button 3 does not move the window.

    One rig note: the first 200 % pass pressed too low on the cube, and the pointer reached the bottom edge of the output, so the drag read +100,+44; with the press higher it reads +100,+50 like the other scales, and the tables are from that rerun.

    Thank you for both, David.

  10. dfattal commented on Oct 14, 2026

    @dfattal
    Collaborator

    Thanks, Daniel. The control runs are what make these convincing: all 14 misses landed exactly on the old conversion, the log line appeared only with the change, and with #78 reverted the drag fails. That settles both changes.

    No kernel or boot loader needed for now. Rather than booting the DS1 into your root filesystem, we'll first run it in a chroot on this laptop's own kernel (Linux 7.0) and start kwin_wayland on a free virtual terminal so it drives the DS1 through DRM. The GPU here is Intel Panther Lake on the xe driver, so your Mesa 26.1.6 inside the root should work as is, and the laptop's own install stays untouched. If DRM or seat access gets in the way, we'll tell you exactly what's missing before asking you to add a kernel.

    The panel is busy with other work right now. Once it's free we'll run your audits on it at 200 % and 125 % and post the numbers here in your columns.

  11. danielcamposramos commented on Oct 14, 2026

    @danielcamposramos
    Author

    Thank you, David.

    Good to read that both changes are settled.

    The chroot plan is fine with us, and it keeps your laptop's install untouched.
    One note that may save a round: KWin picks its session in this order, logind, then ConsoleKit, then a no-op session that opens /dev/dri and /dev/input directly (src/core/session.cpp).
    So from a text login on the free terminal, with /dev, /proc, /sys and /run bind-mounted into the root and kwin_wayland started as that same user (chroot --userspec), it takes the DS1 through logind and switching terminals keeps working.
    Started as root outside a login session, it falls back to the no-op session, which needs the laptop's own compositor to let go of the GPU first.

    We will wait for the panel and for your numbers at 200 % and 125 %.

    And an offer, the same one we made this week to wiz3D's maintainer and to the author of KDE's video wallpaper plugin.
    DisplayXR is a collaboration we value, so we would like your software to live inside our branch, with you in charge of it:

    1. The KWin side stays in our KWin stereo3d branch, where the window-geometry publisher already is, and ships as displayxr-window-geometry-publisher-kwin; the output filter that weaves our full side-by-side frame would join it there whenever you want it.
    2. The runtime would ship as a native Debian package in our repository, built from your release tags. We do the packaging, the builds and the tests on our sets; you decide: every packaging change comes to you as a pull request, and an update ships when you say a release is ready.
    3. Its repository would sit in the Sparky-OS organisation with admin rights for you and anyone you name, as wiz3D does now (https://github.com/Sparky-OS/wiz3D) and as KDE's video wallpaper plugin does with its author keeping admin (https://github.com/Sparky-OS/org.kde.video).
    4. The licence and the credits stay yours: BSL-1.0 file by file, with DisplayXR and its authors named in the package and in our attributions.

    None of it is required, and it never changes how DisplayXR works on other desktops.

    Daniel

  12. dfattal commented on Oct 14, 2026

    @dfattal
    Collaborator

    Thanks, Daniel, both for the session note and for the offer.

    The chroot note is useful. We'll start kwin_wayland from a text login on the free terminal with chroot --userspec, so it goes through logind. Numbers at 200 % and 125 % will follow here once the panel is free.

    On the offer: yes to packaging, and thank you. A distribution that wants to ship DisplayXR is exactly what the project is for. Here's how we'd like to do it:

    1. KWin side: yes. The publisher stays in your stereo3d branch and ships as displayxr-window-geometry-publisher-kwin, as it does now. The weave filter is welcome too, as a generic weave of your full side-by-side frame for simulated and test output. Weaving for a specific panel stays in that vendor's plug-in, behind the runtime.
    2. Runtime package: yes, on the terms you describe. You build from our release tags; packaging changes come to us as pull requests; a version ships when we say the release is ready. Please keep the packaging in a Sparky-OS repository and add us as reviewers. We'll tell you about each release, and we'd ask that the package description say it is built by Sparky from upstream DisplayXR, with bugs going to your tracker first. We'll pick up anything that turns out to be upstream.
    3. The repository stays where it is. DisplayXR is meant to be a neutral home that several vendors and distributions build on. Its repositories stay in the DisplayXR organization so that no single company or distribution owns them, Leia included. This isn't a judgment on Sparky. The same rule applies to every distribution and vendor. You are welcome as a contributor, and the measured work on this issue is the kind we want more of.
    4. License and credits: agreed. BSL-1.0 file by file, with DisplayXR and its authors named.

    One suggestion. The publisher is most useful if KDE takes it upstream, because then every Plasma desktop gets DisplayXR, not only Sparky's. When you're ready to propose it to KDE, tell us and we'll back it with the spec, the conformance audits and our panel results.

    Once Sparky Stereo OS has an installable image, we'd be glad to list it on displayxr.org as a Linux distribution that ships DisplayXR.

  13. danielcamposramos commented on Oct 14, 2026

    @danielcamposramos
    Author

    Thank you, David.

    Agreed on every point, and the neutral home makes sense to us: we will build on it the way any other distribution would.

    What we will do:

    1. The packaging goes to a new repository, https://github.com/Sparky-OS/displayxr-packaging, with you and anyone you name as reviewers; please tell us the GitHub handles.
    2. The package builds from your release tags, and a version ships when you say the release is ready.
    3. Its description will say it is built by Sparky Stereo OS from upstream DisplayXR, with bugs reported to our tracker first (https://github.com/Sparky-OS/sparky-stereo-os/issues); whatever turns out to be upstream comes to you.
    4. The weave filter will be the generic one, for simulated and test output; a panel's own weaving stays in its vendor's plug-in, behind the runtime.

    On KDE: yes, and we would be glad of your help there.
    A proposal backed by your spec, your conformance audits and your panel results will carry more weight than ours alone.
    Everything it needs is public:

    Tell us how you would like to go about it, and we will follow your lead.

    And thank you for the listing offer; we will tell you when the installable image is out.

    Daniel

  14. dfattal commented on Oct 14, 2026

    @dfattal
    Collaborator

    Thanks, Daniel.

    Reviewer: please add dfattal on displayxr-packaging for now. We'll name more as the Linux work grows.

    KDE: panel numbers first, then your merge request. Everything so far ran on a simulated display, so real-panel results are what will make the case. We'll run your audits on two different 3D monitors, the Acer DS1 here and a Samsung Odyssey 3D, at 200 % and 125 %, and post the numbers in this issue in your columns. Then:

    1. You open the merge request against KWin upstream (invent.kde.org/plasma/kwin), since it's your code. Link the spec, STEREO3D.md and the measured runs.
    2. We comment on it with the panel results and say that DisplayXR supports it as the KWin publisher of org.displayxr.WindowGeometry1.
    3. Before the merge request, a short note to the KWin developers saying what is coming and why will help it get reviewed rather than arrive cold.

    We'll post here when the panel runs are done.

  15. danielcamposramos commented on Oct 14, 2026

    @danielcamposramos
    Author

    Thank you, David.

    dfattal is invited to displayxr-packaging with write access, and a CODEOWNERS file asks for your review on every change.

    KDE: agreed, panel numbers first.
    We will wait for the Acer DS1 and Odyssey 3D results here.

    Daniel

  16. dfattal commented on Oct 14, 2026

    @dfattal
    Collaborator

    Thanks, Daniel. displayxr-packaging is set up, and the README matches what we agreed.

    First panel run: the chroot route works. Your 2026-10-13 root runs with no kernel or boot loader. Following your session note, kwin_wayland (+stereo3d20) started in a logind session on tty3 (via systemd-run with PAMName=login and RootDirectory= set to the root) and drove the laptop's eDP and a Samsung Odyssey 3D (3840x2160 over HDMI, Intel Panther Lake, xe) through DRM. Our runtime (main, with #2019) and cube_handle_vk_linux (displayxr-common v2.32.0) ran from the host as Wayland clients, against a separate session bus for your KWin. On real hardware:

    • the geometry service connected, and move sync turned on;
    • the runtime took buffer_device as the content rect ("content device rect is the publisher's buffer_device");
    • the 58 px client-decorated title bar was measured correctly, and a window on the laptop was held flat, as it should be.

    Two things the root needs for a person in front of it, which we're working around for now: there's no cursor theme (KWin logs "Unable to load any cursor theme" and draws no pointer), and no PipeWire client config (kwin_screencast: Failed to create PipeWire context). A cursor theme such as breeze-cursor-theme or adwaita-icon-theme in the root would fix the first.

    Could you share the audit scripts behind your tables? We want the 200 % and 125 % numbers from the Odyssey and the DS1 to be measured the same way as yours, in your columns: placement against buffer_device, the origin stamp read from captures, drop landings, and move sync. Our own harness (scripts/linux/move_sync/) reads the GNOME extension's log, so it can't run against KWin. Until we have yours, we'll post what the runtime logs on the panel and check the origin stamp in KWin screenshots against buffer_device.

  17. dfattal commented on Oct 14, 2026

    @dfattal
    Collaborator

    Panel run on the Samsung Odyssey 3D at 200 %: a real KWin on real DRM, with a person dragging. It found one bug in the publisher.

    Setup: your 2026-10-13 root (KWin +stereo3d20) on the laptop's Intel Panther Lake GPU, with the Odyssey (3840x2160) over HDMI. Our runtime ran at main (with #2019) and cube_handle_vk_linux on displayxr-common v2.32.0, with sim_display interlaced and DXR_WL_ORIGIN_STAMP=1. Each capture went through org.kde.KWin.ScreenShot2.CaptureScreen with native resolution, and the stamp was read from it. Each capture was compared with a fresh GetWindows.

    result
    buffer_device equals the painted pixel 269 / 269 captures
    the old conversion of buffer equals the painted pixel 0 / 269
    moving the window between the laptop and the Odyssey switched correctly between flat and woven
    move sync during drags re-woven every refresh

    The bug: no WindowsChanged when buffer_device changes at the drop. After a drag, the window came to rest at frame [2226, 219], and from then on:

    • KWin painted the content at device (851, 553), and a fresh GetWindows reported buffer_device [851, 553, ...], so the reported value was right.
    • No WindowsChanged was emitted for that change. The runtime follows the signal, so it kept weaving for the last value it had been sent, (852, 554), which is the conversion and what your publisher reports while move sync holds the window. Every stamp read in that rest showed woven (852, 554) painted at (851, 553), one pixel off in both axes, for minutes.
    • As soon as any other change made the publisher signal (a second window opened), the runtime wove for (851, 553), exactly on the paint.

    So the release of the hold, where KWin moves the paint to the window's fractional resting position, needs to emit WindowsChanged. More generally, emit whenever buffer_device changes, since §2 says the signal fires on any change. Your rig would likely miss this if its windows are placed by MoveWindow rather than dragged to a fractional rest by hand. A drag in sub-logical-pixel pointer steps, then a pause, should reproduce it.

    Two smaller notes from the same run:

    • MoveWindow(pid, x, y) from a client other than the window's own returned false each time (I called it from a test script, not the app). If the publisher only accepts its caller's own windows, that's fine, but please say so in the README.
    • We lost the 125 % pass to a problem on our side, so there are no numbers for it yet. Once your signal fix is in, 125 % on your headless rig plus a hand-drag rest case would cover it. We'll re-check on the DS1 and the Odyssey after that.
  18. danielcamposramos commented on Oct 14, 2026

    @danielcamposramos
    Author

    Thank you, David.
    This is the run we were waiting for, and the bug is ours.

    When move sync lets go of the window at the drop, KWin moves the paint to the fractional rest, but the publisher sends nothing for it.
    We are fixing it two ways: the release itself will signal, and after every painted frame the publisher will compare what it would publish with what it sent last, and signal on any difference, as §2 says.
    Our rig will first reproduce it your way, a drag in sub-pixel steps and then a pause, and then show the fix on the same case.

    MoveWindow acts only on the calling process's own windows, by design: KWin takes the process from the bus connection.
    We will say so in the README.

    When the fixed package is out, we will post it here with the 125 % and 200 % numbers from our rig, the hand-drag rest included, so you can re-check on the DS1 and the Odyssey.

    Daniel

  19. danielcamposramos commented on Oct 14, 2026

    @danielcamposramos
    Author

    Thank you, David, for the first panel run.
    269 of 269 on the Odyssey is what we hoped the real glass would say.

    The bug is real, and it is fixed.
    We reproduced it on our headless rig with your description: a drag in sub-logical-pixel steps, then a pause.
    At 200 % the last WindowsChanged went out at the release, while move sync still held the window, with buffer_device (302, 418); at rest GetWindows said (301, 417), KWin painted there, and your runtime kept weaving for (302, 418).
    At 125 % the same, one row off.
    The publisher now signals when the hold releases, and after each presented frame it compares what GetWindows would publish with the last payload it sent and signals when they differ, at most once per frame.
    With the fix, on the same drags, GetWindows, the last signal, the paint and the weave name the same pixel in 5 of 5 rests at 200 % and 5 of 5 at 125 %, the signal 4 to 19 ms after the release.
    It ships in our next KWin set, and the root below is rebuilt with it the day that set is published.

    The audit kit is public: https://github.com/Sparky-OS/displayxr-kwin-audit (BSL-1.0, the same as DisplayXR).
    It works from KWin alone: the bus, KWin's own screenshots and screen cast, KWin's fake input and KWin scripts.
    Its tests write your columns: placement against buffer_device (captures, buffer_device = painted, old conversion = painted), MoveWindow, drop landings, move sync frame by frame, and the rest case you found (GetWindows, the last signal, the paint, the weave).
    docs/chroot.md has your setup: kwin_wayland with KWIN_SCREENSHOT_NO_PERMISSION_CHECKS=1 and KWIN_WAYLAND_NO_PERMISSION_CHECKS=1, /run shared between the root and the host, the kit on either side.
    It is also inside the new root at /opt/displayxr-kwin-audit, built.

    The new root is up: https://repo.sparkyos.org/bench/kwin-root/ (2026-10-14; the 2026-10-13 one under previous/).
    It adds the Oxygen cursors with Breeze's as the fallback, PipeWire's client configuration with its modules, the daemon and WirePlumber, KWin +stereo3d21, and the kit with what it needs.

    Our 125 % numbers, headless, KWin with the fix, your runtime at main (d714cee) with #2019, sim_display interlaced, 200 % beside them:

    scale captures buffer_device = painted old conversion = painted
    125 % 18 18/18 9/18
    200 % 18 18/18 6/18

    MoveWindow 8 of 8 at both; drops 6 of 6 at both; move sync 0 frames painted off their woven origin while held at both (1983 of 1998 and 2126 of 2150 cast frames, the rest the frozen drag's between its timeout and re-sync); the rest case 5 of 5 at both.

    MoveWindow from another process returns false by design: it acts only on the calling process's own windows, and the process is taken from the bus connection; the pid argument must name it or be 0.
    The kit's README says so now.

    We wait for your 200 % and 125 % on the Odyssey and the DS1.

    Daniel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions