Skip to content

Ubuntu 26.04 LTS ships a Wayland-only GNOME session. XWayland still runs X11 applications, but the native path is Wayland, and the assumptions that made X11 apps work are often exactly the assumptions that break under Wayland.

Wayland is not “X11 with a different socket”. It is a different security model. The compositor is the display server, the window manager, the input router, and the policy layer. Clients render their own buffers and describe their surfaces; they do not get to inspect or control the rest of the session.

That model is what makes fractional scaling, per-monitor DPI, input methods, and secure screen sharing tractable and secure. It is also why apps that rely on global coordinates, synthetic input, or framebuffer capture stop working.

┌──────────────────────────────────────────────────────────────┐
│ Your application │
│ GTK 4 / Qt 6 ── renders buffers ── describes surfaces │
└───────────────────────────┬──────────────────────────────────┘
│ Wayland protocol
┌───────────────────────────▼──────────────────────────────────┐
│ Compositor (Mutter, KWin…) │
│ window placement · input routing · scaling · focus · policy │
└───────────────┬──────────────────────────────┬───────────────┘
│ │
┌───────▼────────┐ ┌────────▼─────────┐
│ XWayland │ │ Portals / D-Bus │
│ legacy X11 │ │ screen capture │
└────────────────┘ └──────────────────┘

Your app talks to the compositor through the toolkit. It talks to desktop services through D-Bus and XDG Desktop Portals. It should not talk to the kernel’s input devices, the DRM framebuffer, or another app’s window.

Concern X11 habit Wayland-native approach
Window position XMoveWindow to absolute coordinates Let the compositor place it; save size, not position
Screen capture XGetImage / framebuffer grab ScreenCast portal + PipeWire
Global shortcuts XGrabKey GlobalShortcuts portal, or app-local shortcuts
Input injection XTest Not allowed; design an accessible in-app path
Clipboard X selection ownership Gdk.Clipboard; asynchronous, portal-mediated where needed
Scaling Integer scale factor Fractional scale protocol; toolkit handles it
Decorations Reparenting window manager Client-side decorations by default; xdg-decoration negotiation
Focus stealing XRaiseWindow / _NET_ACTIVE_WINDOW Request activation through the toolkit; compositor decides
Idle inhibition X screensaver extension org.freedesktop.portal.Inhibit / Gtk.Application.inhibit
Accessibility AT-SPI over X AT-SPI over D-Bus; toolkit exposes semantics

Wayland deliberately gives clients no global coordinate system. Gtk.Window has no “move to 1920, 400”. The compositor may be tiled, may have multiple monitors with different scales, or may place the window on a workspace you cannot see.

Do: save and restore window size, maximized state, and fullscreen state. Let the compositor choose the monitor and position. If the app needs a specific monitor, use the toolkit’s monitor APIs for sizing and fullscreen, not absolute coordinates.

Avoid: move(), set_position(), or X11 XMoveWindow calls. They either do nothing or behave inconsistently.

Use client-side decorations, but do not reinvent them

Section titled “Use client-side decorations, but do not reinvent them”

GTK 4 and Qt 6 draw client-side decorations by default. A Adw.HeaderBar or a Qt window frame participates in tiling, dragging, resizing, and accessibility. A custom titlebar that draws its own drag logic usually breaks at least one of those.

Do: use Adw.ApplicationWindow + Adw.HeaderBar, or QMainWindow with the platform theme. If you need a custom header, keep the toolkit’s window controls and drag handling.

Avoid: hiding the system close button, drawing fake window controls, or implementing window dragging with raw pointer events.

Fractional scaling is enabled by default on Ubuntu 26.04. A 125% or 150% session is normal. Toolkit layouts should use logical pixels and vector assets; the compositor and toolkit handle the rest.

Do

  • Use SVG icons and scalable UI assets.
  • Test at 100%, 125%, 150%, 200%, and with two monitors at different scales.
  • Use toolkit spacing and font scaling rather than hardcoded pixel sizes.
  • Let text wrap and windows resize; do not assume a minimum width that only fits your development monitor.

Avoid

  • Raster assets at a single size.
  • Fixed-size windows that clip at 200%.
  • Manual scale-factor math in application code.

GTK 4 handles fractional scaling through the compositor’s scale protocol. If you draw custom content with Gtk.Snapshot, use the widget’s logical coordinates and let the toolkit scale the surface.

An app that needs to record the screen, share a window, or take a screenshot uses the ScreenCast or Screenshot portal. The desktop shows a picker and a permission prompt. The app receives a PipeWire stream, not a framebuffer.

Do

  • Use org.freedesktop.portal.ScreenCast for recording and sharing.
  • Use org.freedesktop.portal.Screenshot for one-shot screenshots.
  • Handle the case where the user cancels or denies permission.
  • Stop the stream when the app is done; do not leave a recorder running.

Avoid

  • X11 screenshot APIs.
  • Reading /dev/dri or the framebuffer directly.
  • Recording without a visible indicator.

A global shortcut is a key grab that works while another app has focus. Under Wayland, that is a privileged capability. Use the GlobalShortcuts portal, which lets the desktop present the shortcut and lets the user change or revoke it.

For shortcuts that only work while your window is focused, use the toolkit’s shortcut system (Gtk.ShortcutController, QShortcut). That needs no portal and should be your default.

Wayland routes text input through the text-input and input-method protocols. GTK and Qt implement this, including Compose sequences, dead keys, and CJK input methods.

Do: use standard text widgets and let the toolkit handle input. Test with at least one non-Latin input method and with Compose sequences.

Avoid: intercepting raw key events to implement text entry, or assuming a US keyboard layout. If you implement a custom text editor, integrate with Gtk.IMContext (GTK) or QInputMethod (Qt).

Clipboard and primary selection are asynchronous

Section titled “Clipboard and primary selection are asynchronous”

Gdk.Clipboard reads and writes asynchronously. The clipboard may be owned by another process, and the data may arrive after a round trip. The primary selection (middle-click paste) is a separate clipboard on Linux.

Do: use Gdk.Display.get_clipboard() and get_primary_clipboard(), handle read_text_async/read_value_async, and provide a sensible fallback when the clipboard is empty.

Avoid: assuming the clipboard is synchronously available, or clearing it on exit. On Wayland, the clipboard survives the source app only if the compositor supports it; do not build features that depend on the source staying alive.

Wayland restricts focus stealing. An app cannot raise itself over another app without the compositor’s consent. Gtk.Application and QApplication use the org.freedesktop.Application D-Bus interface to activate an existing instance and pass files or URIs.

Do: use Gtk.Application with a stable application ID, implement open for file arguments, and let the toolkit handle activation. Use GApplication’s HANDLES_OPEN flag when the app opens documents.

Avoid: launching a second process and hoping the compositor focuses it. Avoid wmctrl, xdotool, or X11 activation hacks.

Long-running work (video export, a download, a presentation) may need to prevent idle suspend. Use the Inhibit portal or the toolkit API:

Python / GTK
cookie = app.inhibit(
window,
Gtk.ApplicationInhibitFlags.IDLE | Gtk.ApplicationInhibitFlags.SUSPEND,
"Exporting video",
)
# ... later
app.uninhibit(cookie)

Release the inhibitor as soon as the work finishes. An app that holds an inhibitor forever is a battery bug.

  • ☐ The app starts on a Wayland session with no X11-specific code path.
  • ☐ Window size and maximized state persist; position does not.
  • ☐ The UI works at 100%, 125%, 150%, and 200% scaling.
  • ☐ Icons are SVG or symbolic; no blurry raster assets.
  • ☐ File dialogs use Gtk.FileDialog or the portal.
  • ☐ Screen capture uses the ScreenCast portal.
  • ☐ Global shortcuts use the portal or are app-local.
  • ☐ Text input works with an IME and with Compose.
  • ☐ The app is single-instance and handles file-open activation.
  • ☐ The app does not hold idle inhibitors after work completes.
  • ☐ Accessibility works with Orca on Wayland.
Tool Use
WAYLAND_DEBUG=1 Log the protocol messages between the app and compositor
GTK_DEBUG=interactive Inspect the widget tree, layout, and render nodes
gtk4-demo / gtk4-widget-factory Compare behavior with canonical GTK apps
Mutter Devkit Test nested GNOME Shell with simulated monitors and scaling
weston --backend=headless Run a headless Wayland compositor for tests
wev Inspect input events on Wayland
wayland-info List globals and protocol support (wayland-utils)

For GNOME-specific behavior, Mutter Devkit in GNOME 50 can simulate HiDPI, fractional scaling, multiple monitors, and clipboard sharing. It is the fastest way to test layout without physical hardware.

Symptom Likely cause Fix
Window appears at a random position App calls move() Remove absolute positioning; save size only
Blurry UI at 125% Raster assets or manual scaling Use SVG and toolkit layouts
Screen share shows a black window App uses X11 capture Use the ScreenCast portal
Global shortcut does nothing X11 key grab Use GlobalShortcuts portal or app-local shortcut
Text input duplicates characters Custom key handling Use the toolkit’s text widgets and IM context
File dialog does not open in Flatpak Direct filesystem access Use Gtk.FileDialog / FileChooser portal
App steals focus X11 activation hack Use Gtk.Application activation
Clipboard empty after copy Asynchronous read not awaited Use async clipboard APIs and handle failure

Next: The XDG Contract — where apps put their data, and why it matters.