std1.0.0-beta-7 SafeC docs

Source gui/gui_wayland.sc

View in the repository

// SafeC Standard Library — GUI backend: Wayland (see gui_wayland.h for
// what it covers and gui_backend.h for the contract it implements).
//
// libwayland-client is dlopen()ed, never linked, and driven through its
// stable C ABI: wl_proxy_marshal_array_flags() (or, before libwayland 1.20,
// wl_proxy_marshal_array_constructor_versioned() + wl_proxy_destroy()) to
// send requests with their arguments in a union wl_argument array, and
// wl_proxy_add_dispatcher() to receive every event of an object in one
// function with (opcode, arguments) — so no per-event C listener structs
// are needed. The core interfaces (wl_surface_interface, ...) are data
// symbols of the library; the extension protocols' struct wl_interface
// tables (xdg-shell, xdg-decoration, viewporter, fractional-scale,
// cursor-shape) are built here at run time from their XML's request/event
// signatures — what wayland-scanner's C output would contain.
// safeguard: link linux -ldl
// safeguard: deb-recommends linux libwayland-client0 libxkbcommon0 libwayland-cursor0
#pragma once
#include <std/stddef.h>
#include <std/gui/gui.h>
#include <std/gui/gui_backend.h>
#include <std/gui/gui_wayland.h>

#if defined(__linux__)
#include <std/mem.h>
#include <std/time.h>
#include <std/collections/string.h>
extern char* getenv(const char* name);
extern double atof(const char* s);
extern int pipe(int* fds);
extern long write(int fd, const unsigned char* buf, unsigned long n);
extern long read(int fd, unsigned char* buf, unsigned long n);
extern int close(int fd);
extern int fcntl(int fd, int cmd, ...);
extern int open(const char* path, int flags, ...);
extern int mkstemp(char* templ);
extern int unlink(const char* path);
extern int ftruncate(int fd, long length);
extern unsigned char* mmap(unsigned char* addr, unsigned long len, int prot, int flags, int fd, long off);
extern int munmap(unsigned char* addr, unsigned long len);
#endif

namespace std {

#if defined(__linux__)

// ── libwayland-client, libxkbcommon, libwayland-cursor (dlopen) ──────────

// A dlopen() handle, and a symbol looked up in one: untyped until cast to
// the function's real signature, or the data's type (in 'unsafe', which
// vouches for it).
struct WlLib_ { char opaque_; };
typedef ?fn void() WlSym_;
extern WlLib_* dlopen(const char* file, int mode);
extern WlSym_ dlsym(WlLib_* handle, const char* name);

// Opaque libwayland / xkbcommon / wayland-cursor objects: the display, a
// protocol object (wl_proxy: every wl_surface, wl_seat, xdg_toplevel, ...),
// the keyboard state and the cursor theme.
struct WlDisplay_       { char opaque_; };
struct WlProxy_         { char opaque_; };
struct XkbContext_      { char opaque_; };
struct XkbKeymap_       { char opaque_; };
struct XkbState_        { char opaque_; };
struct XkbComposeTable_ { char opaque_; };
struct XkbComposeState_ { char opaque_; };
struct WlCursorTheme_   { char opaque_; };

// struct wl_message / struct wl_interface (wayland-util.h).
struct GuiWlMessage {
    const char* name;
    const char* signature;
    const struct GuiWlInterface** types;
};

struct GuiWlInterface {
    const char* name;
    int version;
    int methodCount;
    const GuiWlMessage* methods;
    int eventCount;
    const GuiWlMessage* events;
};

// struct wl_cursor / wl_cursor_image (wayland-cursor.h)
struct GuiWlCursorImage {
    unsigned int width;
    unsigned int height;
    unsigned int hotspotX;
    unsigned int hotspotY;
    unsigned int delay;
};

struct GuiWlCursor {
    unsigned int imageCount;
    GuiWlCursorImage** images;
    char* name;
};

// A proxy's dispatcher (wl_dispatcher_func_t): gets every event of the
// object as (opcode, arguments); 'impl' is what add_dispatcher was given
// (this file's per-object state, erased). 'args' is the union wl_argument
// array, 8 bytes an entry.
typedef fn int(?&Erased, WlProxy_*, unsigned int, const GuiWlMessage*, unsigned long long*) WlDispatcher_;

typedef fn WlDisplay_*(const char*) WlConnect_;
typedef fn void(WlDisplay_*) WlDisplayVoid_;
typedef fn int(WlDisplay_*) WlDisplayInt_;
typedef fn void(WlProxy_*) WlProxyVoid_;
typedef fn WlProxy_*(WlProxy_*, unsigned int, const GuiWlInterface*, unsigned int, unsigned int, unsigned long long*) WlMarshalFlags_;
typedef fn WlProxy_*(WlProxy_*, unsigned int, unsigned long long*, const GuiWlInterface*, unsigned int) WlMarshalCtorV_;
typedef fn int(WlProxy_*, WlDispatcher_, ?&Erased, ?&Erased) WlAddDispatcher_;
typedef fn unsigned int(WlProxy_*) WlGetVersion_;

typedef fn XkbContext_*(int) XkbContextNew_;
typedef fn XkbKeymap_*(XkbContext_*, const char*, int, int) XkbKeymapFromString_;
typedef fn void(XkbKeymap_*) XkbKeymapUnref_;
typedef fn void(XkbState_*) XkbStateUnref_;
typedef fn XkbState_*(XkbKeymap_*) XkbStateNew_;
typedef fn int(XkbState_*, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int) XkbUpdateMask_;
typedef fn unsigned int(XkbState_*, unsigned int) XkbKeyU_;
typedef fn int(XkbState_*, const char*, int) XkbModActive_;
typedef fn int(XkbKeymap_*, unsigned int, unsigned int, unsigned int, const unsigned int**) XkbSymsByLevel_;
typedef fn int(XkbKeymap_*, unsigned int) XkbKeyRepeats_;
typedef fn XkbComposeTable_*(XkbContext_*, const char*, int) XkbComposeTableNew_;
typedef fn XkbComposeState_*(XkbComposeTable_*, int) XkbComposeStateNew_;
typedef fn int(XkbComposeState_*, unsigned int) XkbComposeFeed_;
typedef fn int(XkbComposeState_*) XkbComposeStatus_;
typedef fn unsigned int(XkbComposeState_*) XkbComposeKeysym_;
typedef fn void(XkbComposeState_*) XkbComposeReset_;

typedef fn WlCursorTheme_*(const char*, int, WlProxy_*) WlCursorThemeLoad_;
typedef fn void(WlCursorTheme_*) WlCursorThemeDestroy_;
typedef fn GuiWlCursor*(WlCursorTheme_*, const char*) WlCursorThemeGet_;
typedef fn WlProxy_*(GuiWlCursorImage*) WlCursorImageBuffer_;

struct GuiWlLib {
    int loaded;                        // 0 not tried, 1 ok, -1 unavailable
    WlConnect_ display_connect;
    WlDisplayVoid_ display_disconnect;
    WlDisplayInt_ display_flush;
    WlDisplayInt_ display_get_fd;
    WlDisplayInt_ display_dispatch_pending;
    WlDisplayInt_ display_roundtrip;
    WlDisplayInt_ display_prepare_read;
    WlDisplayInt_ display_read_events;
    WlDisplayVoid_ display_cancel_read;
    WlDisplayInt_ display_get_error;
    int hasMarshalFlags;               // 0 before libwayland 1.20
    WlMarshalFlags_ marshal_flags;
    WlMarshalCtorV_ marshal_ctor;
    WlProxyVoid_ proxy_destroy;
    WlAddDispatcher_ add_dispatcher;
    WlGetVersion_ get_version;
    // core interfaces (data symbols)
    const GuiWlInterface* iRegistry; const GuiWlInterface* iCallback; const GuiWlInterface* iCompositor;
    const GuiWlInterface* iSurface; const GuiWlInterface* iRegion; const GuiWlInterface* iShm;
    const GuiWlInterface* iShmPool; const GuiWlInterface* iBuffer; const GuiWlInterface* iSeat;
    const GuiWlInterface* iPointer; const GuiWlInterface* iKeyboard; const GuiWlInterface* iOutput;
    const GuiWlInterface* iDataDeviceManager; const GuiWlInterface* iDataDevice;
    const GuiWlInterface* iDataSource; const GuiWlInterface* iDataOffer;

    int xkb;                           // libxkbcommon loaded
    XkbContextNew_ xkb_context_new;
    XkbKeymapFromString_ xkb_keymap_new_from_string;
    XkbKeymapUnref_ xkb_keymap_unref;
    XkbStateUnref_ xkb_state_unref;
    XkbStateNew_ xkb_state_new;
    XkbUpdateMask_ xkb_state_update_mask;
    XkbKeyU_ xkb_state_key_get_one_sym;
    XkbKeyU_ xkb_state_key_get_utf32;
    XkbKeyU_ xkb_state_key_get_layout;
    XkbModActive_ xkb_state_mod_name_is_active;
    XkbSymsByLevel_ xkb_keymap_key_get_syms_by_level;
    XkbKeyRepeats_ xkb_keymap_key_repeats;
    int xkbCompose;                    // the xkb_compose_* entries loaded
    XkbComposeTableNew_ xkb_compose_table_new_from_locale;
    XkbComposeStateNew_ xkb_compose_state_new;
    XkbComposeFeed_ xkb_compose_state_feed;
    XkbComposeStatus_ xkb_compose_state_get_status;
    XkbComposeKeysym_ xkb_compose_state_get_one_sym;
    XkbComposeReset_ xkb_compose_state_reset;

    int cursor;                        // libwayland-cursor loaded
    WlCursorThemeLoad_ cursor_theme_load;
    WlCursorThemeDestroy_ cursor_theme_destroy;
    WlCursorThemeGet_ cursor_theme_get_cursor;
    WlCursorImageBuffer_ cursor_image_get_buffer;
};

static struct GuiWlLib gWlLib;

static void __wl_missing() { }

// 's', or (clearing *ok) a stub never to be called: what failed to load
// is never used.
static fn void() __wl_found(WlSym_ s, &int ok) {
    match (s) {
        case some(p): { return p; }
        case null: {}
    }
    *ok = 0;
    return __wl_missing;
}

static fn void() __wl_sym(WlLib_* lib, const char* name, &int ok) {
    unsafe { return __wl_found(dlsym(lib, name), ok); }
}

// A data symbol: one of the library's struct wl_interface tables.
static const GuiWlInterface* __wl_iface_sym(WlLib_* lib, const char* name, &int ok) {
    unsafe {
        match (dlsym(lib, name)) {
            case some(p): { return (const GuiWlInterface*)p; }
            case null: {}
        }
        *ok = 0;
        return null;
    }
}

static WlLib_* __wl_open2(const char* a, const char* b) {
    unsafe {
        WlLib_* h = dlopen(a, 2 /* RTLD_NOW */);
        if (h == null && b != (const char*)0) { h = dlopen(b, 2); }
        return h;
    }
}

static int __wl_load() {
    if (gWlLib.loaded != 0) { return gWlLib.loaded > 0 ? 1 : 0; }
    gWlLib.loaded = -1;
    unsafe {
        WlLib_* lib = __wl_open2("libwayland-client.so.0", "libwayland-client.so");
        if (lib == null) { return 0; }
        int ok = 1;
        gWlLib.display_connect = (WlConnect_)__wl_sym(lib, "wl_display_connect", &ok);
        gWlLib.display_disconnect = (WlDisplayVoid_)__wl_sym(lib, "wl_display_disconnect", &ok);
        gWlLib.display_flush = (WlDisplayInt_)__wl_sym(lib, "wl_display_flush", &ok);
        gWlLib.display_get_fd = (WlDisplayInt_)__wl_sym(lib, "wl_display_get_fd", &ok);
        gWlLib.display_dispatch_pending = (WlDisplayInt_)__wl_sym(lib, "wl_display_dispatch_pending", &ok);
        gWlLib.display_roundtrip = (WlDisplayInt_)__wl_sym(lib, "wl_display_roundtrip", &ok);
        gWlLib.display_prepare_read = (WlDisplayInt_)__wl_sym(lib, "wl_display_prepare_read", &ok);
        gWlLib.display_read_events = (WlDisplayInt_)__wl_sym(lib, "wl_display_read_events", &ok);
        gWlLib.display_cancel_read = (WlDisplayVoid_)__wl_sym(lib, "wl_display_cancel_read", &ok);
        gWlLib.display_get_error = (WlDisplayInt_)__wl_sym(lib, "wl_display_get_error", &ok);
        gWlLib.marshal_ctor = (WlMarshalCtorV_)__wl_sym(lib, "wl_proxy_marshal_array_constructor_versioned", &ok);
        gWlLib.proxy_destroy = (WlProxyVoid_)__wl_sym(lib, "wl_proxy_destroy", &ok);
        gWlLib.add_dispatcher = (WlAddDispatcher_)__wl_sym(lib, "wl_proxy_add_dispatcher", &ok);
        gWlLib.get_version = (WlGetVersion_)__wl_sym(lib, "wl_proxy_get_version", &ok);
        gWlLib.iRegistry = __wl_iface_sym(lib, "wl_registry_interface", &ok);
        gWlLib.iCallback = __wl_iface_sym(lib, "wl_callback_interface", &ok);
        gWlLib.iCompositor = __wl_iface_sym(lib, "wl_compositor_interface", &ok);
        gWlLib.iSurface = __wl_iface_sym(lib, "wl_surface_interface", &ok);
        gWlLib.iRegion = __wl_iface_sym(lib, "wl_region_interface", &ok);
        gWlLib.iShm = __wl_iface_sym(lib, "wl_shm_interface", &ok);
        gWlLib.iShmPool = __wl_iface_sym(lib, "wl_shm_pool_interface", &ok);
        gWlLib.iBuffer = __wl_iface_sym(lib, "wl_buffer_interface", &ok);
        gWlLib.iSeat = __wl_iface_sym(lib, "wl_seat_interface", &ok);
        gWlLib.iPointer = __wl_iface_sym(lib, "wl_pointer_interface", &ok);
        gWlLib.iKeyboard = __wl_iface_sym(lib, "wl_keyboard_interface", &ok);
        gWlLib.iOutput = __wl_iface_sym(lib, "wl_output_interface", &ok);
        gWlLib.iDataDeviceManager = __wl_iface_sym(lib, "wl_data_device_manager_interface", &ok);
        gWlLib.iDataDevice = __wl_iface_sym(lib, "wl_data_device_interface", &ok);
        gWlLib.iDataSource = __wl_iface_sym(lib, "wl_data_source_interface", &ok);
        gWlLib.iDataOffer = __wl_iface_sym(lib, "wl_data_offer_interface", &ok);
        if (!ok) { return 0; }
        int mf = 1;
        gWlLib.marshal_flags = (WlMarshalFlags_)__wl_sym(lib, "wl_proxy_marshal_array_flags", &mf);
        gWlLib.hasMarshalFlags = mf;

        WlLib_* xkb = __wl_open2("libxkbcommon.so.0", "libxkbcommon.so");
        if (xkb != null) {
            int xo = 1;
            gWlLib.xkb_context_new = (XkbContextNew_)__wl_sym(xkb, "xkb_context_new", &xo);
            gWlLib.xkb_keymap_new_from_string = (XkbKeymapFromString_)__wl_sym(xkb, "xkb_keymap_new_from_string", &xo);
            gWlLib.xkb_keymap_unref = (XkbKeymapUnref_)__wl_sym(xkb, "xkb_keymap_unref", &xo);
            gWlLib.xkb_state_unref = (XkbStateUnref_)__wl_sym(xkb, "xkb_state_unref", &xo);
            gWlLib.xkb_state_new = (XkbStateNew_)__wl_sym(xkb, "xkb_state_new", &xo);
            gWlLib.xkb_state_update_mask = (XkbUpdateMask_)__wl_sym(xkb, "xkb_state_update_mask", &xo);
            gWlLib.xkb_state_key_get_one_sym = (XkbKeyU_)__wl_sym(xkb, "xkb_state_key_get_one_sym", &xo);
            gWlLib.xkb_state_key_get_utf32 = (XkbKeyU_)__wl_sym(xkb, "xkb_state_key_get_utf32", &xo);
            gWlLib.xkb_state_key_get_layout = (XkbKeyU_)__wl_sym(xkb, "xkb_state_key_get_layout", &xo);
            gWlLib.xkb_state_mod_name_is_active = (XkbModActive_)__wl_sym(xkb, "xkb_state_mod_name_is_active", &xo);
            gWlLib.xkb_keymap_key_get_syms_by_level = (XkbSymsByLevel_)__wl_sym(xkb, "xkb_keymap_key_get_syms_by_level", &xo);
            gWlLib.xkb_keymap_key_repeats = (XkbKeyRepeats_)__wl_sym(xkb, "xkb_keymap_key_repeats", &xo);
            gWlLib.xkb = xo;
            int co = 1;
            gWlLib.xkb_compose_table_new_from_locale = (XkbComposeTableNew_)__wl_sym(xkb, "xkb_compose_table_new_from_locale", &co);
            gWlLib.xkb_compose_state_new = (XkbComposeStateNew_)__wl_sym(xkb, "xkb_compose_state_new", &co);
            gWlLib.xkb_compose_state_feed = (XkbComposeFeed_)__wl_sym(xkb, "xkb_compose_state_feed", &co);
            gWlLib.xkb_compose_state_get_status = (XkbComposeStatus_)__wl_sym(xkb, "xkb_compose_state_get_status", &co);
            gWlLib.xkb_compose_state_get_one_sym = (XkbComposeKeysym_)__wl_sym(xkb, "xkb_compose_state_get_one_sym", &co);
            gWlLib.xkb_compose_state_reset = (XkbComposeReset_)__wl_sym(xkb, "xkb_compose_state_reset", &co);
            gWlLib.xkbCompose = co;
        }
        WlLib_* cur = __wl_open2("libwayland-cursor.so.0", "libwayland-cursor.so");
        if (cur != null) {
            int co2 = 1;
            gWlLib.cursor_theme_load = (WlCursorThemeLoad_)__wl_sym(cur, "wl_cursor_theme_load", &co2);
            gWlLib.cursor_theme_destroy = (WlCursorThemeDestroy_)__wl_sym(cur, "wl_cursor_theme_destroy", &co2);
            gWlLib.cursor_theme_get_cursor = (WlCursorThemeGet_)__wl_sym(cur, "wl_cursor_theme_get_cursor", &co2);
            gWlLib.cursor_image_get_buffer = (WlCursorImageBuffer_)__wl_sym(cur, "wl_cursor_image_get_buffer", &co2);
            gWlLib.cursor = co2;
        }
    }
    gWlLib.loaded = 1;
    return 1;
}

// ── protocol tables built at run time ───────────────────────────────────

#define GUI_WL_MAX_MSG 16

struct GuiWlProto {
    struct GuiWlInterface iface;
    struct GuiWlMessage methods[GUI_WL_MAX_MSG];
    struct GuiWlMessage events[GUI_WL_MAX_MSG];
};

// No argument needs its interface listed: none of these messages carries
// a server-created object, so null types only skip libwayland's
// object-type check (and its debug output's interface names).
static const GuiWlInterface* gWlNullTypes[8];

static struct GuiWlProto gWlXdgWmBase;
static struct GuiWlProto gWlXdgSurface;
static struct GuiWlProto gWlXdgToplevel;
static struct GuiWlProto gWlDecoManager;
static struct GuiWlProto gWlDeco;
static struct GuiWlProto gWlViewporter;
static struct GuiWlProto gWlViewport;
static struct GuiWlProto gWlFracManager;
static struct GuiWlProto gWlFrac;
static struct GuiWlProto gWlShapeManager;
static struct GuiWlProto gWlShapeDevice;

static void __wl_proto(&GuiWlProto p, const char* name, int version) {
    unsafe {
        safe_memset_obj(p, 0);
        p.iface.name = name;
        p.iface.version = version;
        p.iface.methods = &p.methods[0];
        p.iface.events = &p.events[0];
    }
}

static void __wl_req(&GuiWlProto p, const char* name, const char* sig) {
    unsafe {
        int i = p.iface.methodCount;
        p.methods[i].name = name;
        p.methods[i].signature = sig;
        p.methods[i].types = &gWlNullTypes[0];
        p.iface.methodCount = i + 1;
    }
}

static void __wl_evt(&GuiWlProto p, const char* name, const char* sig) {
    unsafe {
        int i = p.iface.eventCount;
        p.events[i].name = name;
        p.events[i].signature = sig;
        p.events[i].types = &gWlNullTypes[0];
        p.iface.eventCount = i + 1;
    }
}

static void __wl_build_protocols() {
    // xdg-shell (stable), the subset of version 6 used here
    __wl_proto(&gWlXdgWmBase, "xdg_wm_base", 6);
    __wl_req(&gWlXdgWmBase, "destroy", "");
    __wl_req(&gWlXdgWmBase, "create_positioner", "n");
    __wl_req(&gWlXdgWmBase, "get_xdg_surface", "no");
    __wl_req(&gWlXdgWmBase, "pong", "u");
    __wl_evt(&gWlXdgWmBase, "ping", "u");

    __wl_proto(&gWlXdgSurface, "xdg_surface", 6);
    __wl_req(&gWlXdgSurface, "destroy", "");
    __wl_req(&gWlXdgSurface, "get_toplevel", "n");
    __wl_req(&gWlXdgSurface, "get_popup", "n?oo");
    __wl_req(&gWlXdgSurface, "set_window_geometry", "iiii");
    __wl_req(&gWlXdgSurface, "ack_configure", "u");
    __wl_evt(&gWlXdgSurface, "configure", "u");

    __wl_proto(&gWlXdgToplevel, "xdg_toplevel", 6);
    __wl_req(&gWlXdgToplevel, "destroy", "");
    __wl_req(&gWlXdgToplevel, "set_parent", "?o");
    __wl_req(&gWlXdgToplevel, "set_title", "s");
    __wl_req(&gWlXdgToplevel, "set_app_id", "s");
    __wl_req(&gWlXdgToplevel, "show_window_menu", "ouii");
    __wl_req(&gWlXdgToplevel, "move", "ou");
    __wl_req(&gWlXdgToplevel, "resize", "ouu");
    __wl_req(&gWlXdgToplevel, "set_max_size", "ii");
    __wl_req(&gWlXdgToplevel, "set_min_size", "ii");
    __wl_req(&gWlXdgToplevel, "set_maximized", "");
    __wl_req(&gWlXdgToplevel, "unset_maximized", "");
    __wl_req(&gWlXdgToplevel, "set_fullscreen", "?o");
    __wl_req(&gWlXdgToplevel, "unset_fullscreen", "");
    __wl_req(&gWlXdgToplevel, "set_minimized", "");
    __wl_evt(&gWlXdgToplevel, "configure", "iia");
    __wl_evt(&gWlXdgToplevel, "close", "");
    __wl_evt(&gWlXdgToplevel, "configure_bounds", "4ii");
    __wl_evt(&gWlXdgToplevel, "wm_capabilities", "5a");

    // xdg-decoration-unstable-v1
    __wl_proto(&gWlDecoManager, "zxdg_decoration_manager_v1", 1);
    __wl_req(&gWlDecoManager, "destroy", "");
    __wl_req(&gWlDecoManager, "get_toplevel_decoration", "no");
    __wl_proto(&gWlDeco, "zxdg_toplevel_decoration_v1", 1);
    __wl_req(&gWlDeco, "destroy", "");
    __wl_req(&gWlDeco, "set_mode", "u");
    __wl_req(&gWlDeco, "unset_mode", "");
    __wl_evt(&gWlDeco, "configure", "u");

    // viewporter (stable)
    __wl_proto(&gWlViewporter, "wp_viewporter", 1);
    __wl_req(&gWlViewporter, "destroy", "");
    __wl_req(&gWlViewporter, "get_viewport", "no");
    __wl_proto(&gWlViewport, "wp_viewport", 1);
    __wl_req(&gWlViewport, "destroy", "");
    __wl_req(&gWlViewport, "set_source", "ffff");
    __wl_req(&gWlViewport, "set_destination", "ii");

    // fractional-scale-v1 (staging)
    __wl_proto(&gWlFracManager, "wp_fractional_scale_manager_v1", 1);
    __wl_req(&gWlFracManager, "destroy", "");
    __wl_req(&gWlFracManager, "get_fractional_scale", "no");
    __wl_proto(&gWlFrac, "wp_fractional_scale_v1", 1);
    __wl_req(&gWlFrac, "destroy", "");
    __wl_evt(&gWlFrac, "preferred_scale", "u");

    // cursor-shape-v1 (staging)
    __wl_proto(&gWlShapeManager, "wp_cursor_shape_manager_v1", 1);
    __wl_req(&gWlShapeManager, "destroy", "");
    __wl_req(&gWlShapeManager, "get_pointer", "no");
    __wl_req(&gWlShapeManager, "get_tablet_tool_v2", "no");
    __wl_proto(&gWlShapeDevice, "wp_cursor_shape_device_v1", 1);
    __wl_req(&gWlShapeDevice, "destroy", "");
    __wl_req(&gWlShapeDevice, "set_shape", "uu");
}

// ── marshalling ─────────────────────────────────────────────────────────

// union wl_argument, 8 bytes: an argument list is an array of these.
struct GuiWlArgs {
    unsigned long long a[8];
};

static struct GuiWlArgs __wl_args() {
    struct GuiWlArgs r;
    safe_memset_obj(&r, 0);
    return r;
}
static void __wl_set_i(&GuiWlArgs r, int i, int v) { unsafe { *(int*)&r.a[i] = v; } }
static void __wl_set_u(&GuiWlArgs r, int i, unsigned int v) { unsafe { *(unsigned int*)&r.a[i] = v; } }
static void __wl_set_o(&GuiWlArgs r, int i, const WlProxy_* p) { unsafe { *(const WlProxy_**)&r.a[i] = p; } }
static void __wl_set_s(&GuiWlArgs r, int i, const char* s) { unsafe { *(const char**)&r.a[i] = s; } }

static int __wl_arg_i(unsigned long long* args, int i) { unsafe { return *(int*)&args[i]; } }
static unsigned int __wl_arg_u(unsigned long long* args, int i) { unsafe { return *(unsigned int*)&args[i]; } }
static WlProxy_* __wl_arg_o(unsigned long long* args, int i) { unsafe { return *(WlProxy_**)&args[i]; } }
static const char* __wl_arg_s(unsigned long long* args, int i) { unsafe { return *(const char**)&args[i]; } }
static double __wl_arg_fixed(unsigned long long* args, int i) { return (double)__wl_arg_i(args, i) / 256.0; }

// Sends request 'opcode' on 'proxy'. 'iface' (non-null) makes the request
// create a new object of that interface — its 'n' argument — and returns
// it. 'destroy' destroys the proxy afterwards (destructor requests).
static WlProxy_* __wl_send(WlProxy_* proxy, unsigned int opcode, const GuiWlInterface* iface, unsigned int version, &GuiWlArgs args, int destroy) {
    unsafe {
        if (proxy == null) { return null; }
        unsigned int ver = version;
        if (ver == 0U) { ver = gWlLib.get_version(proxy); }
        if (gWlLib.hasMarshalFlags) {
            return gWlLib.marshal_flags(proxy, opcode, iface, ver, destroy ? 1U : 0U, &args.a[0]);
        }
        WlProxy_* r = gWlLib.marshal_ctor(proxy, opcode, &args.a[0], iface, ver);
        if (destroy) { gWlLib.proxy_destroy(proxy); }
        return r;
    }
}

static void __wl_req0(WlProxy_* proxy, unsigned int opcode) {
    struct GuiWlArgs a = __wl_args();
    __wl_send(proxy, opcode, null, 0U, &a, 0);
}

static void __wl_destroy_req(WlProxy_* proxy, unsigned int opcode) {
    unsafe {
        if (proxy == null) { return; }
        struct GuiWlArgs a = __wl_args();
        __wl_send(proxy, opcode, null, 0U, &a, 1);
    }
}

// A request whose first argument is the new object ('n...'), the rest
// filled in by the caller from index 1.
static WlProxy_* __wl_new(WlProxy_* proxy, unsigned int opcode, const GuiWlInterface* iface, &GuiWlArgs a) {
    return __wl_send(proxy, opcode, iface, 0U, a, 0);
}

static int __wl_streq(const char* a, const char* b) {
    unsafe {
        if (a == (const char*)0 || b == (const char*)0) { return 0; }
        unsigned long i = 0UL;
        while (a[i] != '\0' && a[i] == b[i]) { i = i + 1UL; }
        return a[i] == b[i] ? 1 : 0;
    }
}

static int __wl_iface_version(const GuiWlInterface* iface) {
    unsafe { return iface->version; }
}

// ── state ───────────────────────────────────────────────────────────────

#define GUI_WL_MAX_BUFFERS 4
#define GUI_WL_MAX_OUTPUTS 16

struct GuiWlBuffer {
    WlProxy_* buffer;        // wl_buffer
    unsigned char* data;     // mmap()ed pixels, BGRA (WL_SHM_FORMAT_ARGB8888)
    unsigned long size;
    int w; int h;
    int busy;                // attached and not yet released by the compositor
    int orphan;              // replaced (size changed): destroy on release
    int dx0; int dy0; int dx1; int dy1;   // stale region (empty when dx1 <= dx0)
};

struct GuiWlWindow {
    struct GuiWindowState* st;
    WlProxy_* surface;
    WlProxy_* xdgSurface;
    WlProxy_* toplevel;
    WlProxy_* decoration;
    WlProxy_* viewport;
    WlProxy_* fracScale;
    WlProxy_* frameCb;
    double frameAt;          // ms when frameCb was requested
    int configured;          // initial configure seen (the surface may show buffers)
    int created;             // window_create returned
    int cfgW; int cfgH;      // the last xdg_toplevel.configure size (0 = ours to pick)
    int intScale;            // wl_surface buffer scale, when there is no viewport
    int fracScale120;        // preferred fractional scale * 120 (0 = none yet)
    int prefBufScale;        // wl_surface.preferred_buffer_scale (0 = none yet)
    WlProxy_* outputs[GUI_WL_MAX_OUTPUTS];   // outputs the surface is on
    struct GuiWlBuffer* bufs[GUI_WL_MAX_BUFFERS];
    struct GuiWlBuffer* pending;  // filled, waiting for the frame callback to be committed
    int pdx0; int pdy0; int pdx1; int pdy1;  // damage of 'pending'
    int hasContent;          // a buffer is attached (the surface is mapped)
    int decorated;
};

struct GuiWlOutput {
    WlProxy_* proxy;
    unsigned int name;
    int scale;
};

struct GuiWlOffer {
    WlProxy_* proxy;
    int utf8;      // text/plain;charset=utf-8
    int plain;     // text/plain
    int x11utf8;   // UTF8_STRING
    int string;    // STRING / TEXT
};

#define GUI_WL_MAX_OFFERS 8

struct GuiWl {
    WlDisplay_* display;
    WlProxy_* registry;
    WlProxy_* compositor; int compositorVer;
    WlProxy_* shm;
    WlProxy_* wmBase;
    WlProxy_* seat; unsigned int seatName; int seatVer;
    WlProxy_* pointer; int pointerVer;
    WlProxy_* keyboard;
    WlProxy_* ddm;          // wl_data_device_manager
    WlProxy_* dataDevice;
    WlProxy_* decoManager;
    WlProxy_* viewporter;
    WlProxy_* fracManager;
    WlProxy_* shapeManager;
    WlProxy_* shapeDevice;
    struct GuiWlOutput outputs[GUI_WL_MAX_OUTPUTS];
    int outputCount;
    int wakeR; int wakeW;
    int dead;
    double envScale;    // $SAFEC_GUI_SCALE, 0 = none

    // input
    unsigned long long ptrFocus;   // surface key the pointer is in
    unsigned long long kbFocus;
    unsigned int ptrSerial;        // the pointer enter serial (for set_cursor)
    unsigned int lastSerial;       // latest input serial (for the clipboard)
    double ptrX; double ptrY;      // surface coordinates
    int buttons;
    int mods;
    double axisDx; double axisDy;  // accumulated within one wl_pointer.frame
    int axisDiscX; int axisDiscY;  // value120 units (discrete steps * 120)
    int axisHave; int axisHaveDisc;
    // keyboard
    XkbContext_* xkbCtx; XkbKeymap_* keymap; XkbState_* xkbState;
    XkbComposeState_* compose;     // or null
    int repeatRate; int repeatDelay;
    unsigned int repeatKey;        // evdev code + 8, 0 = none
    double repeatNext;
    struct GuiEvent repeatEv;
    // cursors
    WlCursorTheme_* cursorTheme; int cursorThemeSize;
    WlProxy_* cursorSurface;
    int cursorShown;               // GUI_CURSOR_* currently on the pointer
    // clipboard
    struct GuiWlOffer offers[GUI_WL_MAX_OFFERS];
    int selection;                 // index into offers, -1 = none
    WlProxy_* source;              // our wl_data_source while we own the selection
    struct String clip;
    char appId[64];
};

static struct GuiWl gWl;
static int gWlState = 0;     // 0 not tried, 1 connected, -1 unavailable
static struct GuiBackend gWlBackend;
static int gWlBackendInit = 0;

static double __wl_now_ms() { return (double)time_mono_ns() / 1000000.0; }

static struct GuiWlWindow* __wl_win(&GuiWindowState st) {
    unsafe { return (struct GuiWlWindow*)st.native; }
}

static struct GuiWindowState* __wl_find(WlProxy_* surface) {
    unsafe {
        if (surface == null) { return (struct GuiWindowState*)0; }
        ?&GuiWindowState o = gui_backend_find((unsigned long long)(unsigned long)surface);
        if (o.is_null()) { return (struct GuiWindowState*)0; }
        return (struct GuiWindowState*)o;
    }
}

// ── scale ───────────────────────────────────────────────────────────────

static int __wl_output_scale(WlProxy_* proxy) {
    unsafe {
        int i = 0;
        while (i < gWl.outputCount) {
            if (gWl.outputs[i].proxy == proxy) { return gWl.outputs[i].scale; }
            i = i + 1;
        }
    }
    return 1;
}

static double __wl_display_scale() {
    if (gWl.envScale > 0.0) { return gWl.envScale; }
    int s = 1;
    int i = 0;
    while (i < gWl.outputCount) {
        if (gWl.outputs[i].scale > s) { s = gWl.outputs[i].scale; }
        i = i + 1;
    }
    return (double)s;
}

// The scale a window's buffer should have now.
static double __wl_window_scale(struct GuiWlWindow* w) {
    unsafe {
        if (!w->st->hiDpi) { return 1.0; }
        if (gWl.envScale > 0.0) { return gWl.envScale; }
        if (w->fracScale120 > 0 && gWl.viewporter != null) { return (double)w->fracScale120 / 120.0; }
        if (w->prefBufScale > 0) { return (double)w->prefBufScale; }
        int s = 0;
        int i = 0;
        while (i < GUI_WL_MAX_OUTPUTS) {
            if (w->outputs[i] != null) {
                int os = __wl_output_scale(w->outputs[i]);
                if (os > s) { s = os; }
            }
            i = i + 1;
        }
        if (s == 0) { return __wl_display_scale(); }
        return (double)s;
    }
}

static int __wl_round(double v) { return (int)(v + 0.5); }

// Applies the surface-side scaling state for the window's current scale:
// with a viewport the buffer is drawn at the logical size (any scale);
// without one, an integer buffer scale.
static void __wl_apply_scaling(struct GuiWlWindow* w) {
    unsafe {
        struct GuiWindowState* st = w->st;
        if (w->viewport != null) {
            struct GuiWlArgs a = __wl_args();
            __wl_set_i(&a, 0, st->logicalW);
            __wl_set_i(&a, 1, st->logicalH);
            __wl_send(w->viewport, 2U /* set_destination */, null, 0U, &a, 0);
        } else if (gWl.compositorVer >= 3) {
            int s = (int)(st->scale + 0.5);
            if (s < 1) { s = 1; }
            w->intScale = s;
            // double-buffered: applies with the next commit, whoever makes
            // it (the shm path, or a GPU presenter's eglSwapBuffers)
            struct GuiWlArgs b = __wl_args();
            __wl_set_i(&b, 0, s);
            __wl_send(w->surface, 8U /* set_buffer_scale */, null, 0U, &b, 0);
        }
    }
}

// Re-evaluates the window's scale; on a change updates the buffer size and
// posts GUI_EVENT_SCALE.
static void __wl_rescale(struct GuiWlWindow* w) {
    unsafe {
        struct GuiWindowState* st = w->st;
        double s = __wl_window_scale(w);
        if (w->viewport == null) {
            // integer buffer scales only
            s = (double)(int)(s + 0.999);
            if (s < 1.0) { s = 1.0; }
        }
        if (s == st->scale) { return; }
        st->scale = s;
        st->pixelW = __wl_round((double)st->logicalW * s);
        st->pixelH = __wl_round((double)st->logicalH * s);
        __wl_apply_scaling(w);
        if (w->created) {
            struct GuiEvent ev = GuiEvent::new(GUI_EVENT_SCALE);
            ev.width = st->pixelW;
            ev.height = st->pixelH;
            gui_backend_post(st, ev);
        }
    }
}

static void __wl_set_logical(struct GuiWlWindow* w, int lw, int lh) {
    unsafe {
        struct GuiWindowState* st = w->st;
        if (st->minW > 0 && lw < st->minW) { lw = st->minW; }
        if (st->minH > 0 && lh < st->minH) { lh = st->minH; }
        if (st->maxW > 0 && lw > st->maxW) { lw = st->maxW; }
        if (st->maxH > 0 && lh > st->maxH) { lh = st->maxH; }
        if (lw < 1) { lw = 1; }
        if (lh < 1) { lh = 1; }
        if (lw == st->logicalW && lh == st->logicalH) { return; }
        st->logicalW = lw;
        st->logicalH = lh;
        st->pixelW = __wl_round((double)lw * st->scale);
        st->pixelH = __wl_round((double)lh * st->scale);
        __wl_apply_scaling(w);
        if (w->created) {
            struct GuiEvent ev = GuiEvent::new(GUI_EVENT_RESIZE);
            ev.width = st->pixelW;
            ev.height = st->pixelH;
            gui_backend_post(st, ev);
        }
    }
}

// ── shm buffers ─────────────────────────────────────────────────────────

typedef fn int(const char*, unsigned int) MemfdCreate_;

static int __wl_shm_fd(unsigned long size) {
    unsafe {
        int fd = -1;
        int hasMfd = 1;
        MemfdCreate_ mfd = (MemfdCreate_)__wl_sym((WlLib_*)0 /* RTLD_DEFAULT */, "memfd_create", &hasMfd);
        if (hasMfd) { fd = mfd("safec-gui", 1U /* MFD_CLOEXEC */); }
        if (fd < 0) {
            char path[256];
            const char* dir = getenv("XDG_RUNTIME_DIR");
            if (dir == (const char*)0) { dir = "/tmp"; }
            unsigned long n = 0UL;
            while (dir[n] != '\0' && n < 200UL) { path[n] = dir[n]; n = n + 1UL; }
            const char* tail = "/safec-gui-XXXXXX";
            unsigned long k = 0UL;
            while (tail[k] != '\0') { path[n + k] = tail[k]; k = k + 1UL; }
            path[n + k] = '\0';
            fd = mkstemp(&path[0]);
            if (fd >= 0) { unlink(&path[0]); }
        }
        if (fd < 0) { return -1; }
        if (ftruncate(fd, (long)size) != 0) { close(fd); return -1; }
        return fd;
    }
}

static void __wl_buffer_free(struct GuiWlBuffer* b) {
    unsafe {
        if (b == (struct GuiWlBuffer*)0) { return; }
        if (b->buffer != null) { __wl_destroy_req(b->buffer, 0U /* destroy */); }
        if (b->data != (unsigned char*)0) { munmap(b->data, b->size); }
        dealloc(b);
    }
}

static int __wl_buffer_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        struct GuiWlBuffer* b = (struct GuiWlBuffer*)data;
        if (opcode == 0U) {   // release
            b->busy = 0;
            if (b->orphan) { __wl_buffer_free(b); }
        }
    }
    return 0;
}

static struct GuiWlBuffer* __wl_buffer_new(int w, int h) {
    unsafe {
        unsigned long stride = (unsigned long)w * 4UL;
        unsigned long size = stride * (unsigned long)h;
        int fd = __wl_shm_fd(size);
        if (fd < 0) { return (struct GuiWlBuffer*)0; }
        unsigned char* data = mmap(null, size, 3 /* PROT_READ|PROT_WRITE */, 1 /* MAP_SHARED */, fd, 0L);
        if (data == null || (long)data == -1L) { close(fd); return (struct GuiWlBuffer*)0; }
        struct GuiWlArgs a = __wl_args();
        __wl_set_i(&a, 1, fd);
        __wl_set_i(&a, 2, (int)size);
        WlProxy_* pool = __wl_new(gWl.shm, 0U /* create_pool */, gWlLib.iShmPool, &a);
        close(fd);
        if (pool == null) { munmap(data, size); return (struct GuiWlBuffer*)0; }
        struct GuiWlArgs b = __wl_args();
        __wl_set_i(&b, 1, 0);
        __wl_set_i(&b, 2, w);
        __wl_set_i(&b, 3, h);
        __wl_set_i(&b, 4, (int)stride);
        __wl_set_u(&b, 5, 0U /* WL_SHM_FORMAT_ARGB8888 */);
        WlProxy_* buffer = __wl_new(pool, 0U /* create_buffer */, gWlLib.iBuffer, &b);
        __wl_destroy_req(pool, 1U /* destroy */);
        if (buffer == null) { munmap(data, size); return (struct GuiWlBuffer*)0; }
        struct GuiWlBuffer* r = (struct GuiWlBuffer*)alloc_zeroed_raw_(sizeof(struct GuiWlBuffer));
        r->buffer = buffer;
        r->data = data;
        r->size = size;
        r->w = w;
        r->h = h;
        r->dx0 = 0; r->dy0 = 0; r->dx1 = w; r->dy1 = h;   // all stale
        gWlLib.add_dispatcher(buffer, __wl_buffer_dispatch, (?&Erased)r, null);
        return r;
    }
}

// ── windows ─────────────────────────────────────────────────────────────

static void __wl_toplevel_limits(struct GuiWlWindow* w) {
    unsafe {
        struct GuiWindowState* st = w->st;
        if (w->toplevel == null) { return; }
        int minW = st->minW; int minH = st->minH; int maxW = st->maxW; int maxH = st->maxH;
        if (!st->resizable) {
            minW = st->logicalW; maxW = st->logicalW;
            minH = st->logicalH; maxH = st->logicalH;
        }
        struct GuiWlArgs a = __wl_args();
        __wl_set_i(&a, 0, maxW > 0 ? maxW : 0);
        __wl_set_i(&a, 1, maxH > 0 ? maxH : 0);
        __wl_send(w->toplevel, 7U /* set_max_size */, null, 0U, &a, 0);
        struct GuiWlArgs b = __wl_args();
        __wl_set_i(&b, 0, minW > 0 ? minW : 0);
        __wl_set_i(&b, 1, minH > 0 ? minH : 0);
        __wl_send(w->toplevel, 8U /* set_min_size */, null, 0U, &b, 0);
    }
}

static void __wl_commit(struct GuiWlWindow* w) {
    unsafe { __wl_req0(w->surface, 6U /* commit */); }
}

static void __wl_frame_request(struct GuiWlWindow* w);
static void __wl_commit_pending(struct GuiWlWindow* w);

static int __wl_callback_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        struct GuiWlWindow* w = (struct GuiWlWindow*)data;
        if (opcode == 0U && w->frameCb == target) {   // done
            gWlLib.proxy_destroy(target);
            w->frameCb = null;
            if (w->pending != (struct GuiWlBuffer*)0) { __wl_commit_pending(w); }
        }
    }
    return 0;
}

static void __wl_frame_request(struct GuiWlWindow* w) {
    unsafe {
        struct GuiWlArgs a = __wl_args();
        WlProxy_* cb = __wl_new(w->surface, 3U /* frame */, gWlLib.iCallback, &a);
        if (cb != null) {
            gWlLib.add_dispatcher(cb, __wl_callback_dispatch, (?&Erased)w, null);
            w->frameCb = cb;
            w->frameAt = __wl_now_ms();
        }
    }
}

// Attaches and commits the filled buffer 'pending' with its damage.
static void __wl_commit_pending(struct GuiWlWindow* w) {
    unsafe {
        struct GuiWlBuffer* b = w->pending;
        if (b == (struct GuiWlBuffer*)0 || !w->configured) { return; }
        w->pending = (struct GuiWlBuffer*)0;
        struct GuiWlArgs a = __wl_args();
        __wl_set_o(&a, 0, b->buffer);
        __wl_send(w->surface, 1U /* attach */, null, 0U, &a, 0);
        if (w->viewport == null && gWl.compositorVer >= 3) {
            struct GuiWlArgs s = __wl_args();
            __wl_set_i(&s, 0, w->intScale > 0 ? w->intScale : 1);
            __wl_send(w->surface, 8U /* set_buffer_scale */, null, 0U, &s, 0);
        }
        struct GuiWlArgs d = __wl_args();
        if (gWl.compositorVer >= 4) {
            __wl_set_i(&d, 0, w->pdx0);
            __wl_set_i(&d, 1, w->pdy0);
            __wl_set_i(&d, 2, w->pdx1 - w->pdx0);
            __wl_set_i(&d, 3, w->pdy1 - w->pdy0);
            __wl_send(w->surface, 9U /* damage_buffer */, null, 0U, &d, 0);
        } else {
            __wl_set_i(&d, 0, 0);
            __wl_set_i(&d, 1, 0);
            __wl_set_i(&d, 2, 0x7FFFFFFF);
            __wl_set_i(&d, 3, 0x7FFFFFFF);
            __wl_send(w->surface, 2U /* damage */, null, 0U, &d, 0);
        }
        b->busy = 1;
        w->hasContent = 1;
        __wl_frame_request(w);
        __wl_commit(w);
        gWlLib.display_flush(gWl.display);
    }
}

static void __wl_xdg_surface_configure(struct GuiWlWindow* w, unsigned int serial) {
    unsafe {
        struct GuiWlArgs a = __wl_args();
        __wl_set_u(&a, 0, serial);
        __wl_send(w->xdgSurface, 4U /* ack_configure */, null, 0U, &a, 0);
        int first = w->configured ? 0 : 1;
        w->configured = 1;
        int lw = w->st->logicalW;
        int lh = w->st->logicalH;
        if (w->cfgW > 0 && w->cfgH > 0) { __wl_set_logical(w, w->cfgW, w->cfgH); }
        // Same size (a state change: focus, maximize at the same size...):
        // commit at once so a compositor that waits for the acked state
        // (sway's transactions) isn't kept waiting for a frame that the
        // application has no reason to draw.
        if (!first && lw == w->st->logicalW && lh == w->st->logicalH) {
            __wl_commit(w);
            gWlLib.display_flush(gWl.display);
        }
        if (first && w->created) {
            // shown again (after hide) or first map of a window created hidden
            gui_backend_post(w->st, GuiEvent::new(GUI_EVENT_EXPOSE));
        }
    }
}

static int __wl_xdg_surface_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        struct GuiWlWindow* w = (struct GuiWlWindow*)data;
        if (opcode == 0U) { __wl_xdg_surface_configure(w, __wl_arg_u(args, 0)); }
    }
    return 0;
}

static int __wl_toplevel_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        struct GuiWlWindow* w = (struct GuiWlWindow*)data;
        if (opcode == 0U) {          // configure(width, height, states)
            w->cfgW = __wl_arg_i(args, 0);
            w->cfgH = __wl_arg_i(args, 1);
        } else if (opcode == 1U) {   // close
            if (w->created) { gui_backend_post(w->st, GuiEvent::new(GUI_EVENT_CLOSE)); }
        }
    }
    return 0;
}

static int __wl_frac_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        struct GuiWlWindow* w = (struct GuiWlWindow*)data;
        if (opcode == 0U) {          // preferred_scale (120ths)
            w->fracScale120 = (int)__wl_arg_u(args, 0);
            __wl_rescale(w);
        }
    }
    return 0;
}

static int __wl_surface_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        struct GuiWlWindow* w = (struct GuiWlWindow*)data;
        if (opcode == 0U || opcode == 1U) {   // enter / leave (output)
            WlProxy_* out = __wl_arg_o(args, 0);
            int i = 0;
            int slot = -1;
            while (i < GUI_WL_MAX_OUTPUTS) {
                if (w->outputs[i] == out) { slot = i; }
                i = i + 1;
            }
            if (opcode == 0U && slot < 0) {
                i = 0;
                while (i < GUI_WL_MAX_OUTPUTS) {
                    if (w->outputs[i] == null) { w->outputs[i] = out; break; }
                    i = i + 1;
                }
            }
            if (opcode == 1U && slot >= 0) { w->outputs[slot] = null; }
            __wl_rescale(w);
        } else if (opcode == 2U) {   // preferred_buffer_scale
            w->prefBufScale = __wl_arg_i(args, 0);
            __wl_rescale(w);
        }
    }
    return 0;
}

static int __wl_deco_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    return 0;
}

// The program's name for xdg_toplevel.set_app_id: $SAFEC_GUI_APP_ID, else
// /proc/self/comm.
static void __wl_init_app_id() {
    unsafe {
        gWl.appId[0] = '\0';
        const char* env = getenv("SAFEC_GUI_APP_ID");
        unsigned long n = 0UL;
        if (env != (const char*)0) {
            while (env[n] != '\0' && n < 63UL) { gWl.appId[n] = env[n]; n = n + 1UL; }
            gWl.appId[n] = '\0';
            return;
        }
        int fd = open("/proc/self/comm", 0 /* O_RDONLY */);
        if (fd >= 0) {
            long r = read(fd, (unsigned char*)&gWl.appId[0], 63UL);
            close(fd);
            if (r > 0L) {
                n = (unsigned long)r;
                while (n > 0UL && (gWl.appId[n - 1UL] == '\n' || gWl.appId[n - 1UL] == ' ')) { n = n - 1UL; }
                gWl.appId[n] = '\0';
            }
        }
        if (gWl.appId[0] == '\0') {
            const char* d = "safec";
            n = 0UL;
            while (d[n] != '\0') { gWl.appId[n] = d[n]; n = n + 1UL; }
            gWl.appId[n] = '\0';
        }
    }
}

static void __wl_set_title(&GuiWindowState st, const &char title) {
    struct GuiWlWindow* w = __wl_win(st);
    unsafe {
        if (w == (struct GuiWlWindow*)0 || w->toplevel == null) { return; }
        struct GuiWlArgs a = __wl_args();
        __wl_set_s(&a, 0, (const char*)title);
        __wl_send(w->toplevel, 2U /* set_title */, null, 0U, &a, 0);
        gWlLib.display_flush(gWl.display);
    }
}

// Waits (dispatching) until the window's initial configure arrives.
static void __wl_wait_configure(struct GuiWlWindow* w) {
    unsafe {
        int tries = 0;
        while (!w->configured && tries < 20 && !gWl.dead) {
            if (gWlLib.display_roundtrip(gWl.display) < 0) { gWl.dead = 1; break; }
            tries = tries + 1;
        }
    }
}

static void __wl_map(struct GuiWlWindow* w) {
    unsafe {
        // The toplevel's state (an unmapped surface starts over without it),
        // then the initial commit — no buffer; the compositor answers with
        // the first configure.
        struct GuiWindowState* st = w->st;
        struct GuiWlArgs t = __wl_args();
        __wl_set_s(&t, 0, (const char*)st->title.data);
        __wl_send(w->toplevel, 2U /* set_title */, null, 0U, &t, 0);
        struct GuiWlArgs id = __wl_args();
        __wl_set_s(&id, 0, (const char*)&gWl.appId[0]);
        __wl_send(w->toplevel, 3U /* set_app_id */, null, 0U, &id, 0);
        __wl_toplevel_limits(w);
        if (w->decoration != null) {
            struct GuiWlArgs m = __wl_args();
            __wl_set_u(&m, 0, w->decorated ? 2U /* server_side */ : 1U /* client_side */);
            __wl_send(w->decoration, 1U /* set_mode */, null, 0U, &m, 0);
        }
        w->configured = 0;
        __wl_commit(w);
        gWlLib.display_flush(gWl.display);
        __wl_wait_configure(w);
    }
}

static int __wl_create(&GuiWindowState st, const &GuiWindowOptions opts) {
    unsafe {
        if (gWl.compositor == null || gWl.wmBase == null || gWl.shm == null || gWl.dead) { return 0; }
        struct GuiWlWindow* w = (struct GuiWlWindow*)alloc_zeroed_raw_(sizeof(struct GuiWlWindow));
        w->st = (struct GuiWindowState*)st;
        struct GuiWlArgs a = __wl_args();
        w->surface = __wl_new(gWl.compositor, 0U /* create_surface */, gWlLib.iSurface, &a);
        if (w->surface == null) { dealloc(w); return 0; }
        gWlLib.add_dispatcher(w->surface, __wl_surface_dispatch, (?&Erased)w, (?&Erased)w);

        struct GuiWlArgs b = __wl_args();
        __wl_set_o(&b, 1, w->surface);
        w->xdgSurface = __wl_new(gWl.wmBase, 2U /* get_xdg_surface */, &gWlXdgSurface.iface, &b);
        gWlLib.add_dispatcher(w->xdgSurface, __wl_xdg_surface_dispatch, (?&Erased)w, null);
        struct GuiWlArgs c = __wl_args();
        w->toplevel = __wl_new(w->xdgSurface, 1U /* get_toplevel */, &gWlXdgToplevel.iface, &c);
        gWlLib.add_dispatcher(w->toplevel, __wl_toplevel_dispatch, (?&Erased)w, null);

        if (gWl.viewporter != null) {
            struct GuiWlArgs v = __wl_args();
            __wl_set_o(&v, 1, w->surface);
            w->viewport = __wl_new(gWl.viewporter, 1U /* get_viewport */, &gWlViewport.iface, &v);
            if (gWl.fracManager != null && opts.hiDpi) {
                struct GuiWlArgs f = __wl_args();
                __wl_set_o(&f, 1, w->surface);
                w->fracScale = __wl_new(gWl.fracManager, 1U /* get_fractional_scale */, &gWlFrac.iface, &f);
                gWlLib.add_dispatcher(w->fracScale, __wl_frac_dispatch, (?&Erased)w, null);
            }
        }
        if (gWl.decoManager != null) {
            struct GuiWlArgs d = __wl_args();
            __wl_set_o(&d, 1, w->toplevel);
            w->decoration = __wl_new(gWl.decoManager, 1U /* get_toplevel_decoration */, &gWlDeco.iface, &d);
            gWlLib.add_dispatcher(w->decoration, __wl_deco_dispatch, (?&Erased)w, null);
        }

        st.native = (?&Erased)w;
        st.key = (unsigned long long)(unsigned long)w->surface;
        st.scale = 1.0;
        st.pixelW = st.logicalW;
        st.pixelH = st.logicalH;
        w->intScale = 1;
        __wl_rescale(w);

        w->decorated = opts.decorated;
        __wl_apply_scaling(w);
        if (opts.visible) { __wl_map(w); }
        if (gWl.dead) {
            st.native = null;
            dealloc(w);
            return 0;
        }
        // st.x/st.y: Wayland never tells a client where its window is.
        w->created = 1;
        return 1;
    }
}

static void __wl_window_free_buffers(struct GuiWlWindow* w) {
    unsafe {
        int i = 0;
        while (i < GUI_WL_MAX_BUFFERS) {
            if (w->bufs[i] != (struct GuiWlBuffer*)0) {
                __wl_buffer_free(w->bufs[i]);
                w->bufs[i] = (struct GuiWlBuffer*)0;
            }
            i = i + 1;
        }
        w->pending = (struct GuiWlBuffer*)0;
    }
}

static void __wl_destroy(&GuiWindowState st) {
    struct GuiWlWindow* w = __wl_win(st);
    unsafe {
        if (w == (struct GuiWlWindow*)0) { return; }
        if (gWl.ptrFocus == st.key) { gWl.ptrFocus = 0ULL; }
        if (gWl.kbFocus == st.key) { gWl.kbFocus = 0ULL; gWl.repeatKey = 0U; }
        if (w->frameCb != null) { gWlLib.proxy_destroy(w->frameCb); }
        if (w->fracScale != null) { __wl_destroy_req(w->fracScale, 0U); }
        if (w->viewport != null) { __wl_destroy_req(w->viewport, 0U); }
        if (w->decoration != null) { __wl_destroy_req(w->decoration, 0U); }
        if (w->toplevel != null) { __wl_destroy_req(w->toplevel, 0U); }
        if (w->xdgSurface != null) { __wl_destroy_req(w->xdgSurface, 0U); }
        __wl_destroy_req(w->surface, 0U);
        __wl_window_free_buffers(w);
        gWlLib.display_flush(gWl.display);
        dealloc(w);
        st.native = null;
    }
}

static void __wl_add_damage(struct GuiWlBuffer* b, int x0, int y0, int x1, int y1) {
    unsafe {
        if (b->dx1 <= b->dx0 || b->dy1 <= b->dy0) {
            b->dx0 = x0; b->dy0 = y0; b->dx1 = x1; b->dy1 = y1;
            return;
        }
        if (x0 < b->dx0) { b->dx0 = x0; }
        if (y0 < b->dy0) { b->dy0 = y0; }
        if (x1 > b->dx1) { b->dx1 = x1; }
        if (y1 > b->dy1) { b->dy1 = y1; }
    }
}

// A buffer of bw x bh to draw the next frame into: the pending one if a
// frame is already waiting, else a free one (made if needed).
static struct GuiWlBuffer* __wl_next_buffer(struct GuiWlWindow* w, int bw, int bh) {
    unsafe {
        if (w->pending != (struct GuiWlBuffer*)0) {
            if (w->pending->w == bw && w->pending->h == bh) { return w->pending; }
            w->pending = (struct GuiWlBuffer*)0;
        }
        int i = 0;
        // drop free buffers of another size; orphan busy ones
        while (i < GUI_WL_MAX_BUFFERS) {
            struct GuiWlBuffer* b = w->bufs[i];
            if (b != (struct GuiWlBuffer*)0 && (b->w != bw || b->h != bh)) {
                if (b->busy) { b->orphan = 1; } else { __wl_buffer_free(b); }
                w->bufs[i] = (struct GuiWlBuffer*)0;
            }
            i = i + 1;
        }
        int attempt = 0;
        while (attempt < 50) {
            i = 0;
            while (i < GUI_WL_MAX_BUFFERS) {
                struct GuiWlBuffer* b = w->bufs[i];
                if (b != (struct GuiWlBuffer*)0 && !b->busy) { return b; }
                i = i + 1;
            }
            i = 0;
            while (i < GUI_WL_MAX_BUFFERS) {
                if (w->bufs[i] == (struct GuiWlBuffer*)0) {
                    w->bufs[i] = __wl_buffer_new(bw, bh);
                    return w->bufs[i];
                }
                i = i + 1;
            }
            // All held by the compositor: wait for a release.
            if (gWlLib.display_roundtrip(gWl.display) < 0) { gWl.dead = 1; break; }
            attempt = attempt + 1;
        }
        return (struct GuiWlBuffer*)0;
    }
}

static void __wl_present(&GuiWindowState st, const &unsigned char px, int bw, int bh,
                         int x, int y, int rw, int rh) {
    struct GuiWlWindow* w = __wl_win(st);
    unsafe {
        if (w == (struct GuiWlWindow*)0 || gWl.dead || !w->configured || !st.visible) { return; }
        // Without a viewport the buffer must be a multiple of the buffer
        // scale: skip a frame drawn for the old size around a rescale.
        if (w->viewport == null && w->intScale > 1 && ((bw % w->intScale) != 0 || (bh % w->intScale) != 0)) { return; }
        // every buffer is now stale where the frame changed
        int i = 0;
        while (i < GUI_WL_MAX_BUFFERS) {
            struct GuiWlBuffer* ob = w->bufs[i];
            if (ob != (struct GuiWlBuffer*)0 && ob->w == bw && ob->h == bh) { __wl_add_damage(ob, x, y, x + rw, y + rh); }
            i = i + 1;
        }
        struct GuiWlBuffer* b = __wl_next_buffer(w, bw, bh);
        if (b == (struct GuiWlBuffer*)0) { return; }
        __wl_add_damage(b, x, y, x + rw, y + rh);
        // Copy the stale region from the frame, RGBA -> BGRA.
        int yy = b->dy0;
        while (yy < b->dy1) {
            const unsigned char* src = (const unsigned char*)px + ((unsigned long)yy * (unsigned long)bw + (unsigned long)b->dx0) * 4UL;
            unsigned char* dst = b->data + ((unsigned long)yy * (unsigned long)bw + (unsigned long)b->dx0) * 4UL;
            int n = b->dx1 - b->dx0;
            int xx = 0;
            while (xx < n) {
                dst[0] = src[2];
                dst[1] = src[1];
                dst[2] = src[0];
                dst[3] = src[3];
                src = src + 4;
                dst = dst + 4;
                xx = xx + 1;
            }
            yy = yy + 1;
        }
        if (w->pending == b) {
            if (x < w->pdx0) { w->pdx0 = x; }
            if (y < w->pdy0) { w->pdy0 = y; }
            if (x + rw > w->pdx1) { w->pdx1 = x + rw; }
            if (y + rh > w->pdy1) { w->pdy1 = y + rh; }
        } else {
            w->pdx0 = b->dx0; w->pdy0 = b->dy0; w->pdx1 = b->dx1; w->pdy1 = b->dy1;
        }
        b->dx0 = 0; b->dy0 = 0; b->dx1 = 0; b->dy1 = 0;
        w->pending = b;
        // Throttle to the compositor's frames: commit now unless the last
        // frame is still on its way (then the frame callback commits it; a
        // compositor that stopped sending them — window hidden — gets it
        // after 250 ms anyway).
        if (w->frameCb == null || __wl_now_ms() - w->frameAt > 250.0) {
            if (w->frameCb != null) { gWlLib.proxy_destroy(w->frameCb); w->frameCb = null; }
            __wl_commit_pending(w);
        }
    }
}

static void __wl_set_size(&GuiWindowState st, int lw, int lh) {
    struct GuiWlWindow* w = __wl_win(st);
    unsafe {
        if (w == (struct GuiWlWindow*)0) { return; }
        __wl_set_logical(w, lw, lh);
        if (!st.resizable) { __wl_toplevel_limits(w); }
        gWlLib.display_flush(gWl.display);
    }
}

static void __wl_set_position(&GuiWindowState st, int x, int y) { }

static void __wl_update_limits(&GuiWindowState st) {
    struct GuiWlWindow* w = __wl_win(st);
    unsafe {
        if (w == (struct GuiWlWindow*)0) { return; }
        __wl_toplevel_limits(w);
        if (w->configured) { __wl_commit(w); }
        gWlLib.display_flush(gWl.display);
    }
}

static void __wl_set_visible(&GuiWindowState st, int v) {
    struct GuiWlWindow* w = __wl_win(st);
    unsafe {
        if (w == (struct GuiWlWindow*)0) { return; }
        if (v) {
            if (!w->configured) { __wl_map(w); }
        } else {
            // A null buffer unmaps the surface; showing it again starts
            // over with an initial commit and configure.
            struct GuiWlArgs a = __wl_args();
            __wl_set_o(&a, 0, null);
            __wl_send(w->surface, 1U /* attach */, null, 0U, &a, 0);
            __wl_commit(w);
            w->configured = 0;
            w->hasContent = 0;
            w->pending = (struct GuiWlBuffer*)0;
            // an unmapped surface gets no frame callbacks
            if (w->frameCb != null) { gWlLib.proxy_destroy(w->frameCb); w->frameCb = null; }
            int i = 0;
            while (i < GUI_WL_MAX_BUFFERS) {
                if (w->bufs[i] != (struct GuiWlBuffer*)0) {
                    w->bufs[i]->busy = 0;
                    w->bufs[i]->dx0 = 0; w->bufs[i]->dy0 = 0;
                    w->bufs[i]->dx1 = w->bufs[i]->w; w->bufs[i]->dy1 = w->bufs[i]->h;
                }
                i = i + 1;
            }
            gWlLib.display_flush(gWl.display);
        }
    }
}

// Wayland has no way for a client to raise or focus its own window
// without an activation token from another client (xdg-activation).
static void __wl_focus(&GuiWindowState st) { }

// ── cursors ─────────────────────────────────────────────────────────────

static unsigned int __wl_shape(int c) {
    if (c == GUI_CURSOR_IBEAM) { return 9U; }        // text
    if (c == GUI_CURSOR_HAND) { return 4U; }         // pointer
    if (c == GUI_CURSOR_CROSSHAIR) { return 8U; }
    if (c == GUI_CURSOR_RESIZE_EW) { return 26U; }
    if (c == GUI_CURSOR_RESIZE_NS) { return 27U; }
    if (c == GUI_CURSOR_RESIZE_NWSE) { return 29U; }
    if (c == GUI_CURSOR_RESIZE_NESW) { return 28U; }
    if (c == GUI_CURSOR_MOVE) { return 13U; }
    if (c == GUI_CURSOR_WAIT) { return 6U; }
    if (c == GUI_CURSOR_NOT_ALLOWED) { return 15U; }
    return 1U;                                       // default
}

static GuiWlCursor* __wl_theme_cursor(int c) {
    unsafe {
        const char* a = "left_ptr"; const char* b = "default";
        if (c == GUI_CURSOR_IBEAM) { a = "xterm"; b = "text"; }
        else if (c == GUI_CURSOR_HAND) { a = "hand2"; b = "pointer"; }
        else if (c == GUI_CURSOR_CROSSHAIR) { a = "crosshair"; b = "cross"; }
        else if (c == GUI_CURSOR_RESIZE_EW) { a = "sb_h_double_arrow"; b = "ew-resize"; }
        else if (c == GUI_CURSOR_RESIZE_NS) { a = "sb_v_double_arrow"; b = "ns-resize"; }
        else if (c == GUI_CURSOR_RESIZE_NWSE) { a = "bottom_right_corner"; b = "nwse-resize"; }
        else if (c == GUI_CURSOR_RESIZE_NESW) { a = "bottom_left_corner"; b = "nesw-resize"; }
        else if (c == GUI_CURSOR_MOVE) { a = "fleur"; b = "move"; }
        else if (c == GUI_CURSOR_WAIT) { a = "watch"; b = "wait"; }
        else if (c == GUI_CURSOR_NOT_ALLOWED) { a = "crossed_circle"; b = "not-allowed"; }
        GuiWlCursor* cur = gWlLib.cursor_theme_get_cursor(gWl.cursorTheme, a);
        if (cur == null) { cur = gWlLib.cursor_theme_get_cursor(gWl.cursorTheme, b); }
        if (cur == null) { cur = gWlLib.cursor_theme_get_cursor(gWl.cursorTheme, "left_ptr"); }
        return cur;
    }
}

// Shows cursor 'c' on the pointer (which is in a window of scale 'scale').
static void __wl_apply_cursor(int c, double scale) {
    unsafe {
        if (gWl.pointer == null || gWl.ptrFocus == 0ULL) { return; }
        gWl.cursorShown = c;
        if (c == GUI_CURSOR_HIDDEN) {
            struct GuiWlArgs h = __wl_args();
            __wl_set_u(&h, 0, gWl.ptrSerial);
            __wl_send(gWl.pointer, 0U /* set_cursor */, null, 0U, &h, 0);
            return;
        }
        if (gWl.shapeDevice != null) {
            struct GuiWlArgs s = __wl_args();
            __wl_set_u(&s, 0, gWl.ptrSerial);
            __wl_set_u(&s, 1, __wl_shape(c));
            __wl_send(gWl.shapeDevice, 1U /* set_shape */, null, 0U, &s, 0);
            return;
        }
        if (!gWlLib.cursor) { return; }   // the compositor keeps whatever it showed
        int bs = (int)(scale + 0.999);
        if (bs < 1) { bs = 1; }
        int size = 24;
        const char* env = getenv("XCURSOR_SIZE");
        if (env != (const char*)0) {
            int v = (int)atof(env);
            if (v >= 8 && v <= 256) { size = v; }
        }
        if (gWl.cursorTheme == null || gWl.cursorThemeSize != size * bs) {
            if (gWl.cursorTheme != null) { gWlLib.cursor_theme_destroy(gWl.cursorTheme); }
            gWl.cursorTheme = gWlLib.cursor_theme_load(getenv("XCURSOR_THEME"), size * bs, gWl.shm);
            gWl.cursorThemeSize = size * bs;
        }
        if (gWl.cursorTheme == null) { return; }
        struct GuiWlCursor* cur = __wl_theme_cursor(c);
        if (cur == (struct GuiWlCursor*)0 || cur->imageCount == 0U) { return; }
        struct GuiWlCursorImage* img = cur->images[0];
        WlProxy_* buffer = gWlLib.cursor_image_get_buffer(img);
        if (buffer == null) { return; }
        if (gWl.cursorSurface == null) {
            struct GuiWlArgs a = __wl_args();
            gWl.cursorSurface = __wl_new(gWl.compositor, 0U, gWlLib.iSurface, &a);
        }
        WlProxy_* cs = gWl.cursorSurface;
        struct GuiWlArgs at = __wl_args();
        __wl_set_o(&at, 0, buffer);
        __wl_send(cs, 1U /* attach */, null, 0U, &at, 0);
        if (gWl.compositorVer >= 3) {
            struct GuiWlArgs sc = __wl_args();
            __wl_set_i(&sc, 0, bs);
            __wl_send(cs, 8U /* set_buffer_scale */, null, 0U, &sc, 0);
        }
        struct GuiWlArgs dm = __wl_args();
        __wl_set_i(&dm, 2, 0x7FFFFFFF);
        __wl_set_i(&dm, 3, 0x7FFFFFFF);
        __wl_send(cs, 2U /* damage */, null, 0U, &dm, 0);
        __wl_req0(cs, 6U /* commit */);
        struct GuiWlArgs set = __wl_args();
        __wl_set_u(&set, 0, gWl.ptrSerial);
        __wl_set_o(&set, 1, cs);
        __wl_set_i(&set, 2, (int)img->hotspotX / bs);
        __wl_set_i(&set, 3, (int)img->hotspotY / bs);
        __wl_send(gWl.pointer, 0U /* set_cursor */, null, 0U, &set, 0);
    }
}

static void __wl_set_cursor(&GuiWindowState st, int c) {
    unsafe {
        if (gWl.ptrFocus == st.key) {
            __wl_apply_cursor(c, st.scale);
            gWlLib.display_flush(gWl.display);
        }
    }
}

// ── keys ────────────────────────────────────────────────────────────────

// X11/xkb keysyms to GuiKey (the same table as the X11 backend's).
static int __wl_keysym(unsigned int ks) {
    if (ks == 0xFF51U || ks == 0xFF96U) { return GUI_KEY_LEFT; }
    if (ks == 0xFF53U || ks == 0xFF98U) { return GUI_KEY_RIGHT; }
    if (ks == 0xFF52U || ks == 0xFF97U) { return GUI_KEY_UP; }
    if (ks == 0xFF54U || ks == 0xFF99U) { return GUI_KEY_DOWN; }
    if (ks == 0xFF0DU || ks == 0xFF8DU) { return GUI_KEY_ENTER; }
    if (ks == 0xFF1BU) { return GUI_KEY_ESCAPE; }
    if (ks == 0xFF09U || ks == 0xFE20U) { return GUI_KEY_TAB; }
    if (ks == 0xFF08U) { return GUI_KEY_BACKSPACE; }
    if (ks == 0x20U)   { return GUI_KEY_SPACE; }
    if (ks == 0xFFFFU || ks == 0xFF9FU) { return GUI_KEY_DELETE; }
    if (ks == 0xFF50U || ks == 0xFF95U) { return GUI_KEY_HOME; }
    if (ks == 0xFF57U || ks == 0xFF9CU) { return GUI_KEY_END; }
    if (ks == 0xFF55U || ks == 0xFF9AU) { return GUI_KEY_PAGE_UP; }
    if (ks == 0xFF56U || ks == 0xFF9BU) { return GUI_KEY_PAGE_DOWN; }
    if (ks == 0xFF63U || ks == 0xFF9EU) { return GUI_KEY_INSERT; }
    if (ks == 0xFF67U) { return GUI_KEY_MENU; }
    if (ks >= 0xFFBEU && ks <= 0xFFC9U) { return GUI_KEY_F1 + (int)(ks - 0xFFBEU); }
    if (ks == 0xFFE1U || ks == 0xFFE2U) { return GUI_KEY_SHIFT; }
    if (ks == 0xFFE3U || ks == 0xFFE4U) { return GUI_KEY_CONTROL; }
    if (ks == 0xFFE9U || ks == 0xFFEAU) { return GUI_KEY_ALT; }
    if (ks == 0xFFE7U || ks == 0xFFE8U || ks == 0xFFEBU || ks == 0xFFECU) { return GUI_KEY_META; }
    if (ks >= 0x61U && ks <= 0x7AU) { return (int)(ks - 0x20U); }
    if (ks >= 0x41U && ks <= 0x5AU) { return (int)ks; }
    if (ks >= 0x30U && ks <= 0x39U) { return (int)ks; }
    return GUI_KEY_UNKNOWN;
}

// Without libxkbcommon: Linux evdev key codes as a US keyboard, as
// (keysym unshifted, keysym shifted).
static unsigned int __wl_evdev_sym(unsigned int code, int shift) {
    const char* row1 = "1234567890-=";   const char* row1s = "!@#$%^&*()_+";
    const char* row2 = "qwertyuiop[]";   const char* row2s = "QWERTYUIOP{}";
    const char* row3 = "asdfghjkl;'`";   const char* row3s = "ASDFGHJKL:\"~";
    const char* row4 = "\\zxcvbnm,./";   const char* row4s = "|ZXCVBNM<>?";
    unsafe {
        if (code >= 2U && code <= 13U) { return (unsigned int)(unsigned char)(shift ? row1s : row1)[code - 2U]; }
        if (code >= 16U && code <= 27U) { return (unsigned int)(unsigned char)(shift ? row2s : row2)[code - 16U]; }
        if (code >= 30U && code <= 41U) { return (unsigned int)(unsigned char)(shift ? row3s : row3)[code - 30U]; }
        if (code >= 43U && code <= 53U) { return (unsigned int)(unsigned char)(shift ? row4s : row4)[code - 43U]; }
    }
    if (code == 1U) { return 0xFF1BU; }
    if (code == 14U) { return 0xFF08U; }
    if (code == 15U) { return 0xFF09U; }
    if (code == 28U || code == 96U) { return 0xFF0DU; }
    if (code == 29U) { return 0xFFE3U; }
    if (code == 97U) { return 0xFFE4U; }
    if (code == 42U) { return 0xFFE1U; }
    if (code == 54U) { return 0xFFE2U; }
    if (code == 56U) { return 0xFFE9U; }
    if (code == 100U) { return 0xFFEAU; }
    if (code == 57U) { return 0x20U; }
    if (code >= 59U && code <= 68U) { return 0xFFBEU + (code - 59U); }
    if (code == 87U) { return 0xFFC8U; }
    if (code == 88U) { return 0xFFC9U; }
    if (code == 102U) { return 0xFF50U; }
    if (code == 103U) { return 0xFF52U; }
    if (code == 104U) { return 0xFF55U; }
    if (code == 105U) { return 0xFF51U; }
    if (code == 106U) { return 0xFF53U; }
    if (code == 107U) { return 0xFF57U; }
    if (code == 108U) { return 0xFF54U; }
    if (code == 109U) { return 0xFF56U; }
    if (code == 110U) { return 0xFF63U; }
    if (code == 111U) { return 0xFFFFU; }
    if (code == 125U) { return 0xFFEBU; }
    if (code == 126U) { return 0xFFECU; }
    if (code == 127U) { return 0xFF67U; }
    return 0U;
}

static unsigned int __wl_keysym_char(unsigned int ks) {
    if (ks >= 0x20U && ks <= 0x7EU) { return ks; }
    if (ks >= 0xA0U && ks <= 0xFFU) { return ks; }
    if (ks >= 0x01000100U && ks <= 0x0110FFFFU) { return ks - 0x01000000U; }
    return 0U;
}

static void __wl_free_keymap() {
    unsafe {
        if (gWl.xkbState != null) { gWlLib.xkb_state_unref(gWl.xkbState); gWl.xkbState = null; }
        if (gWl.keymap != null) { gWlLib.xkb_keymap_unref(gWl.keymap); gWl.keymap = null; }
    }
}

static void __wl_keymap(unsigned int format, int fd, unsigned int size) {
    unsafe {
        if (format != 1U /* XKB_V1 */ || !gWlLib.xkb) { close(fd); return; }
        unsigned char* map = mmap(null, (unsigned long)size, 1 /* PROT_READ */, 2 /* MAP_PRIVATE */, fd, 0L);
        close(fd);
        if (map == null || (long)map == -1L) { return; }
        if (gWl.xkbCtx == null) {
            gWl.xkbCtx = gWlLib.xkb_context_new(0);
            if (gWl.xkbCtx != null && gWlLib.xkbCompose) {
                const char* loc = getenv("LC_ALL");
                if (loc == (const char*)0 || loc[0] == '\0') { loc = getenv("LC_CTYPE"); }
                if (loc == (const char*)0 || loc[0] == '\0') { loc = getenv("LANG"); }
                if (loc == (const char*)0 || loc[0] == '\0') { loc = "C"; }
                XkbComposeTable_* table = gWlLib.xkb_compose_table_new_from_locale(gWl.xkbCtx, loc, 0);
                if (table != null) { gWl.compose = gWlLib.xkb_compose_state_new(table, 0); }
            }
        }
        if (gWl.xkbCtx == null) { munmap(map, (unsigned long)size); return; }
        XkbKeymap_* km = gWlLib.xkb_keymap_new_from_string(gWl.xkbCtx, (const char*)map, 1, 0);
        munmap(map, (unsigned long)size);
        if (km == null) { return; }
        __wl_free_keymap();
        gWl.keymap = km;
        gWl.xkbState = gWlLib.xkb_state_new(km);
    }
}

static int __wl_mods_from_xkb() {
    unsafe {
        int m = 0;
        XkbState_* s = gWl.xkbState;
        if (gWlLib.xkb_state_mod_name_is_active(s, "Shift", 8) > 0) { m = m | GUI_MOD_SHIFT; }
        if (gWlLib.xkb_state_mod_name_is_active(s, "Control", 8) > 0) { m = m | GUI_MOD_CTRL; }
        if (gWlLib.xkb_state_mod_name_is_active(s, "Mod1", 8) > 0) { m = m | GUI_MOD_ALT; }
        if (gWlLib.xkb_state_mod_name_is_active(s, "Mod4", 8) > 0) { m = m | GUI_MOD_META; }
        return m;
    }
}

static void __wl_key(unsigned int serial, unsigned int key, unsigned int state) {
    unsafe {
        gWl.lastSerial = serial;
        struct GuiWindowState* st = __wl_find((WlProxy_*)(unsigned long)gWl.kbFocus);
        int down = state == 1U ? 1 : 0;   // 2 (repeated, wl_keyboard v10) never reaches v8 clients
        if (!down && gWl.repeatKey == key + 8U) { gWl.repeatKey = 0U; }
        if (st == (struct GuiWindowState*)0) { return; }
        unsigned int code = key + 8U;
        struct GuiEvent ev = GuiEvent::new(down ? GUI_EVENT_KEY_DOWN : GUI_EVENT_KEY_UP);
        ev.modifiers = gWl.mods;
        ev.x = gWl.ptrX * st->scale;
        ev.y = gWl.ptrY * st->scale;
        unsigned int cp = 0U;
        if (gWl.xkbState != null) {
            unsigned int layout = gWlLib.xkb_state_key_get_layout(gWl.xkbState, code);
            const unsigned int* syms = (const unsigned int*)0;
            int n = gWlLib.xkb_keymap_key_get_syms_by_level(gWl.keymap, code, layout, 0U, &syms);
            unsigned int sym = gWlLib.xkb_state_key_get_one_sym(gWl.xkbState, code);
            ev.keycode = n > 0 ? __wl_keysym(syms[0]) : GUI_KEY_UNKNOWN;
            if (ev.keycode == GUI_KEY_UNKNOWN) { ev.keycode = __wl_keysym(sym); }
            if (down) {
                int composed = 0;
                if (gWl.compose != null) {
                    gWlLib.xkb_compose_state_feed(gWl.compose, sym);
                    int cs = gWlLib.xkb_compose_state_get_status(gWl.compose);
                    if (cs == 1 /* COMPOSING */) { composed = 1; cp = 0U; }
                    else if (cs == 2 /* COMPOSED */) {
                        cp = __wl_keysym_char(gWlLib.xkb_compose_state_get_one_sym(gWl.compose));
                        gWlLib.xkb_compose_state_reset(gWl.compose);
                        composed = 1;
                    } else if (cs == 3 /* CANCELLED */) {
                        gWlLib.xkb_compose_state_reset(gWl.compose);
                        composed = 1;
                    }
                }
                if (!composed) { cp = gWlLib.xkb_state_key_get_utf32(gWl.xkbState, code); }
            }
        } else {
            unsigned int sym0 = __wl_evdev_sym(key, 0);
            ev.keycode = __wl_keysym(sym0);
            if (down) { cp = __wl_keysym_char(__wl_evdev_sym(key, (gWl.mods & GUI_MOD_SHIFT) != 0 ? 1 : 0)); }
        }
        if (cp < 32U || cp == 127U || (gWl.mods & (GUI_MOD_CTRL | GUI_MOD_META)) != 0) { cp = 0U; }
        ev.codepoint = cp;
        gui_backend_post(st, ev);
        if (down) {
            int repeats = 1;
            if (gWl.keymap != null) { repeats = gWlLib.xkb_keymap_key_repeats(gWl.keymap, code); }
            if (ev.keycode == GUI_KEY_SHIFT || ev.keycode == GUI_KEY_CONTROL || ev.keycode == GUI_KEY_ALT || ev.keycode == GUI_KEY_META) { repeats = 0; }
            if (repeats && gWl.repeatRate > 0) {
                gWl.repeatKey = code;
                gWl.repeatEv = ev;
                gWl.repeatNext = __wl_now_ms() + (double)gWl.repeatDelay;
            }
        }
    }
}

static void __wl_key_repeat() {
    unsafe {
        if (gWl.repeatKey == 0U || gWl.repeatRate <= 0) { return; }
        double now = __wl_now_ms();
        if (now < gWl.repeatNext) { return; }
        struct GuiWindowState* st = __wl_find((WlProxy_*)(unsigned long)gWl.kbFocus);
        if (st == (struct GuiWindowState*)0) { gWl.repeatKey = 0U; return; }
        int n = 0;
        while (now >= gWl.repeatNext && n < 8) {
            struct GuiEvent ev = gWl.repeatEv;
            ev.timeMs = 0.0;
            gui_backend_post(st, ev);
            gWl.repeatNext = gWl.repeatNext + 1000.0 / (double)gWl.repeatRate;
            n = n + 1;
        }
        if (now >= gWl.repeatNext) { gWl.repeatNext = now + 1000.0 / (double)gWl.repeatRate; }
    }
}

static int __wl_keyboard_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        if (opcode == 0U) {          // keymap(format, fd, size)
            __wl_keymap(__wl_arg_u(args, 0), __wl_arg_i(args, 1), __wl_arg_u(args, 2));
        } else if (opcode == 1U) {   // enter(serial, surface, keys)
            gWl.lastSerial = __wl_arg_u(args, 0);
            WlProxy_* surface = __wl_arg_o(args, 1);
            gWl.kbFocus = (unsigned long long)(unsigned long)surface;
            struct GuiWindowState* st = __wl_find(surface);
            if (st != (struct GuiWindowState*)0 && !st->focused) {
                st->focused = 1;
                gui_backend_post(st, GuiEvent::new(GUI_EVENT_FOCUS_IN));
            }
        } else if (opcode == 2U) {   // leave(serial, surface)
            WlProxy_* surface = __wl_arg_o(args, 1);
            struct GuiWindowState* st = __wl_find(surface);
            if (gWl.kbFocus == (unsigned long long)(unsigned long)surface) { gWl.kbFocus = 0ULL; }
            gWl.repeatKey = 0U;
            if (st != (struct GuiWindowState*)0 && st->focused) {
                st->focused = 0;
                gui_backend_post(st, GuiEvent::new(GUI_EVENT_FOCUS_OUT));
            }
        } else if (opcode == 3U) {   // key(serial, time, key, state)
            __wl_key(__wl_arg_u(args, 0), __wl_arg_u(args, 2), __wl_arg_u(args, 3));
        } else if (opcode == 4U) {   // modifiers(serial, depressed, latched, locked, group)
            unsigned int dep = __wl_arg_u(args, 1);
            unsigned int lat = __wl_arg_u(args, 2);
            unsigned int lck = __wl_arg_u(args, 3);
            unsigned int grp = __wl_arg_u(args, 4);
            if (gWl.xkbState != null) {
                gWlLib.xkb_state_update_mask(gWl.xkbState, dep, lat, lck, 0U, 0U, grp);
                gWl.mods = __wl_mods_from_xkb();
            } else {
                unsigned int m = dep | lat;
                int mods = 0;
                if ((m & 1U) != 0U) { mods = mods | GUI_MOD_SHIFT; }
                if ((m & 4U) != 0U) { mods = mods | GUI_MOD_CTRL; }
                if ((m & 8U) != 0U) { mods = mods | GUI_MOD_ALT; }
                if ((m & 64U) != 0U) { mods = mods | GUI_MOD_META; }
                gWl.mods = mods;
            }
            if (gWl.repeatKey != 0U) { gWl.repeatEv.modifiers = gWl.mods; }
        } else if (opcode == 5U) {   // repeat_info(rate, delay)
            gWl.repeatRate = __wl_arg_i(args, 0);
            gWl.repeatDelay = __wl_arg_i(args, 1);
        }
    }
    return 0;
}

// ── pointer ─────────────────────────────────────────────────────────────

static void __wl_flush_axis() {
    unsafe {
        if (!gWl.axisHave) { return; }
        struct GuiWindowState* st = __wl_find((WlProxy_*)(unsigned long)gWl.ptrFocus);
        if (st != (struct GuiWindowState*)0) {
            struct GuiEvent ev = GuiEvent::new(GUI_EVENT_SCROLL);
            ev.x = gWl.ptrX * st->scale;
            ev.y = gWl.ptrY * st->scale;
            ev.modifiers = gWl.mods;
            if (gWl.axisHaveDisc) {
                ev.scrollDx = (double)gWl.axisDiscX / 120.0;
                ev.scrollDy = -(double)gWl.axisDiscY / 120.0;
            } else {
                // smooth scrolling (touchpads): ~10 surface units per line
                ev.scrollDx = gWl.axisDx / 10.0;
                ev.scrollDy = -gWl.axisDy / 10.0;
            }
            if (ev.scrollDx != 0.0 || ev.scrollDy != 0.0) { gui_backend_post(st, ev); }
        }
        gWl.axisHave = 0; gWl.axisHaveDisc = 0;
        gWl.axisDx = 0.0; gWl.axisDy = 0.0;
        gWl.axisDiscX = 0; gWl.axisDiscY = 0;
    }
}

static int __wl_pointer_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        if (opcode == 0U) {          // enter(serial, surface, x, y)
            gWl.ptrSerial = __wl_arg_u(args, 0);
            WlProxy_* surface = __wl_arg_o(args, 1);
            gWl.ptrFocus = (unsigned long long)(unsigned long)surface;
            gWl.ptrX = __wl_arg_fixed(args, 2);
            gWl.ptrY = __wl_arg_fixed(args, 3);
            struct GuiWindowState* st = __wl_find(surface);
            if (st != (struct GuiWindowState*)0) {
                __wl_apply_cursor(st->cursor, st->scale);
                struct GuiEvent ev = GuiEvent::new(GUI_EVENT_MOUSE_ENTER);
                ev.x = gWl.ptrX * st->scale;
                ev.y = gWl.ptrY * st->scale;
                gui_backend_post(st, ev);
            }
        } else if (opcode == 1U) {   // leave(serial, surface)
            WlProxy_* surface = __wl_arg_o(args, 1);
            struct GuiWindowState* st = __wl_find(surface);
            if (gWl.ptrFocus == (unsigned long long)(unsigned long)surface) { gWl.ptrFocus = 0ULL; }
            if (st != (struct GuiWindowState*)0) {
                struct GuiEvent ev = GuiEvent::new(GUI_EVENT_MOUSE_LEAVE);
                ev.x = gWl.ptrX * st->scale;
                ev.y = gWl.ptrY * st->scale;
                gui_backend_post(st, ev);
            }
        } else if (opcode == 2U) {   // motion(time, x, y)
            gWl.ptrX = __wl_arg_fixed(args, 1);
            gWl.ptrY = __wl_arg_fixed(args, 2);
            struct GuiWindowState* st = __wl_find((WlProxy_*)(unsigned long)gWl.ptrFocus);
            if (st != (struct GuiWindowState*)0) {
                struct GuiEvent ev = GuiEvent::new(GUI_EVENT_MOUSE_MOVE);
                ev.x = gWl.ptrX * st->scale;
                ev.y = gWl.ptrY * st->scale;
                ev.modifiers = gWl.mods;
                gui_backend_post(st, ev);
            }
        } else if (opcode == 3U) {   // button(serial, time, button, state)
            gWl.lastSerial = __wl_arg_u(args, 0);
            unsigned int b = __wl_arg_u(args, 2);
            unsigned int state = __wl_arg_u(args, 3);
            struct GuiWindowState* st = __wl_find((WlProxy_*)(unsigned long)gWl.ptrFocus);
            if (st != (struct GuiWindowState*)0) {
                struct GuiEvent ev = GuiEvent::new(state == 1U ? GUI_EVENT_MOUSE_DOWN : GUI_EVENT_MOUSE_UP);
                ev.x = gWl.ptrX * st->scale;
                ev.y = gWl.ptrY * st->scale;
                ev.modifiers = gWl.mods;
                if (b == 0x110U) { ev.button = GUI_BUTTON_LEFT; }
                else if (b == 0x111U) { ev.button = GUI_BUTTON_RIGHT; }
                else { ev.button = GUI_BUTTON_MIDDLE; }
                gui_backend_post(st, ev);
            }
        } else if (opcode == 4U) {   // axis(time, axis, value)
            unsigned int axis = __wl_arg_u(args, 1);
            double v = __wl_arg_fixed(args, 2);
            if (axis == 0U) { gWl.axisDy = gWl.axisDy + v; } else { gWl.axisDx = gWl.axisDx + v; }
            gWl.axisHave = 1;
            if (gWl.pointerVer < 5) { __wl_flush_axis(); }
        } else if (opcode == 5U) {   // frame
            __wl_flush_axis();
        } else if (opcode == 8U || opcode == 9U) {   // axis_discrete(axis, steps) / axis_value120(axis, v120)
            unsigned int axis = __wl_arg_u(args, 0);
            int v = __wl_arg_i(args, 1);
            if (opcode == 8U) {
                if (gWl.pointerVer >= 8) { return 0; }   // value120 carries it
                v = v * 120;
            }
            if (axis == 0U) { gWl.axisDiscY = gWl.axisDiscY + v; } else { gWl.axisDiscX = gWl.axisDiscX + v; }
            gWl.axisHaveDisc = 1;
            gWl.axisHave = 1;
        }
    }
    return 0;
}

// ── clipboard ───────────────────────────────────────────────────────────

static int __wl_offer_index(WlProxy_* proxy) {
    unsafe {
        int i = 0;
        while (i < GUI_WL_MAX_OFFERS) {
            if (gWl.offers[i].proxy == proxy && proxy != null) { return i; }
            i = i + 1;
        }
    }
    return -1;
}

static void __wl_offer_drop(int i) {
    unsafe {
        if (i < 0 || gWl.offers[i].proxy == null) { return; }
        __wl_destroy_req(gWl.offers[i].proxy, 2U /* destroy */);
        safe_memset_obj(&gWl.offers[i], 0);
        if (gWl.selection == i) { gWl.selection = -1; }
    }
}

static int __wl_offer_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        int i = __wl_offer_index(target);
        if (i < 0) { return 0; }
        if (opcode == 0U) {   // offer(mime)
            const char* m = __wl_arg_s(args, 0);
            if (__wl_streq(m, "text/plain;charset=utf-8")) { gWl.offers[i].utf8 = 1; }
            else if (__wl_streq(m, "text/plain")) { gWl.offers[i].plain = 1; }
            else if (__wl_streq(m, "UTF8_STRING")) { gWl.offers[i].x11utf8 = 1; }
            else if (__wl_streq(m, "STRING") || __wl_streq(m, "TEXT")) { gWl.offers[i].string = 1; }
        }
    }
    return 0;
}

static int __wl_data_device_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        if (opcode == 0U) {          // data_offer(new id)
            WlProxy_* offer = __wl_arg_o(args, 0);
            if (offer == null) { return 0; }
            int slot = -1;
            int i = 0;
            while (i < GUI_WL_MAX_OFFERS) {
                if (gWl.offers[i].proxy == null) { slot = i; break; }
                i = i + 1;
            }
            if (slot < 0) {
                // full (a drag-and-drop peer offering many): recycle a non-selection slot
                slot = gWl.selection == 0 ? 1 : 0;
                __wl_offer_drop(slot);
            }
            safe_memset_obj(&gWl.offers[slot], 0);
            gWl.offers[slot].proxy = offer;
            gWlLib.add_dispatcher(offer, __wl_offer_dispatch, null, null);
        } else if (opcode == 5U) {   // selection(offer or null)
            WlProxy_* offer = __wl_arg_o(args, 0);
            int keep = __wl_offer_index(offer);
            int i = 0;
            while (i < GUI_WL_MAX_OFFERS) {
                if (i != keep) { __wl_offer_drop(i); }
                i = i + 1;
            }
            gWl.selection = keep;
        } else if (opcode == 2U || opcode == 4U) {   // drag leave / drop: offers for DnD, ignored
            int i = 0;
            while (i < GUI_WL_MAX_OFFERS) {
                if (i != gWl.selection) { __wl_offer_drop(i); }
                i = i + 1;
            }
        }
    }
    return 0;
}

static int __wl_source_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        if (opcode == 1U) {          // send(mime, fd)
            int fd = __wl_arg_i(args, 1);
            unsigned long off = 0UL;
            while (off < gWl.clip.len) {
                long n = write(fd, (const unsigned char*)gWl.clip.data + off, gWl.clip.len - off);
                if (n <= 0L) { break; }
                off = off + (unsigned long)n;
            }
            close(fd);
        } else if (opcode == 2U) {   // cancelled: someone else owns the clipboard now
            if (gWl.source == target) { gWl.source = null; }
            __wl_destroy_req(target, 1U /* destroy */);
        }
    }
    return 0;
}

static void __wl_ensure_data_device() {
    unsafe {
        if (gWl.dataDevice != null || gWl.ddm == null || gWl.seat == null) { return; }
        struct GuiWlArgs a = __wl_args();
        __wl_set_o(&a, 1, gWl.seat);
        gWl.dataDevice = __wl_new(gWl.ddm, 1U /* get_data_device */, gWlLib.iDataDevice, &a);
        if (gWl.dataDevice != null) {
            gWlLib.add_dispatcher(gWl.dataDevice, __wl_data_device_dispatch, null, null);
        }
    }
}

static int __wl_clip_set(const &char utf8, unsigned long n) {
    unsafe {
        if (gWl.dataDevice == null || gWl.dead) { return 0; }
        gWl.clip.free();
        gWl.clip = String::from_n_(utf8, n);
        struct GuiWlArgs a = __wl_args();
        WlProxy_* src = __wl_new(gWl.ddm, 0U /* create_data_source */, gWlLib.iDataSource, &a);
        if (src == null) { return 0; }
        gWlLib.add_dispatcher(src, __wl_source_dispatch, null, null);
        const char* mimes[5];
        mimes[0] = "text/plain;charset=utf-8"; mimes[1] = "text/plain"; mimes[2] = "UTF8_STRING";
        mimes[3] = "STRING"; mimes[4] = "TEXT";
        int i = 0;
        while (i < 5) {
            struct GuiWlArgs m = __wl_args();
            __wl_set_s(&m, 0, (const char*)mimes[i]);
            __wl_send(src, 0U /* offer */, null, 0U, &m, 0);
            i = i + 1;
        }
        WlProxy_* old = gWl.source;
        gWl.source = src;
        struct GuiWlArgs s = __wl_args();
        __wl_set_o(&s, 0, src);
        __wl_set_u(&s, 1, gWl.lastSerial);
        __wl_send(gWl.dataDevice, 1U /* set_selection */, null, 0U, &s, 0);
        if (old != null) { __wl_destroy_req(old, 1U); }
        // The compositor cancels the source at once if it refuses (no
        // focus, stale serial).
        if (gWlLib.display_roundtrip(gWl.display) < 0) { gWl.dead = 1; return 0; }
        return gWl.source == src ? 1 : 0;
    }
}

struct GuiWlPollFd {
    int fd;
    short events;
    short revents;
};

extern int poll(GuiWlPollFd* fds, unsigned long nfds, int timeout);

static int __wl_clip_get(&String out) {
    out.clear();
    unsafe {
        if (gWl.dead) { return 0; }
        gWlLib.display_roundtrip(gWl.display);
        if (gWl.selection < 0) { return 0; }
        // Is the selection ours? (Our own source shows up as an offer too.)
        if (gWl.source != null) {
            out.push_n_((const char*)gWl.clip.data, gWl.clip.len);
            return 1;
        }
        struct GuiWlOffer o = gWl.offers[gWl.selection];
        const char* mime = (const char*)0;
        if (o.utf8) { mime = "text/plain;charset=utf-8"; }
        else if (o.x11utf8) { mime = "UTF8_STRING"; }
        else if (o.plain) { mime = "text/plain"; }
        else if (o.string) { mime = "STRING"; }
        if (mime == (const char*)0) { return 0; }
        int fds[2];
        if (pipe(&fds[0]) != 0) { return 0; }
        struct GuiWlArgs a = __wl_args();
        __wl_set_s(&a, 0, (const char*)mime);
        __wl_set_i(&a, 1, fds[1]);
        __wl_send(o.proxy, 1U /* receive */, null, 0U, &a, 0);
        gWlLib.display_flush(gWl.display);
        close(fds[1]);
        double start = __wl_now_ms();
        char buf[4096];
        int ok = 0;
        while (1) {
            double left = 1000.0 - (__wl_now_ms() - start);
            if (left <= 0.0) { break; }
            struct GuiWlPollFd p;
            p.fd = fds[0]; p.events = (short)1; p.revents = (short)0;
            int r = poll(&p, 1UL, (int)left);
            if (r <= 0) { break; }
            long n = read(fds[0], (unsigned char*)&buf[0], 4096UL);
            if (n < 0L) { break; }
            if (n == 0L) { ok = 1; break; }
            out.push_n_((const char*)&buf[0], (unsigned long)n);
        }
        close(fds[0]);
        return ok;
    }
}

// ── registry, seat, outputs ─────────────────────────────────────────────

static int __wl_output_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        if (opcode == 3U) {   // scale(factor)
            int i = 0;
            while (i < gWl.outputCount) {
                if (gWl.outputs[i].proxy == target) { gWl.outputs[i].scale = __wl_arg_i(args, 0); }
                i = i + 1;
            }
        }
    }
    return 0;
}

static void __wl_seat_caps(unsigned int caps) {
    unsafe {
        int wantPointer = (caps & 1U) != 0U ? 1 : 0;
        int wantKeyboard = (caps & 2U) != 0U ? 1 : 0;
        if (wantPointer && gWl.pointer == null) {
            struct GuiWlArgs a = __wl_args();
            gWl.pointer = __wl_new(gWl.seat, 0U /* get_pointer */, gWlLib.iPointer, &a);
            gWl.pointerVer = gWl.seatVer;
            gWlLib.add_dispatcher(gWl.pointer, __wl_pointer_dispatch, null, null);
            if (gWl.shapeManager != null) {
                struct GuiWlArgs s = __wl_args();
                __wl_set_o(&s, 1, gWl.pointer);
                gWl.shapeDevice = __wl_new(gWl.shapeManager, 1U /* get_pointer */, &gWlShapeDevice.iface, &s);
            }
        } else if (!wantPointer && gWl.pointer != null) {
            if (gWl.shapeDevice != null) { __wl_destroy_req(gWl.shapeDevice, 0U); gWl.shapeDevice = null; }
            if (gWl.seatVer >= 3) { __wl_destroy_req(gWl.pointer, 1U /* release */); }
            else { gWlLib.proxy_destroy(gWl.pointer); }
            gWl.pointer = null;
            gWl.ptrFocus = 0ULL;
        }
        if (wantKeyboard && gWl.keyboard == null) {
            struct GuiWlArgs a = __wl_args();
            gWl.keyboard = __wl_new(gWl.seat, 1U /* get_keyboard */, gWlLib.iKeyboard, &a);
            gWl.repeatRate = 25;
            gWl.repeatDelay = 600;
            gWlLib.add_dispatcher(gWl.keyboard, __wl_keyboard_dispatch, null, null);
        } else if (!wantKeyboard && gWl.keyboard != null) {
            if (gWl.seatVer >= 3) { __wl_destroy_req(gWl.keyboard, 0U /* release */); }
            else { gWlLib.proxy_destroy(gWl.keyboard); }
            gWl.keyboard = null;
            gWl.repeatKey = 0U;
            // keyboard focus goes with the keyboard
            struct GuiWindowState* st = __wl_find((WlProxy_*)(unsigned long)gWl.kbFocus);
            gWl.kbFocus = 0ULL;
            if (st != (struct GuiWindowState*)0 && st->focused) {
                st->focused = 0;
                gui_backend_post(st, GuiEvent::new(GUI_EVENT_FOCUS_OUT));
            }
        }
    }
}

static int __wl_seat_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    if (opcode == 0U) { __wl_seat_caps(__wl_arg_u(args, 0)); }
    return 0;
}

static int __wl_wm_base_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    if (opcode == 0U) {   // ping(serial)
        struct GuiWlArgs a = __wl_args();
        __wl_set_u(&a, 0, __wl_arg_u(args, 0));
        __wl_send(target, 3U /* pong */, null, 0U, &a, 0);
    }
    return 0;
}

static unsigned int __wl_minu(unsigned int a, unsigned int b) { return a < b ? a : b; }

static WlProxy_* __wl_bind(unsigned int name, const GuiWlInterface* iface, unsigned int version) {
    unsafe {
        struct GuiWlArgs a = __wl_args();
        __wl_set_u(&a, 0, name);
        __wl_set_s(&a, 1, iface->name);
        __wl_set_u(&a, 2, version);
        return __wl_send(gWl.registry, 0U /* bind */, iface, version, &a, 0);
    }
}

static int __wl_registry_dispatch(?&Erased data, WlProxy_* target, unsigned int opcode, const GuiWlMessage* msg, unsigned long long* args) {
    unsafe {
        if (opcode == 0U) {          // global(name, interface, version)
            unsigned int name = __wl_arg_u(args, 0);
            const char* iface = __wl_arg_s(args, 1);
            unsigned int ver = __wl_arg_u(args, 2);
            if (__wl_streq(iface, "wl_compositor") && gWl.compositor == null) {
                unsigned int v = __wl_minu(__wl_minu(ver, 6U), (unsigned int)__wl_iface_version(gWlLib.iSurface));
                gWl.compositor = __wl_bind(name, gWlLib.iCompositor, v);
                gWl.compositorVer = (int)v;
            } else if (__wl_streq(iface, "wl_shm") && gWl.shm == null) {
                gWl.shm = __wl_bind(name, gWlLib.iShm, 1U);
            } else if (__wl_streq(iface, "xdg_wm_base") && gWl.wmBase == null) {
                gWl.wmBase = __wl_bind(name, &gWlXdgWmBase.iface, __wl_minu(ver, 6U));
                gWlLib.add_dispatcher(gWl.wmBase, __wl_wm_base_dispatch, null, null);
            } else if (__wl_streq(iface, "wl_seat") && gWl.seat == null) {
                unsigned int v = __wl_minu(__wl_minu(ver, 8U), (unsigned int)__wl_iface_version(gWlLib.iSeat));
                gWl.seat = __wl_bind(name, gWlLib.iSeat, v);
                gWl.seatName = name;
                gWl.seatVer = (int)v;
                gWlLib.add_dispatcher(gWl.seat, __wl_seat_dispatch, null, null);
            } else if (__wl_streq(iface, "wl_output") && gWl.outputCount < GUI_WL_MAX_OUTPUTS) {
                unsigned int v = __wl_minu(__wl_minu(ver, 2U), (unsigned int)__wl_iface_version(gWlLib.iOutput));
                WlProxy_* o = __wl_bind(name, gWlLib.iOutput, v);
                gWl.outputs[gWl.outputCount].proxy = o;
                gWl.outputs[gWl.outputCount].name = name;
                gWl.outputs[gWl.outputCount].scale = 1;
                gWl.outputCount = gWl.outputCount + 1;
                gWlLib.add_dispatcher(o, __wl_output_dispatch, null, null);
            } else if (__wl_streq(iface, "wl_data_device_manager") && gWl.ddm == null) {
                unsigned int v = __wl_minu(__wl_minu(ver, 3U), (unsigned int)__wl_iface_version(gWlLib.iDataDeviceManager));
                gWl.ddm = __wl_bind(name, gWlLib.iDataDeviceManager, v);
            } else if (__wl_streq(iface, "zxdg_decoration_manager_v1") && gWl.decoManager == null) {
                gWl.decoManager = __wl_bind(name, &gWlDecoManager.iface, 1U);
            } else if (__wl_streq(iface, "wp_viewporter") && gWl.viewporter == null) {
                gWl.viewporter = __wl_bind(name, &gWlViewporter.iface, 1U);
            } else if (__wl_streq(iface, "wp_fractional_scale_manager_v1") && gWl.fracManager == null) {
                gWl.fracManager = __wl_bind(name, &gWlFracManager.iface, 1U);
            } else if (__wl_streq(iface, "wp_cursor_shape_manager_v1") && gWl.shapeManager == null) {
                gWl.shapeManager = __wl_bind(name, &gWlShapeManager.iface, 1U);
            }
        } else if (opcode == 1U) {   // global_remove(name)
            unsigned int name = __wl_arg_u(args, 0);
            int i = 0;
            while (i < gWl.outputCount) {
                if (gWl.outputs[i].name == name) {
                    gWl.outputs[i] = gWl.outputs[gWl.outputCount - 1];
                    gWl.outputCount = gWl.outputCount - 1;
                    break;
                }
                i = i + 1;
            }
        }
    }
    return 0;
}

// ── connection ──────────────────────────────────────────────────────────

static int __wl_connect() {
    if (gWlState != 0) { return gWlState > 0 ? 1 : 0; }
    gWlState = -1;
    if (!__wl_load()) { return 0; }
    unsafe {
        safe_memset_obj(&gWl, 0);
        gWl.selection = -1;
        gWl.wakeR = -1;
        gWl.wakeW = -1;
        WlDisplay_* d = gWlLib.display_connect((const char*)0);
        if (d == null) { return 0; }
        gWl.display = d;
        __wl_build_protocols();
        struct GuiWlArgs a = __wl_args();
        gWl.registry = __wl_send((WlProxy_*)d, 1U /* get_registry */, gWlLib.iRegistry, 1U, &a, 0);
        gWlLib.add_dispatcher(gWl.registry, __wl_registry_dispatch, null, null);
        // globals, then the events binding them caused (seat caps, output scales)
        if (gWlLib.display_roundtrip(d) < 0 || gWlLib.display_roundtrip(d) < 0) {
            gWlLib.display_disconnect(d);
            gWl.display = null;
            return 0;
        }
        if (gWl.compositor == null || gWl.shm == null || gWl.wmBase == null) {
            gWlLib.display_disconnect(d);
            gWl.display = null;
            return 0;
        }
        __wl_ensure_data_device();
        const char* env = getenv("SAFEC_GUI_SCALE");
        if (env != (const char*)0) {
            double v = atof(env);
            if (v >= 0.5 && v <= 8.0) { gWl.envScale = v; }
        }
        gWl.clip = String::new();
        __wl_init_app_id();
        int fds[2];
        if (pipe(&fds[0]) == 0) {
            fcntl(fds[0], 4 /* F_SETFL */, 2048 /* O_NONBLOCK */);
            fcntl(fds[1], 4, 2048);
            gWl.wakeR = fds[0];
            gWl.wakeW = fds[1];
        }
        gWlLib.display_roundtrip(d);   // the data device's current selection
    }
    gWlState = 1;
    return 1;
}

static int __wl_init() { return __wl_connect(); }

// ── the event pump ──────────────────────────────────────────────────────

static void __wl_lost() {
    // The compositor went away (or killed the connection over a protocol
    // error): tell every window, once.
    if (gWl.dead == 2) { return; }
    gWl.dead = 2;
    unsigned long i = 0UL;
    while (i < gui_backend_window_count()) {
        ?&GuiWindowState o = gui_backend_window_at(i);
        match (o) { case some(st): { gui_backend_post(st, GuiEvent::new(GUI_EVENT_CLOSE)); } case null: { } }
        i = i + 1UL;
    }
}

static int __wl_dispatch_pending() {
    unsafe {
        int r = gWlLib.display_dispatch_pending(gWl.display);
        if (r < 0) { __wl_lost(); return 0; }
        return r;
    }
}

// One read from the socket, waiting up to 'timeoutMs' (also returns when
// wake() writes to the pipe). Returns 1 if woken.
static int __wl_read(int timeoutMs) {
    unsafe {
        WlDisplay_* d = gWl.display;
        while (gWlLib.display_prepare_read(d) != 0) {
            if (__wl_dispatch_pending() == 0 && gWl.dead) { return 0; }
        }
        if (gWlLib.display_flush(d) < 0) {
            // EAGAIN only means the socket buffer is full; anything else is fatal
        }
        struct GuiWlPollFd fds[2];
        fds[0].fd = gWlLib.display_get_fd(d);
        fds[0].events = (short)1;
        fds[0].revents = (short)0;
        fds[1].fd = gWl.wakeR;
        fds[1].events = (short)1;
        fds[1].revents = (short)0;
        unsigned long n = gWl.wakeR >= 0 ? 2UL : 1UL;
        int r = poll(&fds[0], n, timeoutMs);
        if (r > 0 && ((int)fds[0].revents & 1) != 0) {
            if (gWlLib.display_read_events(d) < 0) { __wl_lost(); }
        } else {
            gWlLib.display_cancel_read(d);
            if (r > 0 && ((int)fds[0].revents & (8 | 16)) != 0) { __wl_lost(); }   // POLLERR | POLLHUP
        }
        __wl_dispatch_pending();
        int woke = 0;
        if (gWl.wakeR >= 0 && ((int)fds[1].revents & 1) != 0) {
            char drain[64];
            while (read(gWl.wakeR, (unsigned char*)&drain[0], 64UL) > 0L) { woke = 1; }
        }
        return woke;
    }
}

static void __wl_flush_stale_frames() {
    unsafe {
        double now = __wl_now_ms();
        unsigned long i = 0UL;
        while (i < gui_backend_window_count()) {
            ?&GuiWindowState o = gui_backend_window_at(i);
            i = i + 1UL;
            if (o.is_null()) { continue; }
            struct GuiWindowState* st = (struct GuiWindowState*)o;
            struct GuiWlWindow* w = (struct GuiWlWindow*)st->native;
            if (w == (struct GuiWlWindow*)0 || w->pending == (struct GuiWlBuffer*)0) { continue; }
            if (w->frameCb != null && now - w->frameAt > 250.0) {
                gWlLib.proxy_destroy(w->frameCb);
                w->frameCb = null;
                __wl_commit_pending(w);
            }
        }
    }
}

static int __wl_pump(int timeoutMs) {
    unsafe {
        if (gWlState != 1) { return 0; }
        if (gWl.dead) { return gui_backend_has_events(); }
        __wl_dispatch_pending();
        __wl_read(0);
        __wl_key_repeat();
        __wl_flush_stale_frames();
        if (gui_backend_has_events() || timeoutMs == 0) { return gui_backend_has_events(); }
        double start = __wl_now_ms();
        while (!gui_backend_has_events() && !gWl.dead) {
            double now = __wl_now_ms();
            int left = -1;
            if (timeoutMs > 0) {
                left = timeoutMs - (int)(now - start);
                if (left <= 0) { break; }
            }
            if (gWl.repeatKey != 0U) {
                int r = (int)(gWl.repeatNext - now + 0.999);
                if (r < 0) { r = 0; }
                if (left < 0 || r < left) { left = r; }
            }
            int woke = __wl_read(left);
            __wl_key_repeat();
            __wl_flush_stale_frames();
            if (woke) { break; }
        }
        return gui_backend_has_events();
    }
}

static void __wl_wake() {
    unsafe {
        if (gWl.wakeW >= 0) {
            char b = (char)1;
            write(gWl.wakeW, (const unsigned char*)&b, 1UL);
        }
    }
}

static long long __wl_now_ns() { return time_mono_ns(); }

static struct GuiNativeHandle __wl_native(&GuiWindowState st) {
    struct GuiNativeHandle h;
    h.kind = GUI_NATIVE_WAYLAND;
    unsafe { h.display = (unsigned long long)(unsigned long)gWl.display; }
    h.window = st.key;     // wl_surface*
    h.view = 0ULL;
    h.visual = 0ULL;
    return h;
}

?&GuiBackend gui_wayland_backend() {
    if (!gWlBackendInit) {
        gWlBackend.name = "wayland";
        gWlBackend.primaryMod = GUI_MOD_CTRL;
        gWlBackend.init = __wl_init;
        gWlBackend.window_create = __wl_create;
        gWlBackend.window_destroy = __wl_destroy;
        gWlBackend.window_present = __wl_present;
        gWlBackend.window_set_title = __wl_set_title;
        gWlBackend.window_set_size = __wl_set_size;
        gWlBackend.window_set_position = __wl_set_position;
        gWlBackend.window_update_limits = __wl_update_limits;
        gWlBackend.window_set_visible = __wl_set_visible;
        gWlBackend.window_focus = __wl_focus;
        gWlBackend.window_set_cursor = __wl_set_cursor;
        gWlBackend.pump = __wl_pump;
        gWlBackend.wake = __wl_wake;
        gWlBackend.now_ns = __wl_now_ns;
        gWlBackend.clipboard_set = __wl_clip_set;
        gWlBackend.clipboard_get = __wl_clip_get;
        gWlBackend.display_scale = __wl_display_scale;
        gWlBackend.native_handle = __wl_native;
        gWlBackendInit = 1;
    }
    return &gWlBackend;
}

int gui_wayland_available() { return __wl_connect(); }

int gui_use_wayland() {
    if (!gui_wayland_available()) { return 0; }
    ?&GuiBackend b = gui_wayland_backend();
    match (b) {
        case some(be): return gui_set_backend(be);
        case null: { }
    }
    return 0;
}

#else   // no Wayland on this target

?&GuiBackend gui_wayland_backend() { return null; }
int gui_wayland_available() { return 0; }
int gui_use_wayland() { return 0; }

#endif

} // namespace std