+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol
+ * @section page_ifaces_xdg_decoration_unstable_v1 Interfaces
+ * - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager
+ * - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface
+ * @section page_copyright_xdg_decoration_unstable_v1 Copyright
+ *
+ *
+ * Copyright © 2018 Simon Ser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+struct xdg_toplevel;
+struct zxdg_decoration_manager_v1;
+struct zxdg_toplevel_decoration_v1;
+
+#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE
+#define ZXDG_DECORATION_MANAGER_V1_INTERFACE
+/**
+ * @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1
+ * @section page_iface_zxdg_decoration_manager_v1_desc Description
+ *
+ * This interface allows a compositor to announce support for server-side
+ * decorations.
+ *
+ * A window decoration is a set of window controls as deemed appropriate by
+ * the party managing them, such as user interface components used to move,
+ * resize and change a window's state.
+ *
+ * A client can use this protocol to request being decorated by a supporting
+ * compositor.
+ *
+ * If compositor and client do not negotiate the use of a server-side
+ * decoration using this protocol, clients continue to self-decorate as they
+ * see fit.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ * @section page_iface_zxdg_decoration_manager_v1_api API
+ * See @ref iface_zxdg_decoration_manager_v1.
+ */
+/**
+ * @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface
+ *
+ * This interface allows a compositor to announce support for server-side
+ * decorations.
+ *
+ * A window decoration is a set of window controls as deemed appropriate by
+ * the party managing them, such as user interface components used to move,
+ * resize and change a window's state.
+ *
+ * A client can use this protocol to request being decorated by a supporting
+ * compositor.
+ *
+ * If compositor and client do not negotiate the use of a server-side
+ * decoration using this protocol, clients continue to self-decorate as they
+ * see fit.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ */
+extern const struct wl_interface zxdg_decoration_manager_v1_interface;
+#endif
+#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
+#define ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
+/**
+ * @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1
+ * @section page_iface_zxdg_toplevel_decoration_v1_desc Description
+ *
+ * The decoration object allows the compositor to toggle server-side window
+ * decorations for a toplevel surface. The client can request to switch to
+ * another mode.
+ *
+ * The xdg_toplevel_decoration object must be destroyed before its
+ * xdg_toplevel.
+ * @section page_iface_zxdg_toplevel_decoration_v1_api API
+ * See @ref iface_zxdg_toplevel_decoration_v1.
+ */
+/**
+ * @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface
+ *
+ * The decoration object allows the compositor to toggle server-side window
+ * decorations for a toplevel surface. The client can request to switch to
+ * another mode.
+ *
+ * The xdg_toplevel_decoration object must be destroyed before its
+ * xdg_toplevel.
+ */
+extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
+#endif
+
+#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0
+#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1
+
+
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ */
+#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ */
+#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_decoration_manager_v1 */
+static inline void
+zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_decoration_manager_v1 */
+static inline void *
+zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1);
+}
+
+static inline uint32_t
+zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ *
+ * Destroy the decoration manager. This doesn't destroy objects created
+ * with the manager.
+ */
+static inline void
+zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1,
+ ZXDG_DECORATION_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), WL_MARSHAL_FLAG_DESTROY);
+}
+
+/**
+ * @ingroup iface_zxdg_decoration_manager_v1
+ *
+ * Create a new decoration object associated with the given toplevel.
+ *
+ * For objects of version 1, creating an xdg_toplevel_decoration from an
+ * xdg_toplevel which has a buffer attached or committed is a client
+ * error, and any attempts by a client to attach or manipulate a buffer
+ * prior to the first xdg_toplevel_decoration.configure event must also be
+ * treated as errors.
+ *
+ * For objects of version 2 or newer, creating an xdg_toplevel_decoration
+ * from an xdg_toplevel which has a buffer attached or committed is
+ * allowed. The initial decoration mode of the surface if a buffer is
+ * already attached depends on whether a xdg_toplevel_decoration object
+ * has been associated with the surface or not prior to this request.
+ *
+ * If an xdg_toplevel_decoration was associated with the surface, then
+ * destroyed without a surface commit, the previous decoration mode is
+ * retained.
+ *
+ * If no xdg_toplevel_decoration was associated with the surface prior to
+ * this request, or if a surface commit has been performed after a previous
+ * xdg_toplevel_decoration object associated with the surface was
+ * destroyed, the decoration mode is assumed to be client-side.
+ */
+static inline struct zxdg_toplevel_decoration_v1 *
+zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1,
+ ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), 0, NULL, toplevel);
+
+ return (struct zxdg_toplevel_decoration_v1 *) id;
+}
+
+#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
+#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
+enum zxdg_toplevel_decoration_v1_error {
+ /**
+ * xdg_toplevel has a buffer attached before configure
+ */
+ ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0,
+ /**
+ * xdg_toplevel already has a decoration object
+ */
+ ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1,
+ /**
+ * xdg_toplevel destroyed before the decoration object
+ */
+ ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2,
+ /**
+ * invalid mode
+ */
+ ZXDG_TOPLEVEL_DECORATION_V1_ERROR_INVALID_MODE = 3,
+};
+#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */
+
+#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
+#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ * window decoration modes
+ *
+ * These values describe window decoration modes.
+ */
+enum zxdg_toplevel_decoration_v1_mode {
+ /**
+ * no server-side window decoration
+ */
+ ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1,
+ /**
+ * server-side window decoration
+ */
+ ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2,
+};
+#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ * @struct zxdg_toplevel_decoration_v1_listener
+ */
+struct zxdg_toplevel_decoration_v1_listener {
+ /**
+ * notify a decoration mode change
+ *
+ * The configure event configures the effective decoration mode.
+ * The configured state should not be applied immediately. Clients
+ * must send an ack_configure in response to this event. See
+ * xdg_surface.configure and xdg_surface.ack_configure for details.
+ *
+ * A configure event can be sent at any time. The specified mode
+ * must be obeyed by the client.
+ * @param mode the decoration mode
+ */
+ void (*configure)(void *data,
+ struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
+ uint32_t mode);
+};
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+static inline int
+zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
+ const struct zxdg_toplevel_decoration_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0
+#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1
+#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ */
+#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_toplevel_decoration_v1 */
+static inline void
+zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_toplevel_decoration_v1 */
+static inline void *
+zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1);
+}
+
+static inline uint32_t
+zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ *
+ * Switch back to a mode without any server-side decorations at the next
+ * commit, unless a new xdg_toplevel_decoration is created for the surface
+ * first.
+ */
+static inline void
+zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+ ZXDG_TOPLEVEL_DECORATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), WL_MARSHAL_FLAG_DESTROY);
+}
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ *
+ * Set the toplevel surface decoration mode. This informs the compositor
+ * that the client prefers the provided decoration mode.
+ *
+ * After requesting a decoration mode, the compositor will respond by
+ * emitting an xdg_surface.configure event. The client should then update
+ * its content, drawing it without decorations if the received mode is
+ * server-side decorations. The client must also acknowledge the configure
+ * when committing the new content (see xdg_surface.ack_configure).
+ *
+ * The compositor can decide not to use the client's mode and enforce a
+ * different mode instead.
+ *
+ * Clients whose decoration mode depend on the xdg_toplevel state may send
+ * a set_mode request in response to an xdg_surface.configure event and wait
+ * for the next xdg_surface.configure event to prevent unwanted state.
+ * Such clients are responsible for preventing configure loops and must
+ * make sure not to send multiple successive set_mode requests with the
+ * same decoration mode.
+ *
+ * If an invalid mode is supplied by the client, the invalid_mode protocol
+ * error is raised by the compositor.
+ */
+static inline void
+zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+ ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0, mode);
+}
+
+/**
+ * @ingroup iface_zxdg_toplevel_decoration_v1
+ *
+ * Unset the toplevel surface decoration mode. This informs the compositor
+ * that the client doesn't prefer a particular decoration mode.
+ *
+ * This request has the same semantics as set_mode.
+ */
+static inline void
+zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1,
+ ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/Source/GNUmakefile.preamble b/Source/GNUmakefile.preamble
index b4b47aee..911449d9 100644
--- a/Source/GNUmakefile.preamble
+++ b/Source/GNUmakefile.preamble
@@ -45,7 +45,7 @@ CONFIG_SYSTEM_INCL += $(GRAPHIC_CFLAGS)
# Additional library directories the linker should search
#ADDITIONAL_LIB_DIRS +=
-CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS)
+CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS) -lGL -lEGL -lwayland-egl
#
# Flags for compiling as a bundle or library (if the system depends
diff --git a/Source/cairo/WaylandCairoShmSurface.m b/Source/cairo/WaylandCairoShmSurface.m
index 712816e2..93b20548 100644
--- a/Source/cairo/WaylandCairoShmSurface.m
+++ b/Source/cairo/WaylandCairoShmSurface.m
@@ -1,12 +1,21 @@
/* WaylandCairoSurface
- WaylandCairoShmSurface - A cairo surface backed by a wayland
- shared memory buffer.
- After the wayland surface is configured, the buffer needs to be
- attached to the surface. Subsequent changes to the cairo surface
- needs to be notified to the wayland server using wl_surface_damage
- and wl_surface_commit. The buffer is freed after the compositor
- releases it and the cairo surface is not in use.
+ WaylandCairoShmSurface - A cairo surface backed by a Wayland shared-memory
+ buffer.
+
+ Buffer lifecycle:
+ 1. createShmBuffer — allocate SHM fd, mmap, create wl_shm_pool + wl_buffer.
+ Destroy the pool immediately (safe; the buffer keeps the mapping alive).
+ 2. initWithDevice — attach the buffer to the wl_surface and commit with a
+ full-surface damage region. Only after xdg_surface_configure (i.e.
+ window->configured == YES).
+ 3. handleExposeRect — re-attach with the actual damage rect on every
+ AppKit expose. If the compositor still holds the buffer (busy == true),
+ record needs_repaint and return; the release callback will re-commit.
+ 4. buffer_handle_release — compositor released the buffer. If needs_repaint
+ is set, immediately re-attach + commit to avoid losing the missed frame.
+ 5. dealloc / finishBuffer — destroy the wl_buffer, unmap memory, close the
+ FD, and free the struct once the compositor has released it.
Copyright (C) 2020 Free Software Foundation, Inc.
@@ -45,73 +54,82 @@
#include
-static const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888;
-static const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32;
+static const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888;
+static const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32;
+
+/* ── Buffer lifetime ─────────────────────────────────────────────────────── */
+
+/* Free the pool_buffer once both conditions hold:
+ * (a) the compositor has released the wl_buffer (busy == false), and
+ * (b) nobody is rendering into the cairo surface (surface == NULL).
+ * Also closes the SHM file descriptor to prevent FD leaks. */
static void
finishBuffer(struct pool_buffer *buf)
{
- // The buffer can be deleted if it has been released by the compositor
- // and if not used by the cairo surface
- if(buf == NULL || buf->busy || buf->surface != NULL)
- {
+ if (buf == NULL || buf->busy || buf->surface != NULL)
return;
- }
+
if (buf->buffer)
{
wl_buffer_destroy(buf->buffer);
+ buf->buffer = NULL;
}
if (buf->data)
{
munmap(buf->data, buf->size);
+ buf->data = NULL;
+ }
+ if (buf->poolfd >= 0)
+ {
+ close(buf->poolfd);
+ buf->poolfd = -1;
}
free(buf);
- return;
}
+/* Compositor released the buffer. If a repaint was queued while the buffer
+ * was busy, re-attach and commit now so the frame is not permanently lost. */
static void
buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
{
struct pool_buffer *buffer = data;
buffer->busy = false;
- // If the buffer was not released before dealloc
+
+ if (buffer->needs_repaint && buffer->owner_surface)
+ {
+ buffer->needs_repaint = false;
+ buffer->busy = true;
+
+ wl_surface_attach(buffer->owner_surface, wl_buffer, 0, 0);
+ wl_surface_damage(buffer->owner_surface, 0, 0,
+ (int32_t)buffer->width, (int32_t)buffer->height);
+ wl_surface_commit(buffer->owner_surface);
+
+ if (buffer->owner_display)
+ wl_display_flush(buffer->owner_display);
+
+ /* Don't call finishBuffer — we just re-submitted the buffer. */
+ return;
+ }
+
finishBuffer(buffer);
}
static const struct wl_buffer_listener buffer_listener = {
- // Sent by the compositor when it's no longer using a buffer
.release = buffer_handle_release,
};
-// Creates a file descriptor for the compositor to share pixel buffers
+
+/* ── SHM buffer allocation ───────────────────────────────────────────────── */
+
+/* Creates an anonymous in-memory file suitable for sharing with the
+ * compositor via wl_shm. memfd_create is preferred over mkstemp because
+ * it never touches the filesystem and automatically cleans up on close. */
static int
createPoolFile(off_t size)
{
- static const char template[] = "/gnustep-shared-XXXXXX";
- const char *path;
- char *name;
- int fd;
-
- path = getenv("XDG_RUNTIME_DIR");
- if (!path)
- {
- errno = ENOENT;
- return -1;
- }
-
- name = malloc(strlen(path) + sizeof(template));
- if (!name)
- {
- return -1;
- }
-
- strcpy(name, path);
- strcat(name, template);
-
- fd = memfd_create(name, MFD_CLOEXEC);
-
- free(name);
-
+ int fd = memfd_create("gnustep-wl-shm", MFD_CLOEXEC);
if (fd < 0)
return -1;
@@ -128,104 +146,136 @@
createShmBuffer(int width, int height, struct wl_shm *shm)
{
uint32_t stride = cairo_format_stride_for_width(cairo_fmt, width);
- size_t size = stride * height;
+ size_t size = stride * height;
+
+ if (size == 0)
+ return NULL;
+
+ struct pool_buffer *buf = calloc(1, sizeof(struct pool_buffer));
+ if (!buf)
+ return NULL;
- struct pool_buffer * buf = malloc(sizeof(struct pool_buffer));
+ buf->poolfd = -1;
- void *data = NULL;
- if (size > 0)
+ buf->poolfd = createPoolFile(size);
+ if (buf->poolfd < 0)
{
- buf->poolfd = createPoolFile(size);
- if (buf->poolfd == -1)
- {
- return NULL;
- }
-
- data
- = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, buf->poolfd, 0);
- if (data == MAP_FAILED)
- {
- return NULL;
- }
-
- buf->pool = wl_shm_create_pool(shm, buf->poolfd, size);
- buf->buffer = wl_shm_pool_create_buffer(buf->pool, 0, width, height,
- stride, wl_fmt);
- wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
+ free(buf);
+ return NULL;
}
- else
- {
- return NULL;
- }
-
- buf->data = data;
- buf->size = size;
- buf->width = width;
- buf->height = height;
- buf->surface = cairo_image_surface_create_for_data(data, cairo_fmt, width, height, stride);
-
- if(buf->pool)
- {
- wl_shm_pool_destroy(buf->pool);
- }
+
+ buf->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, buf->poolfd, 0);
+ if (buf->data == MAP_FAILED)
+ {
+ close(buf->poolfd);
+ free(buf);
+ return NULL;
+ }
+
+ /* Create the pool and immediately the buffer. The pool can be destroyed
+ * right after — the buffer retains the memory mapping independently. */
+ struct wl_shm_pool *pool = wl_shm_create_pool(shm, buf->poolfd, size);
+ buf->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, wl_fmt);
+ wl_shm_pool_destroy(pool);
+ /* buf->pool is left NULL — pool is gone, nothing to free later. */
+
+ if (!buf->buffer)
+ {
+ munmap(buf->data, size);
+ close(buf->poolfd);
+ free(buf);
+ return NULL;
+ }
+
+ wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
+
+ buf->size = size;
+ buf->width = width;
+ buf->height = height;
+ buf->surface = cairo_image_surface_create_for_data(
+ buf->data, cairo_fmt, width, height, stride);
+
return buf;
}
+
+/* ── WaylandCairoShmSurface ──────────────────────────────────────────────── */
+
@implementation WaylandCairoShmSurface
-{
- struct pool_buffer *pbuffer;
-}
+
- (id)initWithDevice:(void *)device
{
- struct window *window = (struct window *) device;
- NSDebugLog(@"WaylandCairoShmSurface: initWithDevice win=%d",
- window->window_id);
+ struct window *window = (struct window *)device;
+ NSDebugLog(@"WaylandCairoShmSurface: initWithDevice win=%d", window->window_id);
gsDevice = device;
- pbuffer = createShmBuffer(window->width, window->height, window->wlconfig->shm);
-
+ pbuffer = createShmBuffer((int)window->width, (int)window->height,
+ window->wlconfig->shm);
if (pbuffer == NULL)
{
- NSDebugLog(@"failed to obtain buffer");
+ NSDebugLog(@"WaylandCairoShmSurface: failed to allocate SHM buffer");
return nil;
}
_surface = pbuffer->surface;
-
- window->buffer_needs_attach = YES;
if (_surface == NULL)
{
- NSDebugLog(@"can't create cairo surface");
+ NSDebugLog(@"WaylandCairoShmSurface: failed to create cairo surface");
+ finishBuffer(pbuffer);
+ pbuffer = NULL;
return nil;
}
+ /* Wire back-pointers so the release callback can re-commit missed frames. */
+ pbuffer->owner_surface = window->surface;
+ pbuffer->owner_display = window->wlconfig->display;
+
+ window->wcs = self;
+
if (window->configured)
{
- // we can attach a buffer to the surface only if the surface is configured
- // this is usually done in the configure event handler
- // in case of resize of an already configured surface
- // we should reattach the new allocated buffer
- NSDebugLog(@"wl_surface_attach: win=%d",
- window->window_id);
+ /* Attach the fresh buffer with a full-surface damage region.
+ * wl_surface_damage is mandatory before commit — without it the
+ * compositor treats the commit as a no-op for rendering purposes. */
window->buffer_needs_attach = NO;
+ pbuffer->busy = true;
wl_surface_attach(window->surface, pbuffer->buffer, 0, 0);
+ wl_surface_damage(window->surface, 0, 0,
+ (int32_t)window->width, (int32_t)window->height);
wl_surface_commit(window->surface);
}
- window->wcs = self;
+ else
+ {
+ window->buffer_needs_attach = YES;
+ }
return self;
}
- (void)dealloc
{
- struct window *window = (struct window *) gsDevice;
- NSDebugLog(@"WaylandCairoSurface: dealloc win=%d", window->window_id);
+ struct window *window = (struct window *)gsDevice;
+ NSDebugLog(@"WaylandCairoShmSurface: dealloc win=%d", window->window_id);
+
+ /* Detach this surface from the window so it won't be used again. */
+ if (window->wcs == self)
+ window->wcs = nil;
+
+ /* Sever the Cairo→buffer link; the buffer may still be compositor-held. */
cairo_surface_destroy(_surface);
- _surface = NULL;
+ _surface = NULL;
pbuffer->surface = NULL;
- // try to free the buffer if already released by the compositor
+
+ /* Clear back-pointers so the release callback doesn't touch freed data. */
+ pbuffer->owner_surface = NULL;
+ pbuffer->owner_display = NULL;
+ pbuffer->needs_repaint = false;
+
+ /* Free immediately if the compositor has already released the buffer;
+ * otherwise finishBuffer defers until the release callback fires. */
finishBuffer(pbuffer);
+ pbuffer = NULL;
[super dealloc];
}
@@ -233,38 +283,92 @@ - (void)dealloc
- (NSSize)size
{
if (_surface == NULL)
- {
- return NSZeroSize;
- }
+ return NSZeroSize;
return NSMakeSize(cairo_image_surface_get_width(_surface),
- cairo_image_surface_get_height(_surface));
+ cairo_image_surface_get_height(_surface));
}
- (void)handleExposeRect:(NSRect)rect
{
- struct window *window = (struct window *) gsDevice;
- NSDebugLog(@"[CairoSurface handleExposeRect] %d", window->window_id);
+ struct window *window = (struct window *)gsDevice;
- window->buffer_needs_attach = YES;
+ if (!window->configured)
+ {
+ window->buffer_needs_attach = YES;
+ return;
+ }
- if (window->configured)
+ /* If the buffer dimensions no longer match the window (e.g. after a
+ * compositor-driven resize), the old buffer is stale. Mark it for
+ * repaint-on-release so no frame is lost, then bail out — AppKit will
+ * allocate a correctly-sized WaylandCairoShmSurface via the next
+ * setWindowdevice:forContext: call. */
+ if (pbuffer->width != (uint32_t)window->width
+ || pbuffer->height != (uint32_t)window->height)
{
- window->buffer_needs_attach = NO;
- wl_surface_attach(window->surface, pbuffer->buffer, 0, 0);
- NSDebugLog(@"[%d] updating region: %d,%d %fx%f", window->window_id, 0, 0,
- window->width, window->height);
- // FIXME we should update only the damaged area defined as x,y,width,
- // height at the moment it doesnt work
- wl_surface_damage(window->surface, 0, 0, window->width, window->height);
- wl_surface_commit(window->surface);
- wl_display_dispatch_pending(window->wlconfig->display);
- wl_display_flush(window->wlconfig->display);
+ NSDebugLog(@"[%d] handleExposeRect: size mismatch buf=%dx%d win=%dx%d — "
+ @"deferring until resize completes",
+ window->window_id,
+ pbuffer->width, pbuffer->height,
+ (int)window->width, (int)window->height);
+ pbuffer->needs_repaint = true;
+ return;
}
+
+ /* If the compositor still owns the buffer, queue a repaint for the moment
+ * it returns it. Attaching a busy buffer causes a protocol error. */
+ if (pbuffer->busy)
+ {
+ NSDebugLog(@"[%d] handleExposeRect: buffer busy — queuing repaint",
+ window->window_id);
+ pbuffer->needs_repaint = true;
+ return;
+ }
+
+ /* Attach, mark the actual damaged region, and commit.
+ * Using the precise rect (converted to integer coordinates) reduces the
+ * compositor's repaint area for partial-surface updates. */
+ int dx = (int)NSMinX(rect);
+ int dy = (int)(window->height - NSMaxY(rect)); /* flip Y: AppKit → Wayland */
+ int dw = (int)NSWidth(rect) + 1; /* +1: cover sub-pixel edges */
+ int dh = (int)NSHeight(rect) + 1;
+
+ /* Clamp to buffer dimensions. */
+ if (dx < 0) dx = 0;
+ if (dy < 0) dy = 0;
+ if (dx + dw > (int)pbuffer->width) dw = (int)pbuffer->width - dx;
+ if (dy + dh > (int)pbuffer->height) dh = (int)pbuffer->height - dy;
+
+ NSDebugLog(@"[%d] handleExposeRect: attach+damage (%d,%d %dx%d)",
+ window->window_id, dx, dy, dw, dh);
+
+ pbuffer->needs_repaint = false;
+ pbuffer->busy = true;
+ window->buffer_needs_attach = NO;
+
+ wl_surface_attach(window->surface, pbuffer->buffer, 0, 0);
+ wl_surface_damage(window->surface, dx, dy, dw, dh);
+ wl_surface_commit(window->surface);
+ wl_display_dispatch_pending(window->wlconfig->display);
+ wl_display_flush(window->wlconfig->display);
}
- (void)destroySurface
{
- // noop this is an offscreen surface
- // no need to destroy it when not visible
+ /* Offscreen surface — no-op. Destruction is handled in dealloc. */
}
+
+- (void)clearOwnerSurface
+{
+ /* Called from destroySurfaceRole: just before wl_surface_destroy.
+ * Prevents the buffer_handle_release callback from writing to the
+ * about-to-be-freed proxy. */
+ if (pbuffer)
+ {
+ pbuffer->owner_surface = NULL;
+ pbuffer->owner_display = NULL;
+ pbuffer->needs_repaint = false;
+ }
+}
+
@end
diff --git a/Source/wayland/GNUmakefile b/Source/wayland/GNUmakefile
index a106b5e8..25951877 100644
--- a/Source/wayland/GNUmakefile
+++ b/Source/wayland/GNUmakefile
@@ -41,6 +41,8 @@ wayland_LOCALIZED_RESOURCE_FILES = \
wayland_C_FILES = \
xdg-shell-protocol.c \
wlr-layer-shell-protocol.c \
+xdg-decoration-unstable-v1-protocol.c \
+text-input-unstable-v3-protocol.c \
# The Objective-C source files to be compiled
wayland_OBJC_FILES = \
@@ -51,6 +53,10 @@ WaylandServer+Keyboard.m \
WaylandServer+Seat.m \
WaylandServer+Xdgshell.m \
WaylandServer+Layershell.m \
+WaylandGLContext.m \
+WaylandGLPixelFormat.m \
+WaylandInputServer.m \
+WaylandDragView.m \
-include GNUmakefile.preamble
diff --git a/Source/wayland/GNUmakefile.preamble b/Source/wayland/GNUmakefile.preamble
index 8cde639f..5d43eb77 100644
--- a/Source/wayland/GNUmakefile.preamble
+++ b/Source/wayland/GNUmakefile.preamble
@@ -42,7 +42,7 @@ ADDITIONAL_INCLUDE_DIRS += -I../../Headers \
-I../$(GNUSTEP_TARGET_DIR) $(GRAPHIC_CFLAGS)
# Additional LDFLAGS to pass to the linker
-ADDITIONAL_LDFLAGS =
+ADDITIONAL_LDFLAGS = -lGL -lEGL -lwayland-egl
# Additional library directories the linker should search
ADDITIONAL_LIB_DIRS =
diff --git a/Source/wayland/WaylandDragView.m b/Source/wayland/WaylandDragView.m
new file mode 100644
index 00000000..cc4df251
--- /dev/null
+++ b/Source/wayland/WaylandDragView.m
@@ -0,0 +1,1118 @@
+/* WaylandDragView - Drag and Drop for Wayland backend
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+
+ This file is part of the GNUstep Backend.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; see the file COPYING.LIB.
+ If not, see or write to the
+ Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "wayland/WaylandServer.h"
+#include "wayland/WaylandDragView.h"
+
+/* Private category to expose wlconfig from WaylandServer */
+@interface WaylandServer (DragViewAccess)
+- (WaylandConfig *) wlconfig;
+@end
+
+@implementation WaylandServer (DragViewAccess)
+- (WaylandConfig *) wlconfig
+{
+ return wlconfig;
+}
+@end
+
+
+/* ── MIME ↔ pasteboard type mapping ──────────────────────────────────────── */
+
+static const struct { const char *mime; const char *pboard; } kMimeMap[] = {
+ { "text/plain;charset=utf-8", "NSStringPboardType" },
+ { "text/plain", "NSStringPboardType" },
+ { "text/uri-list", "NSFilenamesPboardType" },
+ { "application/rtf", "NSRTFPboardType" },
+ { "image/tiff", "NSTIFFPboardType" },
+ { "image/png", "NSPNGPboardType" },
+ { NULL, NULL }
+};
+
+static const char *
+mime_for_pboard_type(NSString *pt)
+{
+ if ([pt isEqual: @"NSStringPboardType"] || [pt isEqual: NSPasteboardTypeString])
+ return "text/plain;charset=utf-8";
+ if ([pt isEqual: @"NSFilenamesPboardType"])
+ return "text/uri-list";
+ if ([pt isEqual: @"NSRTFPboardType"])
+ return "application/rtf";
+ if ([pt isEqual: @"NSTIFFPboardType"])
+ return "image/tiff";
+ if ([pt isEqual: @"NSPNGPboardType"])
+ return "image/png";
+ return NULL;
+}
+
+static NSString *
+pboard_type_for_mime(const char *mime)
+{
+ for (int i = 0; kMimeMap[i].mime; i++)
+ if (strcasecmp(mime, kMimeMap[i].mime) == 0)
+ return [NSString stringWithUTF8String: kMimeMap[i].pboard];
+ return nil;
+}
+
+
+/* ── Action mapping ──────────────────────────────────────────────────────── */
+
+static uint32_t
+ns_op_to_wl_actions(NSDragOperation op)
+{
+ uint32_t a = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+ if (op & NSDragOperationCopy) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ if (op & NSDragOperationMove) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+ if (op & NSDragOperationDelete) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+ if (op & NSDragOperationGeneric) a |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ return a ? a : WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+}
+
+static NSDragOperation
+wl_action_to_ns(uint32_t action)
+{
+ switch (action) {
+ case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: return NSDragOperationMove;
+ case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY:
+ default: return NSDragOperationCopy;
+ }
+}
+
+
+/* ── Inbound offer MIME helpers ───────────────────────────────────────────── */
+
+static void
+dnd_offer_mimes_free(WaylandConfig *wlconfig)
+{
+ if (wlconfig->dnd_offer_mimes)
+ {
+ for (int i = 0; i < wlconfig->dnd_offer_mime_count; i++)
+ free(wlconfig->dnd_offer_mimes[i]);
+ free(wlconfig->dnd_offer_mimes);
+ wlconfig->dnd_offer_mimes = NULL;
+ }
+ wlconfig->dnd_offer_mime_count = 0;
+ wlconfig->dnd_offer_mime_cap = 0;
+}
+
+static void
+dnd_offer_mime_append(WaylandConfig *wlconfig, const char *mime)
+{
+ if (wlconfig->dnd_offer_mime_count >= wlconfig->dnd_offer_mime_cap)
+ {
+ int cap = wlconfig->dnd_offer_mime_cap ? wlconfig->dnd_offer_mime_cap * 2 : 8;
+ wlconfig->dnd_offer_mimes = realloc(wlconfig->dnd_offer_mimes,
+ cap * sizeof(char *));
+ wlconfig->dnd_offer_mime_cap = cap;
+ }
+ wlconfig->dnd_offer_mimes[wlconfig->dnd_offer_mime_count++] = strdup(mime);
+}
+
+/* Return the first MIME type in the offer that we know how to receive.
+ Populates *pboardType if non-NULL. Returns NULL if nothing matches. */
+static const char *
+best_mime_for_offer(WaylandConfig *wlconfig, NSString **pboardType)
+{
+ static const char *preferred[] = {
+ "text/plain;charset=utf-8", "text/plain", "text/uri-list",
+ "application/rtf", "image/tiff", "image/png", NULL
+ };
+ for (int p = 0; preferred[p]; p++)
+ for (int i = 0; i < wlconfig->dnd_offer_mime_count; i++)
+ if (strcasecmp(wlconfig->dnd_offer_mimes[i], preferred[p]) == 0)
+ {
+ NSString *pt = pboard_type_for_mime(preferred[p]);
+ if (pt)
+ {
+ if (pboardType) *pboardType = pt;
+ return preferred[p];
+ }
+ }
+ return NULL;
+}
+
+
+/* ── Read all data from a pipe FD into NSData ─────────────────────────────── */
+
+static NSData *
+read_fd_to_data(int fd)
+{
+ size_t cap = 4096, total = 0;
+ char *buf = malloc(cap);
+ if (!buf) { close(fd); return nil; }
+
+ ssize_t n;
+ while ((n = read(fd, buf + total, cap - total)) > 0)
+ {
+ total += n;
+ if (total == cap)
+ {
+ cap *= 2;
+ char *nb = realloc(buf, cap);
+ if (!nb) { free(buf); close(fd); return nil; }
+ buf = nb;
+ }
+ }
+ close(fd);
+
+ NSData *d = [NSData dataWithBytes: buf length: total];
+ free(buf);
+ return d;
+}
+
+
+/* ── Post a fake NSLeftMouseUp to exit GSDragView's event loop ────────────── */
+
+static void
+post_fake_mouse_up(void)
+{
+ NSEvent *ev =
+ [NSEvent mouseEventWithType: NSLeftMouseUp
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: [NSDate timeIntervalSinceReferenceDate]
+ windowNumber: 0
+ context: nil
+ eventNumber: 0
+ clickCount: 1
+ pressure: 0.0
+ buttonNumber: 0
+ deltaX: 0
+ deltaY: 0
+ deltaZ: 0];
+ [NSApp postEvent: ev atStart: YES];
+}
+
+
+/* ── wl_data_source listener (outbound drag) ──────────────────────────────── */
+
+static void
+data_source_target(void *data, struct wl_data_source *source, const char *mime)
+{
+ NSDebugFLLog(@"WaylandDnD", @"data_source_target: %s", mime ? mime : "(none)");
+}
+
+static void
+data_source_send(void *data, struct wl_data_source *source,
+ const char *mime_type, int32_t fd)
+{
+ NSDebugFLLog(@"WaylandDnD", @"data_source_send: %s", mime_type);
+
+ WaylandDragView *dv = [WaylandDragView sharedDragView];
+ NSPasteboard *pb = [dv draggingPasteboard];
+
+ if (strcmp(mime_type, "text/plain;charset=utf-8") == 0
+ || strcmp(mime_type, "text/plain") == 0)
+ {
+ NSString *s = [pb stringForType: NSStringPboardType];
+ if (!s) s = [pb stringForType: NSPasteboardTypeString];
+ if (s)
+ {
+ const char *utf8 = [s UTF8String];
+ write(fd, utf8, strlen(utf8));
+ }
+ }
+ else if (strcmp(mime_type, "text/uri-list") == 0)
+ {
+ NSArray *names = [pb propertyListForType: @"NSFilenamesPboardType"];
+ if (names)
+ {
+ NSMutableString *list = [NSMutableString string];
+ for (NSString *path in names)
+ {
+ NSURL *url = [NSURL fileURLWithPath: path];
+ [list appendFormat: @"%@\r\n", [url absoluteString]];
+ }
+ const char *utf8 = [list UTF8String];
+ write(fd, utf8, strlen(utf8));
+ }
+ }
+ else
+ {
+ /* Generic binary fallback: look for a pasteboard type whose MIME matches */
+ NSString *pt = pboard_type_for_mime(mime_type);
+ if (pt)
+ {
+ NSData *d = [pb dataForType: pt];
+ if (d) write(fd, [d bytes], [d length]);
+ }
+ }
+
+ close(fd);
+}
+
+static void
+data_source_cancelled(void *data, struct wl_data_source *source)
+{
+ NSDebugFLLog(@"WaylandDnD", @"data_source_cancelled");
+ WaylandConfig *wlconfig = data;
+ wlconfig->dnd_source = NULL;
+ post_fake_mouse_up();
+}
+
+static void
+data_source_dnd_drop_performed(void *data, struct wl_data_source *source)
+{
+ NSDebugFLLog(@"WaylandDnD", @"data_source_dnd_drop_performed");
+ /* Wait for dnd_finished before exiting the drag loop. */
+}
+
+static void
+data_source_dnd_finished(void *data, struct wl_data_source *source)
+{
+ NSDebugFLLog(@"WaylandDnD", @"data_source_dnd_finished");
+ WaylandConfig *wlconfig = data;
+ wlconfig->dnd_source = NULL;
+ post_fake_mouse_up();
+}
+
+static void
+data_source_action(void *data, struct wl_data_source *source, uint32_t action)
+{
+ NSDebugFLLog(@"WaylandDnD", @"data_source_action: %u", action);
+ WaylandConfig *wlconfig = data;
+ wlconfig->dnd_current_action = action;
+}
+
+static const struct wl_data_source_listener data_source_listener = {
+ data_source_target,
+ data_source_send,
+ data_source_cancelled,
+ data_source_dnd_drop_performed,
+ data_source_dnd_finished,
+ data_source_action,
+};
+
+
+/* ── wl_data_offer listener (inbound MIME accumulation) ──────────────────── */
+
+static void
+data_offer_offer(void *data, struct wl_data_offer *offer, const char *mime_type)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandDnD", @"data_offer_offer: %s", mime_type);
+ dnd_offer_mime_append(wlconfig, mime_type);
+}
+
+static void
+data_offer_source_actions(void *data, struct wl_data_offer *offer,
+ uint32_t source_actions)
+{
+ WaylandConfig *wlconfig = data;
+ wlconfig->dnd_offer_source_actions = source_actions;
+ NSDebugFLLog(@"WaylandDnD", @"data_offer_source_actions: 0x%x", source_actions);
+}
+
+static void
+data_offer_action(void *data, struct wl_data_offer *offer, uint32_t dnd_action)
+{
+ WaylandConfig *wlconfig = data;
+ wlconfig->dnd_current_action = dnd_action;
+ NSDebugFLLog(@"WaylandDnD", @"data_offer_action: %u", dnd_action);
+}
+
+static const struct wl_data_offer_listener data_offer_listener = {
+ data_offer_offer,
+ data_offer_source_actions,
+ data_offer_action,
+};
+
+
+/* ── wl_data_device listener (inbound drag events) ───────────────────────── */
+
+static void
+device_data_offer(void *data, struct wl_data_device *device,
+ struct wl_data_offer *offer)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandDnD", @"device_data_offer: %p", (void *)offer);
+
+ /* Reset MIME list for this new offer; offer_offer callbacks follow. */
+ dnd_offer_mimes_free(wlconfig);
+ wlconfig->dnd_offer = offer;
+ wlconfig->dnd_offer_source_actions = 0;
+ wlconfig->dnd_current_action = 0;
+ wl_data_offer_add_listener(offer, &data_offer_listener, wlconfig);
+}
+
+static void
+device_enter(void *data, struct wl_data_device *device,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t x_fixed, wl_fixed_t y_fixed,
+ struct wl_data_offer *offer)
+{
+ WaylandConfig *wlconfig = data;
+ float x = wl_fixed_to_double(x_fixed);
+ float y = wl_fixed_to_double(y_fixed);
+
+ wlconfig->dnd_x = x;
+ wlconfig->dnd_y = y;
+ wlconfig->dnd_incoming = YES;
+ wlconfig->event_serial = serial;
+
+ struct window *window = surface ? surface_get_window(surface) : NULL;
+ wlconfig->dnd_target = window;
+
+ NSDebugFLLog(@"WaylandDnD", @"device_enter: win=%d pos=(%g,%g)",
+ window ? window->window_id : -1, x, y);
+
+ if (!offer || !window)
+ return;
+
+ /* Detect in-process drag: we are both the source (dnd_source != NULL) and
+ * the target. The two cases need different treatment:
+ *
+ * in-process — deliver events directly to the window via sendEvent: so
+ * they bypass GSDragView's running event loop. Do NOT call
+ * setupInboundDragWithPasteboard: — it would overwrite
+ * dragPasteboard and break data_source_send. The shared
+ * WaylandDragView already carries the correct source info.
+ *
+ * inter-process — post events to the app queue (GSDragView loop picks them
+ * up) and set up the drag view as the inbound NSDraggingInfo.
+ */
+ BOOL inProcess = (wlconfig->dnd_source != NULL);
+
+ if (!inProcess)
+ {
+ /* Find the best MIME type we can receive from an external source. */
+ NSString *pboardType = nil;
+ const char *mime = best_mime_for_offer(wlconfig, &pboardType);
+
+ if (mime)
+ {
+ wl_data_offer_accept(offer, serial, mime);
+
+ if (wlconfig->data_device_manager_version >= 3)
+ {
+ uint32_t myActions = ns_op_to_wl_actions(
+ NSDragOperationCopy | NSDragOperationMove);
+ wl_data_offer_set_actions(offer, myActions,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
+ }
+ }
+ else
+ {
+ wl_data_offer_accept(offer, serial, NULL);
+ }
+
+ /* Set up the shared drag view as NSDraggingInfo for AppKit. */
+ WaylandDragView *dv = [WaylandDragView sharedDragView];
+ [dv setupInboundDragWithPasteboard:
+ [NSPasteboard pasteboardWithName: NSDragPboard]
+ operation: wl_action_to_ns(
+ wlconfig->dnd_offer_source_actions
+ ? wlconfig->dnd_offer_source_actions
+ : WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)];
+ }
+
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+ if (!nswindow)
+ return;
+
+ NSPoint nsPos = NSMakePoint(x, window->height - y);
+ NSEvent *ev =
+ [NSEvent otherEventWithType: NSAppKitDefined
+ location: nsPos
+ modifierFlags: 0
+ timestamp: [[NSDate date] timeIntervalSinceReferenceDate]
+ windowNumber: window->window_id
+ context: nil
+ subtype: GSAppKitDraggingEnter
+ data1: 0
+ data2: 0];
+
+ /* Deliver directly for in-process; queue for inter-process. */
+ if (inProcess)
+ [nswindow sendEvent: ev];
+ else
+ [NSApp postEvent: ev atStart: NO];
+}
+
+static void
+device_leave(void *data, struct wl_data_device *device)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandDnD", @"device_leave");
+
+ struct window *window = wlconfig->dnd_target;
+ wlconfig->dnd_incoming = NO;
+ wlconfig->dnd_target = NULL;
+
+ if (window)
+ {
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+ if (nswindow)
+ {
+ BOOL inProcess = (wlconfig->dnd_source != NULL);
+ NSEvent *ev =
+ [NSEvent otherEventWithType: NSAppKitDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: [[NSDate date] timeIntervalSinceReferenceDate]
+ windowNumber: window->window_id
+ context: nil
+ subtype: GSAppKitDraggingExit
+ data1: 0
+ data2: 0];
+ if (inProcess)
+ [nswindow sendEvent: ev];
+ else
+ [NSApp postEvent: ev atStart: NO];
+ }
+ }
+
+ if (wlconfig->dnd_offer)
+ {
+ wl_data_offer_destroy(wlconfig->dnd_offer);
+ wlconfig->dnd_offer = NULL;
+ dnd_offer_mimes_free(wlconfig);
+ }
+}
+
+static void
+device_motion(void *data, struct wl_data_device *device,
+ uint32_t time, wl_fixed_t x_fixed, wl_fixed_t y_fixed)
+{
+ WaylandConfig *wlconfig = data;
+ float x = wl_fixed_to_double(x_fixed);
+ float y = wl_fixed_to_double(y_fixed);
+ wlconfig->dnd_x = x;
+ wlconfig->dnd_y = y;
+
+ struct window *window = wlconfig->dnd_target;
+ if (!window)
+ return;
+
+ NSDebugFLLog(@"WaylandDnD", @"device_motion: (%g,%g)", x, y);
+
+ BOOL inProcess = (wlconfig->dnd_source != NULL);
+
+ /* Keep accepting so the compositor knows we still want the drag
+ * (only meaningful for inter-process; in-process has no offer to negotiate). */
+ if (!inProcess && wlconfig->dnd_offer)
+ {
+ NSString *pt = nil;
+ const char *mime = best_mime_for_offer(wlconfig, &pt);
+ if (mime)
+ wl_data_offer_accept(wlconfig->dnd_offer, wlconfig->event_serial, mime);
+ }
+
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+ if (!nswindow)
+ return;
+
+ NSPoint nsPos = NSMakePoint(x, window->height - y);
+ NSEvent *ev =
+ [NSEvent otherEventWithType: NSAppKitDefined
+ location: nsPos
+ modifierFlags: 0
+ timestamp: (NSTimeInterval)time / 1000.0
+ windowNumber: window->window_id
+ context: nil
+ subtype: GSAppKitDraggingUpdate
+ data1: (NSInteger)NSDragOperationCopy
+ data2: 0];
+
+ if (inProcess)
+ [nswindow sendEvent: ev];
+ else
+ [NSApp postEvent: ev atStart: NO];
+}
+
+static void
+device_drop(void *data, struct wl_data_device *device)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandDnD", @"device_drop");
+
+ struct window *window = wlconfig->dnd_target;
+
+ if (!window || !wlconfig->dnd_offer)
+ {
+ if (wlconfig->dnd_offer)
+ {
+ wl_data_offer_destroy(wlconfig->dnd_offer);
+ wlconfig->dnd_offer = NULL;
+ dnd_offer_mimes_free(wlconfig);
+ }
+ wlconfig->dnd_incoming = NO;
+ return;
+ }
+
+ BOOL inProcess = (wlconfig->dnd_source != NULL);
+
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+
+ if (inProcess)
+ {
+ /* In-process drop: source and target are the same Wayland client.
+ *
+ * We cannot go through wl_data_offer_receive + pipe here: receiving
+ * triggers the compositor to call wl_data_source.send back to us, but
+ * we are blocked inside a dispatch callback and cannot re-enter the
+ * event loop to process that send event — a deadlock.
+ *
+ * Instead, copy the types and data from the source pasteboard (which
+ * WaylandDragView still holds from the dragImage: call) directly to
+ * NSDragPboard so the target view gets the correct data. */
+ WaylandDragView *dv = [WaylandDragView sharedDragView];
+ NSPasteboard *srcPboard = [dv draggingPasteboard];
+ NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
+
+ NSArray *types = [srcPboard types];
+ if ([types count] > 0)
+ {
+ [pboard declareTypes: types owner: nil];
+ for (NSString *type in types)
+ {
+ NSData *d = [srcPboard dataForType: type];
+ if (d)
+ [pboard setData: d forType: type];
+ }
+ }
+
+ if (nswindow)
+ {
+ NSDragOperation op = wl_action_to_ns(wlconfig->dnd_current_action);
+ if (op == NSDragOperationNone)
+ op = NSDragOperationCopy;
+ NSPoint nsPos =
+ NSMakePoint(wlconfig->dnd_x, window->height - wlconfig->dnd_y);
+ NSEvent *ev =
+ [NSEvent otherEventWithType: NSAppKitDefined
+ location: nsPos
+ modifierFlags: 0
+ timestamp: [[NSDate date] timeIntervalSinceReferenceDate]
+ windowNumber: window->window_id
+ context: nil
+ subtype: GSAppKitDraggingDrop
+ data1: (NSInteger)op
+ data2: 0];
+ [nswindow sendEvent: ev];
+ }
+ }
+ else
+ {
+ /* Inter-process drop: receive data through the pipe. */
+ NSString *pboardType = nil;
+ const char *mime = best_mime_for_offer(wlconfig, &pboardType);
+
+ if (!mime || !pboardType)
+ goto cleanup;
+
+ int pipefd[2];
+ if (pipe(pipefd) < 0)
+ goto cleanup;
+
+ wl_data_offer_receive(wlconfig->dnd_offer, mime, pipefd[1]);
+ close(pipefd[1]);
+ wl_display_flush(wlconfig->display);
+
+ NSData *rawData = read_fd_to_data(pipefd[0]);
+
+ NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
+ [pboard declareTypes: @[pboardType] owner: nil];
+
+ if ([pboardType isEqual: @"NSStringPboardType"]
+ || [pboardType isEqual: NSPasteboardTypeString])
+ {
+ NSString *s = [[NSString alloc]
+ initWithData: rawData encoding: NSUTF8StringEncoding];
+ if (!s)
+ s = [[NSString alloc]
+ initWithData: rawData encoding: NSISOLatin1StringEncoding];
+ if (s)
+ {
+ [pboard setString: s forType: pboardType];
+ [s release];
+ }
+ }
+ else if ([pboardType isEqual: @"NSFilenamesPboardType"])
+ {
+ NSString *raw = [[NSString alloc]
+ initWithData: rawData encoding: NSUTF8StringEncoding];
+ NSArray *lines = [raw componentsSeparatedByCharactersInSet:
+ [NSCharacterSet newlineCharacterSet]];
+ NSMutableArray *paths = [NSMutableArray array];
+ for (NSString *line in lines)
+ {
+ NSString *trimmed = [line stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceCharacterSet]];
+ if ([trimmed length] == 0 || [trimmed hasPrefix: @"#"])
+ continue;
+ NSURL *url = [NSURL URLWithString: trimmed];
+ if ([url isFileURL])
+ [paths addObject: [url path]];
+ else if ([trimmed hasPrefix: @"/"])
+ [paths addObject: trimmed];
+ }
+ [pboard setPropertyList: paths forType: @"NSFilenamesPboardType"];
+ [raw release];
+ }
+ else
+ {
+ [pboard setData: rawData forType: pboardType];
+ }
+
+ if (nswindow)
+ {
+ NSDragOperation op = wl_action_to_ns(wlconfig->dnd_current_action);
+ NSPoint nsPos =
+ NSMakePoint(wlconfig->dnd_x, window->height - wlconfig->dnd_y);
+ NSEvent *ev =
+ [NSEvent otherEventWithType: NSAppKitDefined
+ location: nsPos
+ modifierFlags: 0
+ timestamp: [[NSDate date] timeIntervalSinceReferenceDate]
+ windowNumber: window->window_id
+ context: nil
+ subtype: GSAppKitDraggingDrop
+ data1: (NSInteger)op
+ data2: 0];
+ [NSApp postEvent: ev atStart: NO];
+ }
+
+ /* Finish the offer (v3+). */
+ if (wlconfig->data_device_manager_version >= 3)
+ wl_data_offer_finish(wlconfig->dnd_offer);
+ }
+
+cleanup:
+
+ wl_data_offer_destroy(wlconfig->dnd_offer);
+ wlconfig->dnd_offer = NULL;
+ dnd_offer_mimes_free(wlconfig);
+ wlconfig->dnd_incoming = NO;
+}
+
+static void
+device_selection(void *data, struct wl_data_device *device,
+ struct wl_data_offer *offer)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandDnD", @"device_selection: %p", (void *)offer);
+
+ /* Clipboard selection is outside M1 scope. Destroy the offer if it's the
+ * same as the pending dnd_offer (which means it was a selection, not DnD). */
+ if (offer && offer == wlconfig->dnd_offer)
+ {
+ wl_data_offer_destroy(offer);
+ wlconfig->dnd_offer = NULL;
+ dnd_offer_mimes_free(wlconfig);
+ }
+ else if (offer)
+ {
+ wl_data_offer_destroy(offer);
+ }
+}
+
+const struct wl_data_device_listener data_device_listener = {
+ device_data_offer,
+ device_enter,
+ device_leave,
+ device_motion,
+ device_drop,
+ device_selection,
+};
+
+
+/* ── Lightweight NSWindow subclass for the drag icon ─────────────────────── */
+
+@interface WaylandRawWindow : NSWindow
+@end
+
+@implementation WaylandRawWindow
+
+- (BOOL) canBecomeMainWindow { return NO; }
+- (BOOL) canBecomeKeyWindow { return NO; }
+
+- (void) _initDefaults
+{
+ [super _initDefaults];
+ [self setReleasedWhenClosed: NO];
+ [self setExcludedFromWindowsMenu: YES];
+}
+
+- (void) orderWindow: (NSWindowOrderingMode)place relativeTo: (NSInteger)otherWin
+{
+ [super orderWindow: place relativeTo: otherWin];
+ [self setLevel: NSPopUpMenuWindowLevel];
+}
+
+@end
+
+
+/* ── WaylandDragView ─────────────────────────────────────────────────────── */
+
+@interface WaylandDragView ()
+{
+ void *_dragCursorCid;
+ BOOL _waylandExternalDragActive; /* YES after wl_data_device_start_drag */
+}
+@end
+
+
+@implementation WaylandDragView
+
+static WaylandDragView *sharedDragView = nil;
+
++ (id) sharedDragView
+{
+ if (sharedDragView == nil)
+ sharedDragView = [WaylandDragView new];
+ return sharedDragView;
+}
+
++ (Class) windowClass
+{
+ return [WaylandRawWindow class];
+}
+
+- (void) updateDragInfoFromEvent: (NSEvent *)event
+{
+ destWindow = [event window];
+ dragPoint = [event locationInWindow];
+ dragSequence = [event timestamp];
+ dragMask = [event data2];
+}
+
+- (void) resetDragInfo
+{
+ DESTROY(dragPasteboard);
+}
+
+/* Called from device_enter to set up NSDraggingInfo for an inbound drag. */
+- (void) setupInboundDragWithPasteboard: (NSPasteboard *)pb
+ operation: (NSDragOperation)op
+{
+ ASSIGN(dragPasteboard, pb);
+ dragSource = nil;
+ destExternal = YES;
+ dragMask = op;
+ operationMask = NSDragOperationAll;
+}
+
+
+/* ── Outbound drag ─────────────────────────────────────────────────────────
+ *
+ * Override dragImage: to call wl_data_device_start_drag before letting
+ * GSDragView run its event loop. The data_source callbacks post a fake
+ * NSLeftMouseUp to exit the loop when the compositor signals completion.
+ */
+- (void) dragImage: (NSImage *)anImage
+ at: (NSPoint)screenLocation
+ offset: (NSSize)initialOffset
+ event: (NSEvent *)event
+ pasteboard: (NSPasteboard *)pboard
+ source: (id)sourceObject
+ slideBack: (BOOL)slideFlag
+{
+ WaylandConfig *wlconfig = [(WaylandServer *)GSCurrentServer() wlconfig];
+
+ if (!wlconfig->data_device || !wlconfig->data_device_manager)
+ {
+ NSDebugMLLog(@"WaylandDnD",
+ @"WaylandDragView: no wl_data_device — skipping external drag");
+ [super dragImage: anImage at: screenLocation offset: initialOffset
+ event: event pasteboard: pboard source: sourceObject
+ slideBack: slideFlag];
+ return;
+ }
+
+ /* Find the origin surface (the surface the drag started on). */
+ int originWinNum = [event windowNumber];
+ struct window *originWin = get_window_with_id(wlconfig, originWinNum);
+ struct wl_surface *originSurface = originWin ? originWin->surface : NULL;
+
+ if (!originSurface)
+ {
+ NSDebugMLLog(@"WaylandDnD",
+ @"WaylandDragView: no origin surface for window %d", originWinNum);
+ [super dragImage: anImage at: screenLocation offset: initialOffset
+ event: event pasteboard: pboard source: sourceObject
+ slideBack: slideFlag];
+ return;
+ }
+
+ /* Create the data source and offer all MIME types from the pasteboard. */
+ struct wl_data_source *source =
+ wl_data_device_manager_create_data_source(wlconfig->data_device_manager);
+ if (!source)
+ {
+ NSDebugMLLog(@"WaylandDnD", @"WaylandDragView: failed to create wl_data_source");
+ [super dragImage: anImage at: screenLocation offset: initialOffset
+ event: event pasteboard: pboard source: sourceObject
+ slideBack: slideFlag];
+ return;
+ }
+
+ wl_data_source_add_listener(source, &data_source_listener, wlconfig);
+
+ for (NSString *pt in [pboard types])
+ {
+ const char *mime = mime_for_pboard_type(pt);
+ if (mime)
+ {
+ wl_data_source_offer(source, mime);
+ /* Also offer the plain ASCII variant for text so legacy apps can receive it. */
+ if (strcmp(mime, "text/plain;charset=utf-8") == 0)
+ wl_data_source_offer(source, "text/plain");
+ }
+ }
+
+ if (wlconfig->data_device_manager_version >= 3)
+ {
+ NSDragOperation srcMask =
+ [sourceObject draggingSourceOperationMaskForLocal: NO];
+ wl_data_source_set_actions(source, ns_op_to_wl_actions(srcMask));
+ }
+
+ wlconfig->dnd_source = source;
+
+ /* The drag serial comes from the button-press event that triggered this drag. */
+ uint32_t serial = (uint32_t)[event eventNumber];
+
+ wl_data_device_start_drag(wlconfig->data_device, source,
+ originSurface,
+ NULL, /* icon surface — cursor is set via wl_pointer */
+ serial);
+ wl_display_flush(wlconfig->display);
+
+ _waylandExternalDragActive = YES;
+
+ NSDebugMLLog(@"WaylandDnD",
+ @"WaylandDragView: wl_data_device_start_drag (serial=%u)", serial);
+
+ /* Let GSDragView run its standard event loop. The data_source callbacks
+ * (cancelled / dnd_finished) will post a fake NSLeftMouseUp to exit it. */
+ [super dragImage: anImage at: screenLocation offset: initialOffset
+ event: event pasteboard: pboard source: sourceObject
+ slideBack: slideFlag];
+
+ _waylandExternalDragActive = NO;
+
+ /* Clean up the source if the compositor did not fire dnd_finished
+ * (e.g., version < 3 compositor). */
+ if (wlconfig->dnd_source)
+ {
+ wl_data_source_destroy(wlconfig->dnd_source);
+ wlconfig->dnd_source = NULL;
+ }
+}
+
+- (void) postDragEvent: (NSEvent *)theEvent
+{
+ if (!_waylandExternalDragActive)
+ {
+ [super postDragEvent: theEvent];
+ return;
+ }
+
+ /* During a Wayland-driven drag the compositor stops delivering pointer
+ * events, so only two event types matter in GSDragView's loop:
+ *
+ * NSLeftMouseUp — fake event posted by data_source callbacks to exit
+ * the loop when the drag ends (cancel or finish).
+ *
+ * NSAppKitDefined — GSAppKitDraggingEnter/Update/Drop events generated
+ * by the inter-process device_enter/motion/drop callbacks
+ * and posted to the app queue. Forward them through
+ * super so AppKit routes them to the target window's
+ * dragging protocol methods (draggingEntered: etc.).
+ * In-process events go directly via sendEvent: and
+ * never reach here.
+ *
+ * Everything else is suppressed — no pointer motion arrives in this mode. */
+ switch ([theEvent type])
+ {
+ case NSLeftMouseUp:
+ isDragging = NO;
+ break;
+ case NSAppKitDefined:
+ [super postDragEvent: theEvent];
+ break;
+ default:
+ break;
+ }
+}
+
+- (void) sendExternalEvent: (GSAppKitSubtype)subtype
+ action: (NSDragOperation)action
+ position: (NSPoint)eventLocation
+ timestamp: (NSTimeInterval)time
+ toWindow: (int)dWindowNumber
+{
+ /* The Wayland compositor manages the external drag entirely after
+ * wl_data_device_start_drag — no protocol messages need to be sent here. */
+ NSDebugMLLog(@"WaylandDnD",
+ @"WaylandDragView: sendExternalEvent (subtype=%d) — handled by compositor",
+ (int)subtype);
+}
+
+
+/* ── Drag icon (outbound) ─────────────────────────────────────────────────── */
+
+- (void) _setupWindowFor: (NSImage *)anImage
+ mousePosition: (NSPoint)mPoint
+ imagePosition: (NSPoint)iPoint
+{
+ if (anImage == nil)
+ anImage = [NSImage imageNamed: @"common_Close"];
+
+ NSSize imageSize = [anImage size];
+
+ [dragCell setImage: anImage];
+ dragPosition = mPoint;
+ newPosition = mPoint;
+ offset.width = mPoint.x - iPoint.x;
+ offset.height = mPoint.y - iPoint.y;
+
+ NSPoint hotspot;
+ hotspot.x = offset.width;
+ hotspot.y = imageSize.height - offset.height;
+ if (hotspot.x < 0) hotspot.x = 0;
+ if (hotspot.y < 0) hotspot.y = 0;
+
+ NSDebugMLLog(@"WaylandDnD", @"WaylandDragView: drag cursor hotspot=(%g,%g)",
+ hotspot.x, hotspot.y);
+
+ WaylandServer *server = (WaylandServer *)GSCurrentServer();
+ [server imagecursor: hotspot : anImage : &_dragCursorCid];
+ if (_dragCursorCid != NULL)
+ [server setcursor: _dragCursorCid];
+}
+
+- (void) _clearupWindow
+{
+ WaylandServer *server = (WaylandServer *)GSCurrentServer();
+
+ void *arrowCid = NULL;
+ [server standardcursor: GSArrowCursor : &arrowCid];
+ if (arrowCid != NULL)
+ [server setcursor: arrowCid];
+
+ if (_dragCursorCid != NULL)
+ {
+ [server freecursor: _dragCursorCid];
+ _dragCursorCid = NULL;
+ }
+}
+
+- (void) _moveDraggedImageToNewPosition
+{
+ dragPosition = newPosition;
+}
+
+- (NSWindow *) windowAcceptingDnDunder: (NSPoint)p
+ windowRef: (int *)mouseWindowRef
+{
+ WaylandConfig *wlconfig =
+ [(WaylandServer *)GSCurrentServer() wlconfig];
+ struct window *window;
+ struct output *output = NULL;
+
+ wl_list_for_each(output, &wlconfig->output_list, link)
+ break;
+
+ if (output == NULL)
+ {
+ if (mouseWindowRef) *mouseWindowRef = 0;
+ return nil;
+ }
+
+ int dragWinNum = (_window != nil) ? [_window windowNumber] : -1;
+
+ struct window *candidate = NULL;
+ wl_list_for_each(window, &wlconfig->window_list, link)
+ {
+ if (window->window_id == dragWinNum)
+ continue;
+ if (window->ignoreMouse || window->terminated || !window->configured)
+ continue;
+
+ float ns_x = window->pos_x;
+ float ns_y = output->height - window->pos_y - window->height;
+
+ if (p.x >= ns_x && p.x < ns_x + window->width
+ && p.y >= ns_y && p.y < ns_y + window->height)
+ {
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+ if (nswindow == nil) continue;
+ NSCountedSet *dragTypes =
+ [GSCurrentServer() dragTypesForWindow: nswindow];
+ if ([dragTypes count] > 0)
+ candidate = window;
+ }
+ }
+
+ if (candidate != NULL)
+ {
+ if (mouseWindowRef) *mouseWindowRef = candidate->window_id;
+ return GSWindowWithNumber(candidate->window_id);
+ }
+
+ if (mouseWindowRef) *mouseWindowRef = 0;
+ return nil;
+}
+
+@end
+
+
+/* ── WaylandServer (DragAndDrop) ─────────────────────────────────────────── */
+
+@implementation WaylandServer (DragAndDrop)
+
+- (id ) dragInfo
+{
+ return [WaylandDragView sharedDragView];
+}
+
+- (BOOL) addDragTypes: (NSArray *)types toWindow: (NSWindow *)win
+{
+ return [super addDragTypes: types toWindow: win];
+}
+
+- (BOOL) removeDragTypes: (NSArray *)types fromWindow: (NSWindow *)win
+{
+ return [super removeDragTypes: types fromWindow: win];
+}
+
+@end
diff --git a/Source/wayland/WaylandGLContext.m b/Source/wayland/WaylandGLContext.m
new file mode 100644
index 00000000..5c71bd6b
--- /dev/null
+++ b/Source/wayland/WaylandGLContext.m
@@ -0,0 +1,773 @@
+/* -*- mode:ObjC -*-
+ WaylandGLContext - backend implementation of NSOpenGLContext using EGL
+
+ Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This file is part of the GNUstep Backend.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; see the file COPYING.LIB.
+ If not, see or write to the
+ Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "wayland/WaylandServer.h"
+#include "wayland/WaylandOpenGL.h"
+
+static WaylandGLContext *currentGLContext;
+
+@implementation WaylandGLContext
+
++ (void)clearCurrentContext
+{
+ if (currentGLContext != nil && currentGLContext->_eglDisplay != EGL_NO_DISPLAY)
+ {
+ eglMakeCurrent(currentGLContext->_eglDisplay,
+ EGL_NO_SURFACE,
+ EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ }
+ currentGLContext = nil;
+}
+
++ (NSOpenGLContext *)currentContext
+{
+ return currentGLContext;
+}
+
+- (void *)CGLContextObj
+{
+ return (void *)_eglContext;
+}
+
+- (void)copyAttributesFromContext:(NSOpenGLContext *)context
+ withMask:(unsigned long)mask
+{
+ (void)context;
+ (void)mask;
+}
+
+- (id)initWithCGLContextObj:(void *)context
+{
+ NSDebugMLLog(@"OpenGL", @"initWithCGLContextObj is not supported on Wayland (%p)", context);
+ [self release];
+ return nil;
+}
+
+- (BOOL)_ensureDisplayAndContextWithShare:(NSOpenGLContext *)share
+{
+ EGLint major;
+ EGLint minor;
+ EGLConfig eglConfig;
+ EGLContext shareContext = EGL_NO_CONTEXT;
+ EGLint glesContextAttrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+ EGLint *contextAttrs = NULL;
+ struct wl_display *wlDisplay;
+
+ if (_eglDisplay != EGL_NO_DISPLAY && _eglContext != EGL_NO_CONTEXT)
+ {
+ return YES;
+ }
+
+ if (_window == NULL && [self _attachToWindowIfNeeded] == NO)
+ {
+ return NO;
+ }
+
+ wlDisplay = NULL;
+ if (_window != NULL && _window->wlconfig != NULL)
+ {
+ wlDisplay = _window->wlconfig->display;
+ }
+
+ if (wlDisplay == NULL)
+ {
+ NSDebugMLLog(@"OpenGL", @"Cannot create EGL display without an attached Wayland window");
+ return NO;
+ }
+
+ _eglDisplay = eglGetDisplay((EGLNativeDisplayType)wlDisplay);
+ if (_eglDisplay == EGL_NO_DISPLAY)
+ {
+ NSDebugMLLog(@"OpenGL", @"eglGetDisplay failed");
+ return NO;
+ }
+
+ if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE)
+ {
+ NSDebugMLLog(@"OpenGL", @"eglInitialize failed");
+ return NO;
+ }
+
+ if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE)
+ {
+ NSDebugMLLog(@"OpenGL", @"eglBindAPI(EGL_OPENGL_API) failed, trying GLES2");
+ if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE)
+ {
+ NSDebugMLLog(@"OpenGL", @"eglBindAPI failed for OpenGL and GLES");
+ return NO;
+ }
+ contextAttrs = glesContextAttrs;
+ }
+
+ eglConfig = [(WaylandGLPixelFormat *)_pixelFormat eglConfigForDisplay:_eglDisplay];
+ if (eglConfig == NULL)
+ {
+ return NO;
+ }
+
+ if (share != nil && [share isKindOfClass:[WaylandGLContext class]])
+ {
+ shareContext = ((WaylandGLContext *)share)->_eglContext;
+ }
+
+ _eglContext = eglCreateContext(_eglDisplay, eglConfig, shareContext, contextAttrs);
+ if (_eglContext == EGL_NO_CONTEXT)
+ {
+ NSDebugMLLog(@"OpenGL", @"eglCreateContext failed");
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)_attachToWindowIfNeeded
+{
+ GSDisplayServer *server;
+ NSWindow *window;
+ struct window *newWindow;
+
+ if (_view == nil)
+ {
+ return NO;
+ }
+
+ window = [_view window];
+ if (window == nil)
+ {
+ return NO;
+ }
+
+ server = GSCurrentServer();
+ newWindow = (struct window *)[server windowDevice:[window windowNumber]];
+ if (newWindow == NULL)
+ {
+ return NO;
+ }
+
+ if (_window != newWindow)
+ {
+ if (_window != NULL)
+ {
+ _window->usesOpenGL = NO;
+ }
+ _window = newWindow;
+ [self _destroySurface];
+ }
+
+ return YES;
+}
+
+- (void)_computeViewGeometry:(NSRect *)outFrame
+{
+ NSRect bounds = [_view bounds];
+ NSRect frame = [_view convertRect:bounds toView:nil];
+ /* AppKit Y-up → Wayland Y-down: flip origin.y relative to window height */
+ frame.origin.y = _window->height - NSMaxY(frame);
+
+ static BOOL _loggedOnce = NO;
+ if (!_loggedOnce)
+ {
+ _loggedOnce = YES;
+ NSLog(@"WaylandGL: _computeViewGeometry:"
+ @" viewBounds=%@ convertedFrame=%@ windowH=%.0f"
+ @" → waylandFrame=%@",
+ NSStringFromRect(bounds),
+ NSStringFromRect([_view convertRect:bounds toView:nil]),
+ (double)_window->height,
+ NSStringFromRect(frame));
+ }
+
+ *outFrame = frame;
+}
+
+- (BOOL)_ensureSurface
+{
+ EGLConfig eglConfig;
+ NSRect viewFrame;
+ struct wl_surface *renderSurface;
+ int subW, subH, subX, subY;
+
+ if (_window == NULL || _window->surface == NULL)
+ {
+ return NO;
+ }
+
+ [self _computeViewGeometry:&viewFrame];
+ subX = (int)NSMinX(viewFrame);
+ subY = (int)NSMinY(viewFrame);
+ subW = (int)NSWidth(viewFrame);
+ subH = (int)NSHeight(viewFrame);
+ if (subW <= 0 || subH <= 0)
+ {
+ return NO;
+ }
+
+ if (_glSurface == NULL)
+ {
+ WaylandConfig *wlconfig = _window->wlconfig;
+
+ if (wlconfig->subcompositor == NULL)
+ {
+ NSLog(@"WaylandGL: _ensureSurface: no subcompositor — using window surface directly");
+ renderSurface = _window->surface;
+ _window->usesOpenGL = YES;
+ }
+ else
+ {
+ _glSurface = wl_compositor_create_surface(wlconfig->compositor);
+ if (_glSurface == NULL)
+ {
+ NSLog(@"WaylandGL: _ensureSurface: wl_compositor_create_surface failed");
+ return NO;
+ }
+
+ _glSubsurface = wl_subcompositor_get_subsurface(
+ wlconfig->subcompositor, _glSurface, _window->surface);
+ if (_glSubsurface == NULL)
+ {
+ NSLog(@"WaylandGL: _ensureSurface: wl_subcompositor_get_subsurface failed");
+ wl_surface_destroy(_glSurface);
+ _glSurface = NULL;
+ return NO;
+ }
+
+ wl_subsurface_set_desync(_glSubsurface);
+ wl_subsurface_set_position(_glSubsurface, subX, subY);
+ _glSurfaceBinding = (struct wl_surface_binding *)
+ malloc(sizeof(struct wl_surface_binding));
+ _glSurfaceBinding->window = _window;
+ _glSurfaceBinding->offset_x = (float)subX;
+ _glSurfaceBinding->offset_y = (float)subY;
+ wl_surface_set_user_data(_glSurface, _glSurfaceBinding);
+ wl_surface_commit(_window->surface);
+ wl_display_flush(wlconfig->display);
+
+ NSLog(@"WaylandGL: _ensureSurface: subsurface created glSurface=%p pos=(%d,%d) size=(%dx%d)",
+ _glSurface, subX, subY, subW, subH);
+ renderSurface = _glSurface;
+ }
+ }
+ else
+ {
+ renderSurface = _glSurface;
+ }
+
+ if (_eglWindow == NULL)
+ {
+ _eglWindow = wl_egl_window_create(renderSurface, subW, subH);
+ if (_eglWindow == NULL)
+ {
+ NSDebugMLLog(@"OpenGL", @"wl_egl_window_create failed");
+ return NO;
+ }
+ }
+
+ if (_eglSurface != EGL_NO_SURFACE)
+ {
+ return YES;
+ }
+
+ eglConfig = [(WaylandGLPixelFormat *)_pixelFormat eglConfigForDisplay:_eglDisplay];
+ _eglSurface = eglCreateWindowSurface(_eglDisplay,
+ eglConfig,
+ (EGLNativeWindowType)_eglWindow,
+ NULL);
+
+ if (_eglSurface == EGL_NO_SURFACE)
+ {
+ NSLog(@"WaylandGL: _ensureSurface: eglCreateWindowSurface failed (0x%x)", eglGetError());
+ return NO;
+ }
+
+ if (_swapInterval >= 0)
+ {
+ eglSwapInterval(_eglDisplay, _swapInterval);
+ }
+
+ NSLog(@"WaylandGL: _ensureSurface: EGL surface ready eglSurface=%p eglWindow=%p",
+ _eglSurface, _eglWindow);
+ return YES;
+}
+
+- (void)_destroySurface
+{
+ if (_eglDisplay != EGL_NO_DISPLAY && _eglSurface != EGL_NO_SURFACE)
+ {
+ eglDestroySurface(_eglDisplay, _eglSurface);
+ _eglSurface = EGL_NO_SURFACE;
+ }
+
+ if (_eglWindow != NULL)
+ {
+ wl_egl_window_destroy(_eglWindow);
+ _eglWindow = NULL;
+ }
+
+ if (_glSubsurface != NULL)
+ {
+ wl_subsurface_destroy(_glSubsurface);
+ _glSubsurface = NULL;
+ }
+
+ if (_glSurface != NULL)
+ {
+ wl_surface_destroy(_glSurface);
+ _glSurface = NULL;
+ }
+
+ if (_glSurfaceBinding != NULL)
+ {
+ free(_glSurfaceBinding);
+ _glSurfaceBinding = NULL;
+ }
+}
+
+- (void)_loadExtensions
+{
+ const char *eglExts;
+ const char *glExts;
+
+ if (_eglDisplay == EGL_NO_DISPLAY || _eglContext == EGL_NO_CONTEXT)
+ return;
+
+ _extensionsLoaded = YES;
+
+ eglExts = eglQueryString(_eglDisplay, EGL_EXTENSIONS);
+ if (eglExts == NULL)
+ eglExts = "";
+
+ if (strstr(eglExts, "EGL_EXT_image_dma_buf_import") != NULL)
+ {
+ _pfnCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)
+ eglGetProcAddress("eglCreateImageKHR");
+ _pfnDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)
+ eglGetProcAddress("eglDestroyImageKHR");
+ if (_pfnCreateImageKHR != NULL && _pfnDestroyImageKHR != NULL)
+ {
+ _hasDmaBufImport = YES;
+ NSDebugMLLog(@"OpenGL", @"WaylandGL: EGL_EXT_image_dma_buf_import available");
+ }
+ }
+
+ if (_hasDmaBufImport
+ && strstr(eglExts, "EGL_EXT_image_dma_buf_import_modifiers") != NULL)
+ {
+ _pfnQueryDmaBufFormats = (PFNEGLQUERYDMABUFFORMATSEXTPROC)
+ eglGetProcAddress("eglQueryDmaBufFormatsEXT");
+ _pfnQueryDmaBufModifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)
+ eglGetProcAddress("eglQueryDmaBufModifiersEXT");
+ if (_pfnQueryDmaBufFormats != NULL && _pfnQueryDmaBufModifiers != NULL)
+ {
+ _hasDmaBufImportModifiers = YES;
+ NSDebugMLLog(@"OpenGL", @"WaylandGL: EGL_EXT_image_dma_buf_import_modifiers available");
+ }
+ }
+
+ /* GL_OES_EGL_image + GL_OES_EGL_image_external — needs a current context */
+ glExts = (const char *)glGetString(GL_EXTENSIONS);
+ if (glExts == NULL)
+ glExts = "";
+
+ if (strstr(glExts, "GL_OES_EGL_image") != NULL)
+ {
+ void *pfn = (void *)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+ if (pfn != NULL)
+ {
+ _pfnGLImageTargetTexture2D = pfn;
+ _hasExternalTexture = YES;
+ NSDebugMLLog(@"OpenGL", @"WaylandGL: GL_OES_EGL_image_external available");
+ }
+ }
+}
+
+- (BOOL)supportsDmaBufImport
+{
+ return _hasDmaBufImport;
+}
+
+- (BOOL)supportsExternalTexture
+{
+ return _hasExternalTexture;
+}
+
+- (EGLDisplay)eglDisplay
+{
+ return _eglDisplay;
+}
+
+- (EGLImageKHR)createEGLImageFromDmaBufFd:(int)fd
+ width:(int)width
+ height:(int)height
+ stride:(int)stride
+ offset:(int)offset
+ fourcc:(uint32_t)fourcc
+{
+ EGLint attrs[] = {
+ EGL_WIDTH, (EGLint)width,
+ EGL_HEIGHT, (EGLint)height,
+ EGL_LINUX_DRM_FOURCC_EXT, (EGLint)fourcc,
+ EGL_DMA_BUF_PLANE0_FD_EXT, (EGLint)fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, (EGLint)offset,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)stride,
+ EGL_NONE
+ };
+ EGLImageKHR image;
+
+ if (!_hasDmaBufImport || _pfnCreateImageKHR == NULL
+ || _eglDisplay == EGL_NO_DISPLAY)
+ return EGL_NO_IMAGE_KHR;
+
+ image = _pfnCreateImageKHR(_eglDisplay, EGL_NO_CONTEXT,
+ EGL_LINUX_DMA_BUF_EXT,
+ (EGLClientBuffer)NULL, attrs);
+ if (image == EGL_NO_IMAGE_KHR)
+ NSDebugMLLog(@"OpenGL", @"WaylandGL: eglCreateImageKHR(dma-buf) failed: 0x%x",
+ eglGetError());
+ return image;
+}
+
+- (EGLImageKHR)createEGLImageFromDmaBufFd:(int)fd
+ width:(int)width
+ height:(int)height
+ stride:(int)stride
+ offset:(int)offset
+ fourcc:(uint32_t)fourcc
+ modifier:(uint64_t)modifier
+{
+ EGLint modLo = (EGLint)(modifier & 0xFFFFFFFFu);
+ EGLint modHi = (EGLint)(modifier >> 32);
+ EGLint attrs[] = {
+ EGL_WIDTH, (EGLint)width,
+ EGL_HEIGHT, (EGLint)height,
+ EGL_LINUX_DRM_FOURCC_EXT, (EGLint)fourcc,
+ EGL_DMA_BUF_PLANE0_FD_EXT, (EGLint)fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, (EGLint)offset,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)stride,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, modLo,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, modHi,
+ EGL_NONE
+ };
+ EGLImageKHR image;
+
+ if (!_hasDmaBufImport || _pfnCreateImageKHR == NULL
+ || _eglDisplay == EGL_NO_DISPLAY)
+ return EGL_NO_IMAGE_KHR;
+
+ if (!_hasDmaBufImportModifiers)
+ {
+ NSDebugMLLog(@"OpenGL",
+ @"WaylandGL: modifier requested but EGL_EXT_image_dma_buf_import_modifiers unavailable");
+ return EGL_NO_IMAGE_KHR;
+ }
+
+ image = _pfnCreateImageKHR(_eglDisplay, EGL_NO_CONTEXT,
+ EGL_LINUX_DMA_BUF_EXT,
+ (EGLClientBuffer)NULL, attrs);
+ if (image == EGL_NO_IMAGE_KHR)
+ NSDebugMLLog(@"OpenGL",
+ @"WaylandGL: eglCreateImageKHR(dma-buf+modifier) failed: 0x%x",
+ eglGetError());
+ return image;
+}
+
+- (void)destroyEGLImage:(EGLImageKHR)image
+{
+ if (image == EGL_NO_IMAGE_KHR || _pfnDestroyImageKHR == NULL
+ || _eglDisplay == EGL_NO_DISPLAY)
+ return;
+ _pfnDestroyImageKHR(_eglDisplay, image);
+}
+
+- (void)bindEGLImage:(EGLImageKHR)image toExternalTexture:(unsigned int)texId
+{
+ typedef void (*TargetTex2DOES)(GLenum, GLeglImageOES);
+ TargetTex2DOES fn = (TargetTex2DOES)_pfnGLImageTargetTexture2D;
+
+ if (image == EGL_NO_IMAGE_KHR || fn == NULL)
+ return;
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, texId);
+ fn(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image);
+}
+
+- (id)initWithFormat:(NSOpenGLPixelFormat *)format
+ shareContext:(NSOpenGLContext *)share
+{
+ self = [super init];
+
+ if (!self)
+ {
+ return nil;
+ }
+
+ if (format == nil || [format isKindOfClass:[WaylandGLPixelFormat class]] == NO)
+ {
+ NSDebugMLLog(@"OpenGL", @"Invalid pixel format %@", format);
+ [self release];
+ return nil;
+ }
+
+ _eglDisplay = EGL_NO_DISPLAY;
+ _eglContext = EGL_NO_CONTEXT;
+ _eglSurface = EGL_NO_SURFACE;
+ _eglWindow = NULL;
+ _glSurface = NULL;
+ _glSubsurface = NULL;
+ _window = NULL;
+ _extensionsLoaded = NO;
+ _hasDmaBufImport = NO;
+ _hasDmaBufImportModifiers = NO;
+ _hasExternalTexture = NO;
+ _pfnCreateImageKHR = NULL;
+ _pfnDestroyImageKHR = NULL;
+ _pfnQueryDmaBufFormats = NULL;
+ _pfnQueryDmaBufModifiers = NULL;
+ _pfnGLImageTargetTexture2D = NULL;
+ /* Default to unthrottled swaps. With vsync=1 eglSwapBuffers blocks on the
+ compositor frame callback, which starves the main run loop when called
+ from a timer. Apps that want vsync can set NSOpenGLCPSwapInterval = 1. */
+ _swapInterval = 0;
+
+ _pixelFormat = RETAIN(format);
+ _shareContext = RETAIN(share);
+
+ if (share != nil && [share isKindOfClass:[WaylandGLContext class]])
+ {
+ _eglDisplay = ((WaylandGLContext *)share)->_eglDisplay;
+ }
+
+ return self;
+}
+
+- (NSOpenGLPixelFormat *)pixelFormat
+{
+ return _pixelFormat;
+}
+
+- (void)setView:(NSView *)view
+{
+ if (view == nil)
+ {
+ [NSException raise:NSInvalidArgumentException
+ format:@"setView called with nil"];
+ }
+
+ ASSIGN(_view, view);
+
+ if ([self _attachToWindowIfNeeded] == NO)
+ {
+ return;
+ }
+
+ if ([self _ensureDisplayAndContextWithShare:_shareContext] == NO)
+ {
+ return;
+ }
+
+ [self _ensureSurface];
+}
+
+- (NSView *)view
+{
+ return _view;
+}
+
+- (void)clearDrawable
+{
+ if (_window != NULL)
+ {
+ _window->usesOpenGL = NO;
+ }
+ [self _destroySurface];
+}
+
+- (void)makeCurrentContext
+{
+ if (_view == nil)
+ {
+ [NSException raise:NSGenericException
+ format:@"GL Context has no view attached, cannot be made current"];
+ }
+
+ if ([self _attachToWindowIfNeeded] == NO)
+ {
+ return;
+ }
+
+ if ([self _ensureDisplayAndContextWithShare:_shareContext] == NO)
+ {
+ return;
+ }
+
+ if ([self _ensureSurface] == NO)
+ {
+ return;
+ }
+
+ if (eglMakeCurrent(_eglDisplay, _eglSurface, _eglSurface, _eglContext) == EGL_FALSE)
+ {
+ NSDebugMLLog(@"OpenGL", @"eglMakeCurrent failed");
+ return;
+ }
+
+ if (!_extensionsLoaded)
+ [self _loadExtensions];
+
+ currentGLContext = self;
+}
+
+- (void)flushBuffer
+{
+ if (_eglDisplay == EGL_NO_DISPLAY || _eglSurface == EGL_NO_SURFACE)
+ {
+ NSLog(@"WaylandGL: flushBuffer: skipped — no EGL display/surface");
+ return;
+ }
+
+ static NSUInteger _swapCount = 0;
+ EGLBoolean ok = eglSwapBuffers(_eglDisplay, _eglSurface);
+ if (++_swapCount <= 5 || _swapCount % 90 == 0)
+ NSLog(@"WaylandGL: flushBuffer: eglSwapBuffers #%lu result=%d err=0x%x",
+ (unsigned long)_swapCount, (int)ok, ok ? 0 : eglGetError());
+
+ if (_window != NULL && _window->wlconfig != NULL)
+ {
+ wl_display_flush(_window->wlconfig->display);
+ }
+}
+
+- (void)update
+{
+ NSRect viewFrame;
+
+ [self _attachToWindowIfNeeded];
+
+ if (_eglWindow == NULL || _window == NULL)
+ {
+ return;
+ }
+
+ [self _computeViewGeometry:&viewFrame];
+
+ wl_egl_window_resize(_eglWindow,
+ (int)NSWidth(viewFrame),
+ (int)NSHeight(viewFrame),
+ 0,
+ 0);
+
+ if (_glSubsurface != NULL)
+ {
+ int newSubX = (int)NSMinX(viewFrame);
+ int newSubY = (int)NSMinY(viewFrame);
+ wl_subsurface_set_position(_glSubsurface, newSubX, newSubY);
+ if (_glSurfaceBinding != NULL)
+ {
+ _glSurfaceBinding->offset_x = (float)newSubX;
+ _glSurfaceBinding->offset_y = (float)newSubY;
+ }
+ wl_surface_commit(_window->surface);
+ wl_display_flush(_window->wlconfig->display);
+ }
+}
+
+- (void)getValues:(long *)vals forParameter:(NSOpenGLContextParameter)param
+{
+ if (vals == NULL)
+ {
+ return;
+ }
+
+ switch (param)
+ {
+ case NSOpenGLCPSwapInterval:
+ *vals = _swapInterval;
+ break;
+ default:
+ *vals = 0;
+ break;
+ }
+}
+
+- (void)setValues:(const long *)vals forParameter:(NSOpenGLContextParameter)param
+{
+ if (vals == NULL)
+ {
+ return;
+ }
+
+ if (param == NSOpenGLCPSwapInterval)
+ {
+ _swapInterval = (int)*vals;
+ if (_eglDisplay != EGL_NO_DISPLAY)
+ {
+ eglSwapInterval(_eglDisplay, _swapInterval);
+ }
+ }
+}
+
+- (void)dealloc
+{
+ if (currentGLContext == self)
+ {
+ [WaylandGLContext clearCurrentContext];
+ }
+
+ if (_window != NULL)
+ {
+ _window->usesOpenGL = NO;
+ }
+
+ [self _destroySurface];
+
+ if (_eglDisplay != EGL_NO_DISPLAY && _eglContext != EGL_NO_CONTEXT)
+ {
+ eglDestroyContext(_eglDisplay, _eglContext);
+ _eglContext = EGL_NO_CONTEXT;
+ }
+
+ RELEASE(_view);
+ RELEASE(_shareContext);
+ RELEASE(_pixelFormat);
+
+ [super dealloc];
+}
+
+@end
diff --git a/Source/wayland/WaylandGLPixelFormat.m b/Source/wayland/WaylandGLPixelFormat.m
new file mode 100644
index 00000000..5e59b89c
--- /dev/null
+++ b/Source/wayland/WaylandGLPixelFormat.m
@@ -0,0 +1,250 @@
+/* -*- mode:ObjC -*-
+ WaylandGLPixelFormat - backend implementation of NSOpenGLPixelFormat
+
+ Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This file is part of the GNUstep Backend.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; see the file COPYING.LIB.
+ If not, see or write to the
+ Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "wayland/WaylandOpenGL.h"
+
+@implementation WaylandGLPixelFormat
+
+static BOOL
+_isAttributeWithValue(NSOpenGLPixelFormatAttribute attr)
+{
+ switch (attr)
+ {
+ case NSOpenGLPFAAuxBuffers:
+ case NSOpenGLPFAColorSize:
+ case NSOpenGLPFAAlphaSize:
+ case NSOpenGLPFADepthSize:
+ case NSOpenGLPFAStencilSize:
+ case NSOpenGLPFAAccumSize:
+ case NSOpenGLPFARendererID:
+ case NSOpenGLPFAScreenMask:
+ case NSOpenGLPFASamples:
+ case NSOpenGLPFAAuxDepthStencil:
+ case NSOpenGLPFASampleBuffers:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+- (id)initWithAttributes:(NSOpenGLPixelFormatAttribute *)attribs
+{
+ NSOpenGLPixelFormatAttribute *ptr;
+
+ self = [super init];
+ if (self == nil)
+ {
+ return nil;
+ }
+
+ if (attribs == NULL)
+ {
+ _attributeCount = 1;
+ _attributes = NSZoneMalloc(NSDefaultMallocZone(),
+ sizeof(NSOpenGLPixelFormatAttribute));
+ _attributes[0] = (NSOpenGLPixelFormatAttribute)0;
+ return self;
+ }
+
+ _attributeCount = 1;
+ for (ptr = attribs; *ptr != 0; ptr++)
+ {
+ _attributeCount++;
+ if (_isAttributeWithValue(*ptr))
+ {
+ if (*(ptr + 1) != 0)
+ {
+ ptr++;
+ _attributeCount++;
+ }
+ }
+ }
+
+ _attributes = NSZoneMalloc(NSDefaultMallocZone(),
+ _attributeCount * sizeof(NSOpenGLPixelFormatAttribute));
+ memcpy(_attributes, attribs,
+ _attributeCount * sizeof(NSOpenGLPixelFormatAttribute));
+
+ return self;
+}
+
+- (EGLConfig)eglConfigForDisplay:(EGLDisplay)eglDisplay
+{
+ EGLint redSize = 8;
+ EGLint greenSize = 8;
+ EGLint blueSize = 8;
+ EGLint alphaSize = 8;
+ EGLint depthSize = 24;
+ EGLint stencilSize = 8;
+ EGLint sampleBuffers = 0;
+ EGLint samples = 0;
+ /* Match the renderable type to the API that was bound with eglBindAPI()
+ before this call. Requesting both EGL_OPENGL_BIT|EGL_OPENGL_ES2_BIT
+ requires a single config to support both APIs simultaneously, which
+ most drivers do not provide, causing eglChooseConfig to return 0
+ configs and the context creation to fail. */
+ EGLint renderableType;
+ switch (eglQueryAPI())
+ {
+ case EGL_OPENGL_ES_API:
+ renderableType = EGL_OPENGL_ES2_BIT;
+ break;
+ case EGL_OPENGL_API:
+ default:
+ renderableType = EGL_OPENGL_BIT;
+ break;
+ }
+ EGLConfig config = NULL;
+ EGLint configCount = 0;
+ NSUInteger i;
+
+ if (_attributes != NULL)
+ {
+ for (i = 0; i < _attributeCount; i++)
+ {
+ NSOpenGLPixelFormatAttribute attr = _attributes[i];
+ if (_isAttributeWithValue(attr) == NO)
+ {
+ continue;
+ }
+
+ if (i + 1 >= _attributeCount)
+ {
+ break;
+ }
+
+ switch (attr)
+ {
+ case NSOpenGLPFAColorSize:
+ redSize = greenSize = blueSize = ((EGLint)_attributes[i + 1] / 3);
+ if (redSize < 1)
+ {
+ redSize = greenSize = blueSize = 1;
+ }
+ break;
+ case NSOpenGLPFAAlphaSize:
+ alphaSize = _attributes[i + 1];
+ break;
+ case NSOpenGLPFADepthSize:
+ depthSize = _attributes[i + 1];
+ break;
+ case NSOpenGLPFAStencilSize:
+ stencilSize = _attributes[i + 1];
+ break;
+ case NSOpenGLPFASampleBuffers:
+ sampleBuffers = _attributes[i + 1];
+ break;
+ case NSOpenGLPFASamples:
+ samples = _attributes[i + 1];
+ break;
+ default:
+ break;
+ }
+
+ i++;
+ }
+ }
+
+ {
+ EGLint attrs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, renderableType,
+ EGL_RED_SIZE, redSize,
+ EGL_GREEN_SIZE, greenSize,
+ EGL_BLUE_SIZE, blueSize,
+ EGL_ALPHA_SIZE, alphaSize,
+ EGL_DEPTH_SIZE, depthSize,
+ EGL_STENCIL_SIZE, stencilSize,
+ EGL_SAMPLE_BUFFERS, sampleBuffers,
+ EGL_SAMPLES, samples,
+ EGL_NONE
+ };
+
+ if (eglChooseConfig(eglDisplay, attrs, &config, 1, &configCount) == EGL_FALSE
+ || configCount == 0)
+ {
+ NSDebugMLLog(@"OpenGL", @"No EGL config matched requested NSOpenGL attributes");
+ return NULL;
+ }
+ }
+
+ return config;
+}
+
+- (void)getValues:(int *)vals
+ forAttribute:(NSOpenGLPixelFormatAttribute)attrib
+ forVirtualScreen:(int)screen
+{
+ NSUInteger i;
+
+ (void)screen;
+
+ if (vals == NULL)
+ {
+ return;
+ }
+
+ *vals = 0;
+ if (_attributes == NULL)
+ {
+ return;
+ }
+
+ for (i = 0; i + 1 < _attributeCount; i++)
+ {
+ if (_attributes[i] == attrib)
+ {
+ if (_isAttributeWithValue(attrib))
+ {
+ *vals = _attributes[i + 1];
+ }
+ else
+ {
+ *vals = 1;
+ }
+ return;
+ }
+ }
+}
+
+- (void)dealloc
+{
+ if (_attributes != NULL)
+ {
+ NSZoneFree(NSDefaultMallocZone(), _attributes);
+ _attributes = NULL;
+ }
+
+ [super dealloc];
+}
+
+@end
diff --git a/Source/wayland/WaylandInputServer.m b/Source/wayland/WaylandInputServer.m
new file mode 100644
index 00000000..dc4e81de
--- /dev/null
+++ b/Source/wayland/WaylandInputServer.m
@@ -0,0 +1,286 @@
+/* WaylandInputServer - Input method / preedit support for Wayland backend
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+
+ This file is part of the GNUstep Backend.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; see the file COPYING.LIB.
+ If not, see or write to the
+ Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "wayland/WaylandInputServer.h"
+
+/* Commit pending text_input state to the compositor. */
+static void
+commit_text_input(WaylandConfig *wlconfig)
+{
+ if (wlconfig && wlconfig->text_input)
+ {
+ zwp_text_input_v3_commit(wlconfig->text_input);
+ wl_display_flush(wlconfig->display);
+ }
+}
+
+
+@implementation WaylandInputServer
+
+- (id) initWithDelegate: (id)aDelegate name: (NSString *)name
+{
+ delegate = aDelegate;
+ ASSIGN(server_name, name);
+ focused_window_id = 0;
+ wlconfig = NULL;
+ NSDebugMLLog(@"WaylandIME", @"WaylandInputServer: initialized");
+ return self;
+}
+
+- (void) dealloc
+{
+ DESTROY(server_name);
+ [super dealloc];
+}
+
+- (void) setWlconfig: (WaylandConfig *)config
+{
+ wlconfig = config;
+}
+
+- (void) setFocusedWindowId: (int)windowId
+{
+ focused_window_id = windowId;
+ NSDebugMLLog(@"WaylandIME", @"WaylandInputServer: focused window id = %d", windowId);
+}
+
+- (int) focusedWindowId
+{
+ return focused_window_id;
+}
+
+
+/* NSInputServiceProvider protocol */
+
+- (void) activeConversationChanged: (id)sender
+ toNewConversation: (long)newConversation
+{
+ [super activeConversationChanged: sender toNewConversation: newConversation];
+
+ if ([sender respondsToSelector: @selector(window)] == NO)
+ return;
+
+ NSWindow *window = [sender performSelector: @selector(window)];
+ if (window != nil)
+ {
+ focused_window_id = [window windowNumber];
+ NSDebugMLLog(@"WaylandIME",
+ @"WaylandInputServer: conversation changed, focused window = %d",
+ focused_window_id);
+ }
+}
+
+- (void) activeConversationWillChange: (id)sender
+ fromOldConversation: (long)oldConversation
+{
+ [super activeConversationWillChange: sender
+ fromOldConversation: oldConversation];
+}
+
+@end
+
+
+@implementation WaylandInputServer (InputMethod)
+
+- (NSString *) inputMethodStyle
+{
+ /* When text_input_v3 is available the preedit is delivered inline via
+ * setMarkedText:selectedRange: on the focused responder; no separate IM
+ * window is needed. Return nil so AppKit does not try to manage an IM
+ * panel on our behalf. */
+ return nil;
+}
+
+- (NSString *) fontSize: (int *)size
+{
+ NSString *str = [[NSUserDefaults standardUserDefaults]
+ stringForKey: @"NSFontSize"];
+ if (!str)
+ str = @"12";
+ if (size)
+ *size = (int) strtol([str cString], NULL, 10);
+ return str;
+}
+
+- (BOOL) clientWindowRect: (NSRect *)rect
+{
+ if (!rect || focused_window_id == 0)
+ return NO;
+ NSWindow *window = GSWindowWithNumber(focused_window_id);
+ if (window == nil)
+ return NO;
+ *rect = [window frame];
+ return YES;
+}
+
+/* ── IME geometry: status area ─────────────────────────────────────────────
+ *
+ * The status area is where the IM draws its mode indicator (e.g. "あ" for
+ * hiragana mode). We report the bottom-left corner of the focused window
+ * as a reasonable default; a real status bar is not rendered in the backend.
+ */
+- (BOOL) statusArea: (NSRect *)rect
+{
+ if (!rect)
+ return NO;
+
+ if (focused_window_id != 0)
+ {
+ NSWindow *window = GSWindowWithNumber(focused_window_id);
+ if (window)
+ {
+ NSRect frame = [window frame];
+ *rect = NSMakeRect(frame.origin.x,
+ frame.origin.y,
+ frame.size.width,
+ 20.0);
+ return YES;
+ }
+ }
+
+ /* Fallback: bottom-left of screen. */
+ NSRect screen = [[NSScreen mainScreen] frame];
+ *rect = NSMakeRect(screen.origin.x, screen.origin.y, screen.size.width, 20.0);
+ return YES;
+}
+
+/* ── IME geometry: preedit area ─────────────────────────────────────────────
+ *
+ * The preedit area covers the region where candidate text is displayed.
+ * We return the stored rect if we have one, otherwise the client window rect.
+ */
+- (BOOL) preeditArea: (NSRect *)rect
+{
+ if (!rect)
+ return NO;
+
+ if (wlconfig && !NSIsEmptyRect(wlconfig->ime_preedit_rect))
+ {
+ *rect = wlconfig->ime_preedit_rect;
+ return YES;
+ }
+
+ return [self clientWindowRect: rect];
+}
+
+/* ── IME geometry: preedit spot ─────────────────────────────────────────────
+ *
+ * The preedit spot is the screen coordinate of the text-insertion cursor.
+ * The compositor IM uses this to position its candidate window.
+ */
+- (BOOL) preeditSpot: (NSPoint *)p
+{
+ if (!p)
+ return NO;
+
+ if (wlconfig && wlconfig->text_input_active)
+ {
+ *p = wlconfig->ime_preedit_spot;
+ return YES;
+ }
+
+ /* Fallback: centre of the focused window. */
+ if (focused_window_id != 0)
+ {
+ NSWindow *window = GSWindowWithNumber(focused_window_id);
+ if (window)
+ {
+ NSRect frame = [window frame];
+ *p = NSMakePoint(NSMidX(frame), NSMidY(frame));
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+/* ── IME geometry setters ───────────────────────────────────────────────────
+ *
+ * AppKit calls these when the text cursor moves. We store the values and
+ * forward them to the compositor via set_cursor_rectangle so the IM can
+ * reposition its candidate window.
+ */
+- (BOOL) setStatusArea: (NSRect *)rect
+{
+ /* Status area is compositor-managed; acknowledge but don't act. */
+ return YES;
+}
+
+- (BOOL) setPreeditArea: (NSRect *)rect
+{
+ if (!rect || !wlconfig)
+ return NO;
+
+ wlconfig->ime_preedit_rect = *rect;
+
+ if (wlconfig->text_input && wlconfig->text_input_active)
+ {
+ zwp_text_input_v3_set_cursor_rectangle(
+ wlconfig->text_input,
+ (int32_t) rect->origin.x,
+ (int32_t) rect->origin.y,
+ (int32_t) rect->size.width,
+ (int32_t) rect->size.height);
+ commit_text_input(wlconfig);
+ }
+
+ return YES;
+}
+
+- (BOOL) setPreeditSpot: (NSPoint *)p
+{
+ if (!p || !wlconfig)
+ return NO;
+
+ wlconfig->ime_preedit_spot = *p;
+
+ /* Forward cursor position as a 1×(line-height) rectangle. */
+ if (wlconfig->text_input && wlconfig->text_input_active)
+ {
+ int32_t lineHeight = 16; /* sensible default; AppKit can update via setPreeditArea: */
+ zwp_text_input_v3_set_cursor_rectangle(
+ wlconfig->text_input,
+ (int32_t) p->x,
+ (int32_t) p->y,
+ 0,
+ lineHeight);
+ commit_text_input(wlconfig);
+ NSDebugMLLog(@"WaylandIME",
+ @"WaylandInputServer: preedit spot → (%g,%g)", p->x, p->y);
+ }
+
+ return YES;
+}
+
+@end
diff --git a/Source/wayland/WaylandServer+Cursor.m b/Source/wayland/WaylandServer+Cursor.m
index 7c9cf14a..4e8113be 100644
--- a/Source/wayland/WaylandServer+Cursor.m
+++ b/Source/wayland/WaylandServer+Cursor.m
@@ -27,6 +27,7 @@
#include "wayland/WaylandServer.h"
#include "cairo/WaylandCairoShmSurface.h"
+#include
#import
#import
#import
@@ -52,14 +53,53 @@
{
WaylandConfig *wlconfig = data;
- struct window *window = wl_surface_get_user_data(surface);
+ struct window *window = surface_get_window(surface);
- if (window->ignoreMouse)
+ if (window == NULL || window->ignoreMouse)
{
return;
}
- wlconfig->pointer.focus = window;
+ float ox, oy;
+ surface_get_offset(surface, &ox, &oy);
+ wlconfig->pointer.focus = window;
+ wlconfig->pointer.focus_offset_x = ox;
+ wlconfig->pointer.focus_offset_y = oy;
+
+ float sx = wl_fixed_to_double(sx_w) + ox;
+ float sy = wl_fixed_to_double(sy_w) + oy;
+
+ /* Track cursor global (output-relative) position so we can infer where
+ * xdg_toplevel windows actually are on screen.
+ *
+ * Layer-shell surfaces have a known global position (we set the margins
+ * ourselves), so entering one gives us a ground-truth fix. When the
+ * cursor then enters an xdg_toplevel we subtract the surface-local entry
+ * point to infer that toplevel's global origin and store it in
+ * saved_pos_x/y. Both values are in Wayland output coordinates
+ * (Y increasing downward from the output's top-left corner). */
+ if (window->layer_surface)
+ {
+ wlconfig->cursor_global_x = window->pos_x + sx;
+ wlconfig->cursor_global_y = window->pos_y + sy;
+ wlconfig->cursor_global_valid = YES;
+ }
+ else if (window->toplevel)
+ {
+ if (wlconfig->cursor_global_valid)
+ {
+ window->saved_pos_x = wlconfig->cursor_global_x - sx;
+ window->saved_pos_y = wlconfig->cursor_global_y - sy;
+ window->global_pos_known = YES;
+ }
+ /* Keep cursor_global current while traversing toplevels. */
+ if (window->global_pos_known)
+ {
+ wlconfig->cursor_global_x = window->saved_pos_x + sx;
+ wlconfig->cursor_global_y = window->saved_pos_y + sy;
+ wlconfig->cursor_global_valid = YES;
+ }
+ }
if (wlconfig->pointer.captured)
{
@@ -68,11 +108,8 @@
[(WaylandServer *)GSCurrentServer() initializeMouseIfRequired];
-
- NSDebugLog(@"[%d] pointer_handle_enter",window->window_id);
-
- float sx = wl_fixed_to_double(sx_w);
- float sy = wl_fixed_to_double(sy_w);
+ NSDebugFLLog(@"WaylandPointer", @"[%d] pointer_handle_enter sx=%g sy=%g",
+ window->window_id, sx, sy);
if (window && wlconfig->pointer.serial)
@@ -116,9 +153,9 @@
{
WaylandConfig *wlconfig = data;
- struct window *window = wl_surface_get_user_data(surface);
+ struct window *window = surface_get_window(surface);
- if (window->ignoreMouse)
+ if (window == NULL || window->ignoreMouse)
{
return;
}
@@ -159,8 +196,10 @@
[GSCurrentServer() postEvent:event atStart:NO];
}
- wlconfig->pointer.focus = NULL;
- wlconfig->pointer.serial = serial;
+ wlconfig->pointer.focus = NULL;
+ wlconfig->pointer.focus_offset_x = 0.0f;
+ wlconfig->pointer.focus_offset_y = 0.0f;
+ wlconfig->pointer.serial = serial;
wlconfig->event_serial = serial;
}
}
@@ -181,11 +220,32 @@
{
return;
}
- float sx = wl_fixed_to_double(sx_w);
- float sy = wl_fixed_to_double(sy_w);
+ float sx = wl_fixed_to_double(sx_w) + wlconfig->pointer.focus_offset_x;
+ float sy = wl_fixed_to_double(sy_w) + wlconfig->pointer.focus_offset_y;
wlconfig->pointer.last_timestamp = (NSTimeInterval) time / 1000.0;
+ /* Keep cursor_global current on every motion so that the position is
+ * fresh at the moment a context menu is about to be opened. Use
+ * pointer.focus (the actual Wayland surface receiving events) not the
+ * potentially-captured focused_window. */
+ {
+ struct window *tw = wlconfig->pointer.focus;
+ if (tw)
+ {
+ if (tw->layer_surface)
+ {
+ wlconfig->cursor_global_x = tw->pos_x + sx;
+ wlconfig->cursor_global_y = tw->pos_y + sy;
+ wlconfig->cursor_global_valid = YES;
+ }
+ else if (tw->global_pos_known)
+ {
+ wlconfig->cursor_global_x = tw->saved_pos_x + sx;
+ wlconfig->cursor_global_y = tw->saved_pos_y + sy;
+ }
+ }
+ }
[(WaylandServer *)GSCurrentServer() initializeMouseIfRequired];
@@ -209,7 +269,6 @@
if (wlconfig->pointer.button_state == WL_POINTER_BUTTON_STATE_PRESSED)
{
-
switch (wlconfig->pointer.button)
{
case BTN_LEFT:
@@ -218,7 +277,7 @@
case BTN_RIGHT:
eventType = NSRightMouseDragged;
break;
- case BTN_MIDDLE:
+ default: /* BTN_MIDDLE, BTN_SIDE, BTN_EXTRA, BTN_FORWARD, BTN_BACK, … */
eventType = NSOtherMouseDragged;
break;
}
@@ -285,49 +344,41 @@
{
wlconfig->pointer.button = button;
if (window->toplevel)
- {
- // if the window is a toplevel we check if the event is for resizing or
- // moving the window these actions are delegated to the compositor and
- // therefore we skip forwarding the events to the NSWindow / NSView
-
- NSWindow *nswindow = GSWindowWithNumber(window->window_id);
- if (nswindow != nil)
- {
- GSStandardWindowDecorationView * wd = [nswindow _windowView];
-
- if ([wd pointInTitleBarRect:eventLocation])
- {
- xdg_toplevel_move(window->toplevel, wlconfig->seat, serial);
- window->moving = YES;
- return;
- }
- if ([wd pointInResizeBarRect:eventLocation])
- {
- GSResizeEdgeMode mode = [wd resizeModeForPoint:eventLocation];
-
- uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
-
- if (mode == GSResizeEdgeBottomLeftMode)
- {
- edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
- }
- else if (mode == GSResizeEdgeBottomRightMode)
- {
- edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
- }
- else if (mode == GSResizeEdgeBottomMode)
- {
- edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
- }
-
- xdg_toplevel_resize(window->toplevel, wlconfig->seat, serial,
- edges);
- window->resizing = YES;
- return;
- }
- } // endif nswindow != nil
- } // endif window->toplevel
-
+ {
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+
+ if (nswindow != nil
+ && ![(WaylandServer *)GSCurrentServer() handlesWindowDecorations])
+ {
+ GSStandardWindowDecorationView *wd = [nswindow _windowView];
+
+ if ([wd pointInTitleBarRect:eventLocation])
+ {
+ xdg_toplevel_move(window->toplevel, wlconfig->seat, serial);
+ window->moving = YES;
+ return;
+ }
+
+ if ([wd pointInResizeBarRect:eventLocation])
+ {
+ GSResizeEdgeMode mode = [wd resizeModeForPoint:eventLocation];
+
+ uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
+
+ if (mode == GSResizeEdgeBottomLeftMode)
+ edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
+ else if (mode == GSResizeEdgeBottomRightMode)
+ edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
+ else if (mode == GSResizeEdgeBottomMode)
+ edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
+
+ xdg_toplevel_resize(window->toplevel, wlconfig->seat, serial,
+ edges);
+ window->resizing = YES;
+ return;
+ }
+ }
+ }
if (button == wlconfig->pointer.last_click_button
&& time - wlconfig->pointer.last_click_time < DOUBLECLICK_DELAY
&& fabsf(wlconfig->pointer.x - wlconfig->pointer.last_click_x)
@@ -354,13 +405,14 @@
case BTN_RIGHT:
eventType = NSRightMouseDown;
break;
- case BTN_MIDDLE:
+ default:
+ /* BTN_MIDDLE (2), BTN_SIDE (3), BTN_EXTRA (4),
+ BTN_FORWARD (5), BTN_BACK (6), BTN_TASK (7), … */
eventType = NSOtherMouseDown;
+ NSDebugFLLog(@"WaylandPointer",
+ @"pointer_handle_button: button=0x%x (btn%u) pressed",
+ button, button - BTN_LEFT);
break;
- // TODO: handle BTN_SIDE, BTN_EXTRA, BTN_FORWARD, BTN_BACK and other
- // constants in libinput.
- // We may just want to send NSOtherMouseDown and populate buttonNumber
- // with the libinput constant?
}
}
else if (state == WL_POINTER_BUTTON_STATE_RELEASED)
@@ -385,8 +437,11 @@
case BTN_RIGHT:
eventType = NSRightMouseUp;
break;
- case BTN_MIDDLE:
+ default:
eventType = NSOtherMouseUp;
+ NSDebugFLLog(@"WaylandPointer",
+ @"pointer_handle_button: button=0x%x (btn%u) released",
+ button, button - BTN_LEFT);
break;
}
}
@@ -394,20 +449,22 @@
{
return;
}
- /* FIXME: unlike in _motion and _axis handlers, the argument used in _button
- is the "serial" of the event, not passed and unavailable in _motion and
- _axis handlers. Is it allowed to pass "serial" as the eventNumber: in
- _button handler, but "time" as the eventNumber: in the _motion and _axis
- handlers? */
+ /* eventNumber: use the Wayland serial (a monotonically increasing counter
+ from the compositor) — it is consistent with what the button handlers
+ receive and matches what the pointer-enter handler uses for serial. */
tick = serial;
- /* FIXME: X11 backend uses the XGetPointerMapping()-returned values from
- its map_return argument as constants for buttonNumber. As the variant
- with buttonNumber: seems to be a GNUstep extension, and the value
- internal, it might be ok to just provide libinput constant as we're doing
- here. If this is truly correct, please update this comment to document
- the correctness of doing so. */
- buttonNumber = button;
+ /* buttonNumber: map evdev button codes to a 0-based AppKit button index.
+ * BTN_LEFT (0x110) → 0 (NSLeftMouse*)
+ * BTN_RIGHT (0x111) → 1 (NSRightMouse*)
+ * BTN_MIDDLE (0x112) → 2 (NSOtherMouse*)
+ * BTN_SIDE (0x113) → 3 (NSOtherMouse* — browser back)
+ * BTN_EXTRA (0x114) → 4 (NSOtherMouse* — browser forward)
+ * BTN_FORWARD(0x115) → 5
+ * BTN_BACK (0x116) → 6
+ * This is equivalent to the X11 approach of subtracting the base button
+ * code, and produces stable values across different mouse hardware. */
+ buttonNumber = (int)(button - BTN_LEFT);
event = [NSEvent mouseEventWithType:eventType
location:eventLocation
@@ -433,117 +490,200 @@
}
-// Discrete step information for scroll and other axes.
+/* Accumulate axis delta for this logical frame.
+ * The event is dispatched as a single NSScrollWheel in pointer_handle_frame.
+ * This avoids two separate events when a compositor sends both vertical and
+ * horizontal components in the same frame (common for diagonal touchpad swipes). */
static void
-pointer_handle_frame(void *data, struct wl_pointer *pointer)
-{}
+pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time,
+ uint32_t axis, wl_fixed_t value)
+{
+ WaylandConfig *wlconfig = data;
+ struct window *window = wlconfig->pointer.focus;
+
+ if (window == NULL || window->ignoreMouse)
+ return;
+
+ float delta = wl_fixed_to_double(value) * wlconfig->mouse_scroll_multiplier;
+
+ switch (axis)
+ {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ wlconfig->pointer.frame_deltaY += delta;
+ break;
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ wlconfig->pointer.frame_deltaX += delta;
+ break;
+ default:
+ return;
+ }
-// Source information for scroll and other axes.
+ wlconfig->pointer.frame_has_axis = YES;
+ wlconfig->pointer.frame_time = time;
+
+ NSDebugFLLog(@"WaylandScroll",
+ @"pointer_handle_axis: axis=%u delta=%g (source=%u)",
+ axis, delta, wlconfig->pointer.axis_source);
+}
+
+/* Store the axis source for the current frame (wheel, finger, continuous…). */
static void
pointer_handle_axis_source(void *data, struct wl_pointer *pointer,
uint32_t axis_source)
{
WaylandConfig *wlconfig = data;
wlconfig->pointer.axis_source = axis_source;
+ NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_source: source=%u", axis_source);
}
-// Stop notification for scroll and other axes.
+/* Emit a zero-delta NSScrollWheel to signal that a touchpad gesture ended.
+ * AppKit uses this to stop inertial scrolling. */
static void
pointer_handle_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time,
uint32_t axis)
-{}
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandScroll", @"pointer_handle_axis_stop: axis=%u", axis);
+
+ struct window *window = wlconfig->pointer.focus;
+ if (window == NULL || window->ignoreMouse)
+ return;
+
+ [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired];
+
+ NSPoint loc = NSMakePoint(wlconfig->pointer.x, window->height - wlconfig->pointer.y);
+ NSEvent *ev = [NSEvent mouseEventWithType:NSScrollWheel
+ location:loc
+ modifierFlags:wlconfig->modifiers
+ timestamp:(NSTimeInterval)time / 1000.0
+ windowNumber:(int)window->window_id
+ context:GSCurrentContext()
+ eventNumber:time
+ clickCount:0
+ pressure:0.0
+ buttonNumber:0
+ deltaX:0.0
+ deltaY:0.0
+ deltaZ:0.0];
+ [GSCurrentServer() postEvent:ev atStart:NO];
+}
-// Discrete step information for scroll and other axes.
+/* Accumulate the integer scroll-wheel step count for the current frame.
+ * Discrete steps are reported alongside the smooth axis value and allow
+ * applications to snap to whole-line increments. */
static void
pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer,
uint32_t axis, int discrete)
-{}
-
-// Scroll and other axis notifications.
-static void
-pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time,
- uint32_t axis, wl_fixed_t value)
{
- WaylandConfig *wlconfig = data;
- NSEvent *event;
- NSEventType eventType;
- NSPoint eventLocation;
- NSGraphicsContext *gcontext;
- unsigned int eventFlags;
- float deltaX = 0.0;
- float deltaY = 0.0;
- int clickCount = 1;
- int buttonNumber;
-
- struct window *window = wlconfig->pointer.focus;
- if (window->ignoreMouse)
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandScroll",
+ @"pointer_handle_axis_discrete: axis=%u discrete=%d", axis, discrete);
+ switch (axis)
{
- return;
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ wlconfig->pointer.frame_discrete_y += discrete;
+ break;
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ wlconfig->pointer.frame_discrete_x += discrete;
+ break;
}
+}
- [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired];
+/* Dispatch the accumulated scroll deltas as a single NSScrollWheel event,
+ * then reset the per-frame state. Grouping axis events per frame produces
+ * smoother diagonal scrolling and avoids redundant event dispatch. */
+static void
+pointer_handle_frame(void *data, struct wl_pointer *pointer)
+{
+ WaylandConfig *wlconfig = data;
- gcontext = GSCurrentContext();
- eventLocation
- = NSMakePoint(wlconfig->pointer.x, window->height - wlconfig->pointer.y);
- eventFlags = wlconfig->modifiers;
+ if (!wlconfig->pointer.frame_has_axis)
+ return;
+
+ struct window *window = wlconfig->pointer.focus;
+ if (window == NULL || window->ignoreMouse)
+ goto reset;
- if (wlconfig->pointer.axis_source != WL_POINTER_AXIS_SOURCE_WHEEL)
{
- //axis_source == WL POINTER AXIS SOURCE FINGER
- //axis_source == WL POINTER AXIS SOURCE CONTINUOUS
- // XXX the scroll is from trackpad we should calculate
- // the momentumPhase
- NSDebugLog(@"touch scroll");
+ [(WaylandServer *)GSCurrentServer() initializeMouseIfRequired];
+
+ NSPoint loc = NSMakePoint(wlconfig->pointer.x,
+ window->height - wlconfig->pointer.y);
+ NSTimeInterval ts = (NSTimeInterval)wlconfig->pointer.frame_time / 1000.0;
+
+ NSDebugFLLog(@"WaylandScroll",
+ @"pointer_handle_frame: dx=%g dy=%g disc_x=%d disc_y=%d src=%u",
+ wlconfig->pointer.frame_deltaX, wlconfig->pointer.frame_deltaY,
+ wlconfig->pointer.frame_discrete_x, wlconfig->pointer.frame_discrete_y,
+ wlconfig->pointer.axis_source);
+
+ NSEvent *ev = [NSEvent mouseEventWithType:NSScrollWheel
+ location:loc
+ modifierFlags:wlconfig->modifiers
+ timestamp:ts
+ windowNumber:(int)window->window_id
+ context:GSCurrentContext()
+ eventNumber:wlconfig->pointer.frame_time
+ clickCount:0
+ pressure:0.0
+ buttonNumber:0
+ deltaX:wlconfig->pointer.frame_deltaX
+ deltaY:wlconfig->pointer.frame_deltaY
+ deltaZ:0.0];
+ [GSCurrentServer() postEvent:ev atStart:NO];
}
- //float mouse_scroll_multiplier = wlconfig->mouse_scroll_multiplier;
- /* For smooth-scroll events, we're not doing any cross-event or delta
- calculations, as is done in button event handling. */
+reset:
+ wlconfig->pointer.frame_has_axis = NO;
+ wlconfig->pointer.frame_deltaX = 0.0f;
+ wlconfig->pointer.frame_deltaY = 0.0f;
+ wlconfig->pointer.frame_discrete_x = 0;
+ wlconfig->pointer.frame_discrete_y = 0;
+ wlconfig->pointer.frame_time = 0;
+}
+
+/* wl_pointer.axis_value120 (protocol v8) — high-resolution wheel step.
+ * Accumulate into the per-frame scroll state using the standard 1/120 scale. */
+static void
+pointer_handle_axis_value120(void *data, struct wl_pointer *pointer,
+ uint32_t axis, int32_t value120)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandScroll",
+ @"pointer_handle_axis_value120: axis=%u value120=%d", axis, value120);
+ float delta = ((float)value120 / 120.0f) * wlconfig->mouse_scroll_multiplier;
switch (axis)
{
- case WL_POINTER_AXIS_VERTICAL_SCROLL:
- eventType = NSScrollWheel;
- deltaY = wl_fixed_to_double(value) * wlconfig->mouse_scroll_multiplier;
- break;
- case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
- eventType = NSScrollWheel;
- deltaX = wl_fixed_to_double(value) * wlconfig->mouse_scroll_multiplier;
- break;
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ wlconfig->pointer.frame_deltaY += delta;
+ wlconfig->pointer.frame_discrete_y += (value120 / 120);
+ break;
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ wlconfig->pointer.frame_deltaX += delta;
+ wlconfig->pointer.frame_discrete_x += (value120 / 120);
+ break;
}
+ wlconfig->pointer.frame_has_axis = YES;
+}
- /* FIXME: X11 backend uses the XGetPointerMapping()-returned values from
- its map_return argument as constants for buttonNumber. As the variant
- with buttonNumber: seems to be a GNUstep extension, and the value
- internal, it might be ok to just not provide any value here.
- If this is truly correct, please update this comment to document
- the correctness of doing so. */
- buttonNumber = 0;
-
- event = [NSEvent mouseEventWithType:eventType
- location:eventLocation
- modifierFlags:eventFlags
- timestamp:(NSTimeInterval) time / 1000.0
- windowNumber:(int) window->window_id
- context:gcontext
- eventNumber:time
- clickCount:clickCount
- pressure:1.0
- buttonNumber:buttonNumber
- deltaX:deltaX
- deltaY:deltaY
- deltaZ:0.];
-
- [GSCurrentServer() postEvent:event atStart:NO];
+/* wl_pointer.axis_relative_direction (protocol v9) — natural-scroll hint.
+ * Logged for traceability; direction inversion can be applied here later. */
+static void
+pointer_handle_axis_relative_direction(void *data, struct wl_pointer *pointer,
+ uint32_t axis, uint32_t direction)
+{
+ NSDebugFLLog(@"WaylandScroll",
+ @"pointer_handle_axis_relative_direction: axis=%u direction=%u",
+ axis, direction);
}
-// the Seat category uses this listener
const struct wl_pointer_listener pointer_listener
- = {pointer_handle_enter, pointer_handle_leave,
- pointer_handle_motion, pointer_handle_button,
- pointer_handle_axis, pointer_handle_frame,
- pointer_handle_axis_source, pointer_handle_axis_stop,
- pointer_handle_axis_discrete};
+ = {pointer_handle_enter, pointer_handle_leave,
+ pointer_handle_motion, pointer_handle_button,
+ pointer_handle_axis, pointer_handle_frame,
+ pointer_handle_axis_source, pointer_handle_axis_stop,
+ pointer_handle_axis_discrete, pointer_handle_axis_value120,
+ pointer_handle_axis_relative_direction};
@implementation
WaylandServer (Cursor)
@@ -613,7 +753,7 @@ - (void)releasemouse
- (void)setMouseLocation:(NSPoint)mouseLocation onScreen:(int)aScreen
{
- NSDebugLog(@"setMouseLocation: not supported");
+ NSDebugMLLog(@"WaylandPointer", @"setMouseLocation: not supported on Wayland");
}
- (void)hidecursor
@@ -694,8 +834,8 @@ - (void)standardcursor:(int)style :(void **)cid
}
if (strlen(cursor_name) != 0)
{
- NSDebugLog(@"load cursor from theme for style %d: %s", style,
- cursor_name);
+ NSDebugMLLog(@"WaylandPointer", @"load cursor from theme for style %d: %s", style,
+ cursor_name);
struct cursor *wayland_cursor = calloc(1, sizeof(struct cursor));
wayland_cursor->cursor
@@ -709,7 +849,7 @@ - (void)standardcursor:(int)style :(void **)cid
}
else
{
- NSDebugLog(@"unable to load cursor from theme for style %d", style);
+ NSDebugMLLog(@"WaylandPointer", @"unable to load cursor from theme for style %d", style);
}
}
@@ -727,9 +867,9 @@ - (void)imagecursor:(NSPoint)hotp :(NSImage *)image :(void **)cid
// TODO should check if the bitmaprep format is compatible
memcpy(pbuffer->data, data, [raw_img bytesPerPlane]);
- struct cursor * wayland_cursor = malloc(sizeof(struct cursor));
+ struct cursor *wayland_cursor = calloc(1, sizeof(struct cursor));
- struct wl_cursor * cursor = malloc(sizeof(struct wl_cursor));
+ struct wl_cursor *cursor = calloc(1, sizeof(struct wl_cursor));
cursor->image_count = 1;
cursor->name = "custom";
struct wl_cursor_image * cursor_image = malloc(sizeof(struct wl_cursor_image));
@@ -755,8 +895,8 @@ - (void)setcursorcolor:(NSColor *)fg :(NSColor *)bg :(void *)cid
- (void) recolorcursor:(NSColor *)fg :(NSColor *)bg :(void*) cid
{
- // TODO recolorcursor
- NSDebugLog(@"recolorcursor");
+ /* TODO: implement cursor recolouring on Wayland */
+ NSDebugMLLog(@"WaylandPointer", @"recolorcursor: not yet implemented");
}
- (void)setcursor:(void *)cid
@@ -779,10 +919,6 @@ - (void)setcursor:(void *)cid
return;
}
- if (wayland_cursor->surface)
- {
- wl_surface_destroy(wayland_cursor->surface);
- }
wl_pointer_set_cursor(wlconfig->pointer.wlpointer, wlconfig->event_serial,
wlconfig->cursor_surface,
wayland_cursor->image->hotspot_x,
diff --git a/Source/wayland/WaylandServer+Keyboard.m b/Source/wayland/WaylandServer+Keyboard.m
index c6132a67..5e36197c 100644
--- a/Source/wayland/WaylandServer+Keyboard.m
+++ b/Source/wayland/WaylandServer+Keyboard.m
@@ -1,5 +1,5 @@
-/*
- WaylandServer - Keyboard Handling
+/*
+ WaylandServer - Keyboard Handling + zwp_text_input_v3 (IME/preedit)
Copyright (C) 2020 Free Software Foundation, Inc.
@@ -26,18 +26,34 @@
*/
#include "wayland/WaylandServer.h"
+#include
+
+/* Informal protocol for marked (preedit) text — implemented by NSTextView. */
+@interface NSObject (WaylandMarkedText)
+- (void) setMarkedText: (id)aString selectedRange: (NSRange)selRange;
+- (void) unmarkText;
+- (void) insertText: (id)aString;
+@end
+#include
+#include
#include
#include
-#include
+#include
#include
+#include
+#include
#include
+#include
+#include
#include
+
+/* ── wl_keyboard listener ─────────────────────────────────────────────────── */
+
static void
keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
uint32_t format, int fd, uint32_t size)
{
- NSDebugLog(@"keyboard_handle_keymap");
WaylandConfig *wlconfig = data;
struct xkb_keymap *keymap;
struct xkb_state *state;
@@ -93,6 +109,8 @@
wlconfig->xkb.keymap = keymap;
wlconfig->xkb.state = state;
+ NSDebugFLLog(@"WaylandIME", @"keyboard_handle_keymap: XKB keymap loaded (size=%u)", size);
+
wlconfig->xkb.control_mask
= 1 << xkb_keymap_mod_get_index(wlconfig->xkb.keymap, "Control");
wlconfig->xkb.alt_mask
@@ -105,18 +123,90 @@
keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial,
struct wl_surface *surface, struct wl_array *keys)
{
- // NSDebugLog(@"keyboard_handle_enter");
- WaylandConfig *wlconfig = data;
+ WaylandConfig *wlconfig = data;
wlconfig->event_serial = serial;
+ NSDebugFLLog(@"WaylandIME", @"keyboard_handle_enter: serial=%u", serial);
+
+ if (!surface)
+ return;
+ struct window *window = surface_get_window(surface);
+ if (!window)
+ return;
+
+ wlconfig->keyboard_focus = window;
+
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+ if (!nswindow)
+ return;
+
+ NSEvent *ev = [NSEvent otherEventWithType:NSAppKitDefined
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:window->window_id
+ context:GSCurrentContext()
+ subtype:GSAppKitWindowFocusIn
+ data1:0
+ data2:0];
+ [nswindow sendEvent:ev];
+
+ /* Enable text input so the compositor IM can send preedit/commit events. */
+ if (wlconfig->text_input)
+ {
+ zwp_text_input_v3_enable(wlconfig->text_input);
+ zwp_text_input_v3_commit(wlconfig->text_input);
+ }
}
static void
keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial,
struct wl_surface *surface)
{
- WaylandConfig *wlconfig = data;
+ WaylandConfig *wlconfig = data;
wlconfig->event_serial = serial;
- // NSDebugLog(@"keyboard_handle_leave");
+ NSDebugFLLog(@"WaylandIME", @"keyboard_handle_leave: serial=%u", serial);
+
+ if (!wlconfig->keyboard_focus)
+ {
+ /* Disable text input even if we lost track of focus window. */
+ if (wlconfig->text_input)
+ {
+ zwp_text_input_v3_disable(wlconfig->text_input);
+ zwp_text_input_v3_commit(wlconfig->text_input);
+ }
+ return;
+ }
+
+ /* Clear any pending preedit before disabling. */
+ if (wlconfig->ime_pending_preedit)
+ {
+ free(wlconfig->ime_pending_preedit);
+ wlconfig->ime_pending_preedit = NULL;
+ }
+
+ NSWindow *nswindow = GSWindowWithNumber(wlconfig->keyboard_focus->window_id);
+ if (nswindow)
+ {
+ NSEvent *ev = [NSEvent otherEventWithType:NSAppKitDefined
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:wlconfig->keyboard_focus->window_id
+ context:GSCurrentContext()
+ subtype:GSAppKitWindowFocusOut
+ data1:0
+ data2:0];
+ [nswindow sendEvent:ev];
+ }
+
+ wlconfig->keyboard_focus = NULL;
+
+ if (wlconfig->text_input)
+ {
+ zwp_text_input_v3_disable(wlconfig->text_input);
+ zwp_text_input_v3_commit(wlconfig->text_input);
+ wlconfig->text_input_active = NO;
+ }
}
static void
@@ -125,12 +215,10 @@
uint32_t mods_latched, uint32_t mods_locked,
uint32_t group)
{
- // NSDebugLog(@"keyboard_handle_modifiers");
WaylandConfig *wlconfig = data;
wlconfig->event_serial = serial;
xkb_mod_mask_t mask;
- /* If we're not using a keymap, then we don't handle PC-style modifiers */
if (!wlconfig->xkb.keymap)
return;
@@ -152,50 +240,50 @@
keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state_w)
{
- // NSDebugLog(@"keyboard_handle_key: %d", key);
- WaylandConfig *wlconfig = data;
+ WaylandConfig *wlconfig = data;
wlconfig->event_serial = serial;
- uint32_t code, num_syms;
enum wl_keyboard_key_state state = state_w;
- const xkb_keysym_t *syms;
- xkb_keysym_t sym;
- struct window *window = wlconfig->pointer.focus;
+ uint32_t code;
+ struct window *window = wlconfig->keyboard_focus
+ ? wlconfig->keyboard_focus
+ : wlconfig->pointer.focus;
if (!window)
return;
- code = 0;
- if (key == 28)
- {
- sym = NSCarriageReturnCharacter;
- }
- else if (key == 14)
- {
- sym = NSDeleteCharacter;
- }
- else
- {
- code = key + 8;
+ /* Resolve the XKB keycode (evdev + 8 offset). */
+ code = key + 8;
- num_syms = xkb_state_key_get_syms(wlconfig->xkb.state, code, &syms);
+ /* Build the character string for this keypress.
+ *
+ * xkb_state_key_get_utf8 handles the full XKB composition pipeline,
+ * including dead-key sequences (e.g. dead_acute + 'e' → 'é').
+ * It returns 0 for non-printable keysyms (arrows, function keys, etc.).
+ */
+ char utf8buf[16] = {0};
+ NSString *s = @"";
- sym = XKB_KEY_NoSymbol;
- if (num_syms == 1)
- sym = syms[0];
+ if (key == 28) /* Enter / Return */
+ {
+ unichar cr = NSCarriageReturnCharacter;
+ s = [NSString stringWithCharacters:&cr length:1];
}
-
- NSString *s = [NSString stringWithUTF8String:&sym];
- NSEventType eventType;
-
- if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
+ else if (key == 14) /* Backspace */
{
- eventType = NSKeyDown;
+ unichar del = NSDeleteCharacter;
+ s = [NSString stringWithCharacters:&del length:1];
}
- else
+ else if (wlconfig->xkb.state)
{
- eventType = NSKeyUp;
+ int len = xkb_state_key_get_utf8(wlconfig->xkb.state, code,
+ utf8buf, sizeof(utf8buf) - 1);
+ if (len > 0)
+ s = [NSString stringWithUTF8String:utf8buf];
}
+ NSEventType eventType = (state == WL_KEYBOARD_KEY_STATE_PRESSED)
+ ? NSKeyDown : NSKeyUp;
+
NSEvent *ev = [NSEvent keyEventWithType:eventType
location:NSZeroPoint
modifierFlags:wlconfig->modifiers
@@ -208,15 +296,13 @@
keyCode:code];
[GSCurrentServer() postEvent:ev atStart:NO];
-
- // NSDebugLog(@"keyboard_handle_key: %@", s);
}
static void
keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard,
int32_t rate, int32_t delay)
{
- // NSDebugLog(@"keyboard_handle_repeat_info");
+ /* Key repeat is handled by AppKit; nothing to do here. */
}
const struct wl_keyboard_listener keyboard_listener
@@ -224,7 +310,244 @@
keyboard_handle_leave, keyboard_handle_key,
keyboard_handle_modifiers, keyboard_handle_repeat_info};
-@implementation
-WaylandServer (KeyboardOps)
+
+/* ── zwp_text_input_v3 listener (IME/preedit) ────────────────────────────── */
+
+/* Apply pending preedit to the focused text view via setMarkedText:. */
+static void
+apply_pending_preedit(WaylandConfig *wlconfig)
+{
+ if (!wlconfig->keyboard_focus || !wlconfig->ime_pending_preedit)
+ return;
+
+ NSWindow *win = GSWindowWithNumber(wlconfig->keyboard_focus->window_id);
+ if (!win)
+ return;
+
+ id responder = [win firstResponder];
+ if (![responder respondsToSelector:@selector(setMarkedText:selectedRange:)])
+ return;
+
+ NSString *preeditStr =
+ [NSString stringWithUTF8String:wlconfig->ime_pending_preedit];
+ if (!preeditStr)
+ return;
+
+ /* Underline the preedit text — standard IM convention. */
+ NSAttributedString *marked =
+ [[NSAttributedString alloc]
+ initWithString:preeditStr
+ attributes:@{NSUnderlineStyleAttributeName:
+ @(NSUnderlineStyleSingle)}];
+
+ /* Convert byte cursor offsets to character offsets (UTF-8 → UTF-16).
+ * For simplicity we use the midpoint of the preedit range as selection. */
+ NSUInteger len = [preeditStr length];
+ NSRange sel = (len > 0) ? NSMakeRange(len / 2, 0) : NSMakeRange(0, 0);
+
+ [responder setMarkedText:marked selectedRange:sel];
+ [marked release];
+}
+
+/* Clear any marked text that was set by the IME. */
+static void
+clear_marked_text(WaylandConfig *wlconfig)
+{
+ if (!wlconfig->keyboard_focus)
+ return;
+ NSWindow *win = GSWindowWithNumber(wlconfig->keyboard_focus->window_id);
+ if (!win)
+ return;
+ id responder = [win firstResponder];
+ if ([responder respondsToSelector:@selector(unmarkText)])
+ [responder unmarkText];
+}
+
+static void
+text_input_enter(void *data, struct zwp_text_input_v3 *ti,
+ struct wl_surface *surface)
+{
+ WaylandConfig *wlconfig = data;
+ wlconfig->text_input_active = YES;
+ NSDebugFLLog(@"WaylandIME", @"text_input_enter");
+
+ zwp_text_input_v3_enable(ti);
+ zwp_text_input_v3_commit(ti);
+}
+
+static void
+text_input_leave(void *data, struct zwp_text_input_v3 *ti,
+ struct wl_surface *surface)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandIME", @"text_input_leave");
+
+ /* Clear any preedit text the IM left behind. */
+ if (wlconfig->ime_pending_preedit)
+ {
+ clear_marked_text(wlconfig);
+ free(wlconfig->ime_pending_preedit);
+ wlconfig->ime_pending_preedit = NULL;
+ }
+ if (wlconfig->ime_pending_commit)
+ {
+ free(wlconfig->ime_pending_commit);
+ wlconfig->ime_pending_commit = NULL;
+ }
+
+ wlconfig->text_input_active = NO;
+ zwp_text_input_v3_disable(ti);
+ zwp_text_input_v3_commit(ti);
+}
+
+static void
+text_input_preedit_string(void *data, struct zwp_text_input_v3 *ti,
+ const char *text, int32_t cursor_begin,
+ int32_t cursor_end)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandIME", @"text_input_preedit: '%s' [%d,%d]",
+ text ? text : "", cursor_begin, cursor_end);
+
+ free(wlconfig->ime_pending_preedit);
+ wlconfig->ime_pending_preedit = text ? strdup(text) : NULL;
+ wlconfig->ime_preedit_cursor_begin = cursor_begin;
+ wlconfig->ime_preedit_cursor_end = cursor_end;
+}
+
+static void
+text_input_commit_string(void *data, struct zwp_text_input_v3 *ti,
+ const char *text)
+{
+ WaylandConfig *wlconfig = data;
+ NSDebugFLLog(@"WaylandIME", @"text_input_commit: '%s'", text ? text : "");
+
+ free(wlconfig->ime_pending_commit);
+ wlconfig->ime_pending_commit = text ? strdup(text) : NULL;
+}
+
+static void
+text_input_delete_surrounding_text(void *data, struct zwp_text_input_v3 *ti,
+ uint32_t before_length,
+ uint32_t after_length)
+{
+ NSDebugFLLog(@"WaylandIME",
+ @"text_input_delete_surrounding: before=%u after=%u",
+ before_length, after_length);
+ /* Full surrounding text deletion is deferred to a later milestone. */
+}
+
+static void
+text_input_done(void *data, struct zwp_text_input_v3 *ti, uint32_t serial)
+{
+ WaylandConfig *wlconfig = data;
+ wlconfig->ime_serial = serial;
+ NSDebugFLLog(@"WaylandIME", @"text_input_done: serial=%u", serial);
+
+ struct window *window = wlconfig->keyboard_focus;
+ if (!window)
+ goto cleanup;
+
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+ if (!nswindow)
+ goto cleanup;
+
+ id responder = [nswindow firstResponder];
+
+ /* ── Commit string: insert text into the focused control ── */
+ if (wlconfig->ime_pending_commit)
+ {
+ NSString *commitStr =
+ [NSString stringWithUTF8String:wlconfig->ime_pending_commit];
+ if (commitStr && [commitStr length] > 0)
+ {
+ /* Clear any preedit before committing. */
+ if ([responder respondsToSelector:@selector(unmarkText)])
+ [responder unmarkText];
+
+ if ([responder respondsToSelector:@selector(insertText:)])
+ {
+ [responder insertText:commitStr];
+ }
+ else
+ {
+ /* Fallback: deliver each character as a key event. */
+ for (NSUInteger i = 0; i < [commitStr length]; i++)
+ {
+ unichar c = [commitStr characterAtIndex:i];
+ NSString *cs = [NSString stringWithCharacters:&c length:1];
+ NSEvent *ev = [NSEvent keyEventWithType:NSKeyDown
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:[[NSDate date]
+ timeIntervalSinceReferenceDate]
+ windowNumber:window->window_id
+ context:nil
+ characters:cs
+ charactersIgnoringModifiers:cs
+ isARepeat:NO
+ keyCode:0];
+ [nswindow sendEvent:ev];
+ }
+ }
+ }
+ free(wlconfig->ime_pending_commit);
+ wlconfig->ime_pending_commit = NULL;
+ }
+
+ /* ── Preedit string: show/update marked text ── */
+ if (wlconfig->ime_pending_preedit)
+ {
+ apply_pending_preedit(wlconfig);
+ /* Keep ime_pending_preedit alive for area/spot queries. */
+ }
+ else
+ {
+ /* NULL preedit = clear any marked text the IM set previously. */
+ clear_marked_text(wlconfig);
+ }
+
+cleanup:;
+}
+
+/* v2 events — logged but not acted on in this milestone. */
+static void
+text_input_action(void *data, struct zwp_text_input_v3 *ti,
+ uint32_t index, uint32_t direction)
+{
+ NSDebugFLLog(@"WaylandIME", @"text_input_action: idx=%u dir=%u",
+ index, direction);
+}
+
+static void
+text_input_language(void *data, struct zwp_text_input_v3 *ti,
+ const char *language)
+{
+ NSDebugFLLog(@"WaylandIME", @"text_input_language: %s",
+ language ? language : "");
+}
+
+static void
+text_input_preedit_hint(void *data, struct zwp_text_input_v3 *ti,
+ uint32_t start, uint32_t end, uint32_t hint)
+{
+ NSDebugFLLog(@"WaylandIME", @"text_input_preedit_hint: [%u,%u] hint=%u",
+ start, end, hint);
+}
+
+const struct zwp_text_input_v3_listener text_input_v3_listener = {
+ text_input_enter,
+ text_input_leave,
+ text_input_preedit_string,
+ text_input_commit_string,
+ text_input_delete_surrounding_text,
+ text_input_done,
+ text_input_action,
+ text_input_language,
+ text_input_preedit_hint,
+};
+
+
+@implementation WaylandServer (KeyboardOps)
@end
diff --git a/Source/wayland/WaylandServer+Output.m b/Source/wayland/WaylandServer+Output.m
index 650ddd83..7797b7e7 100644
--- a/Source/wayland/WaylandServer+Output.m
+++ b/Source/wayland/WaylandServer+Output.m
@@ -1,4 +1,4 @@
-/*
+/*
WaylandServer - Output Handling
Copyright (C) 2020 Free Software Foundation, Inc.
@@ -26,63 +26,224 @@
*/
#include "wayland/WaylandServer.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/* ── Helpers ─────────────────────────────────────────────────────────────── */
+
+/* Compute the logical (AppKit) dimensions of an output, accounting for its
+ * pixel scale factor and any rotation transform reported by the compositor.
+ *
+ * Physical mode width/height are in hardware pixels. After dividing by the
+ * scale factor we get logical pixels (points). A 90° or 270° rotation also
+ * swaps the two axes. */
+static void
+output_compute_effective_size(struct output *output)
+{
+ int ew = (output->scale > 0) ? output->width / output->scale : output->width;
+ int eh = (output->scale > 0) ? output->height / output->scale : output->height;
+
+ switch (output->transform)
+ {
+ case WL_OUTPUT_TRANSFORM_90:
+ case WL_OUTPUT_TRANSFORM_270:
+ case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+ case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+ /* Swap: the output is rotated 90° or 270° relative to normal. */
+ output->effective_width = eh;
+ output->effective_height = ew;
+ break;
+ default:
+ output->effective_width = ew;
+ output->effective_height = eh;
+ break;
+ }
+}
+
+/* Post NSApplicationDidChangeScreenParametersNotification on the main thread
+ * so NSScreen reloads its geometry. Guard against early-init calls that
+ * arrive before NSApp is running. */
+static void
+notify_screen_parameters_changed(void)
+{
+ if (NSApp == nil)
+ return;
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName: NSApplicationDidChangeScreenParametersNotification
+ object: NSApp];
+}
+
+/* Clamp all regular (non-layer-shell) windows assigned to an output so that
+ * they remain within the output's effective (logical) bounds after a
+ * reconfigure. We update our internal pos_x/y and ask AppKit to move the
+ * window; the compositor will honour or adjust the request as it sees fit. */
+static void
+reposition_windows_for_output(struct output *output)
+{
+ WaylandConfig *wlconfig = output->wlconfig;
+ struct window *window;
+
+ wl_list_for_each(window, &wlconfig->window_list, link)
+ {
+ if (window->output != output)
+ continue;
+ if (window->terminated || window->layer_surface)
+ continue;
+
+ int ew = output->effective_width;
+ int eh = output->effective_height;
+ BOOL moved = NO;
+
+ /* Clamp position so at least the top-left corner is on-screen. */
+ if (window->pos_x < 0) { window->pos_x = 0; moved = YES; }
+ if (window->pos_y < 0) { window->pos_y = 0; moved = YES; }
+ if (window->pos_x >= (float)ew) { window->pos_x = MAX(0, ew - (int)window->width); moved = YES; }
+ if (window->pos_y >= (float)eh) { window->pos_y = MAX(0, eh - (int)window->height); moved = YES; }
+
+ if (moved)
+ {
+ NSDebugFLLog(@"WaylandOutput",
+ @"reposition_windows_for_output: clamped window %d "
+ @"to (%g,%g) within %dx%d output",
+ window->window_id, window->pos_x, window->pos_y, ew, eh);
+
+ /* Notify AppKit of the new position in GNUstep screen coordinates
+ * (Y-flipped: bottom-left origin, as in AppKit). */
+ NSWindow *nswindow = GSWindowWithNumber(window->window_id);
+ if (nswindow)
+ {
+ float ns_y = eh - window->pos_y - window->height;
+ [nswindow setFrameOrigin: NSMakePoint(window->pos_x, ns_y)];
+ }
+ }
+ }
+}
+
+
+/* ── wl_output event handlers ─────────────────────────────────────────────── */
static void
handle_geometry(void *data, struct wl_output *wl_output, int x, int y,
int physical_width, int physical_height, int subpixel,
const char *make, const char *model, int transform)
{
- NSDebugLog(@"handle_geometry");
struct output *output = data;
- output->alloc_x = x;
- output->alloc_y = y;
+ output->alloc_x = x;
+ output->alloc_y = y;
output->transform = transform;
- if (output->make)
- free(output->make);
- output->make = strdup(make);
+ if (output->make) free(output->make);
+ if (output->model) free(output->model);
+ output->make = strdup(make ? make : "");
+ output->model = strdup(model ? model : "");
- if (output->model)
- free(output->model);
- output->model = strdup(model);
+ NSDebugFLLog(@"WaylandOutput",
+ @"handle_geometry: output %u @(%d,%d) physical=%dx%dmm "
+ @"transform=%d make=%s model=%s",
+ output->server_output_id, x, y,
+ physical_width, physical_height, transform, make, model);
}
static void
-handle_done(void *data, struct wl_output *wl_output)
+handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
+ int width, int height, int refresh)
{
- NSDebugLog(@"handle_done");
+ struct output *output = data;
+
+ NSDebugFLLog(@"WaylandOutput",
+ @"handle_mode: output %u flags=0x%x size=%dx%d refresh=%dHz",
+ output->server_output_id, flags, width, height, refresh / 1000);
+
+ if (flags & WL_OUTPUT_MODE_CURRENT)
+ {
+ output->width = width;
+ output->height = height;
+ NSDebugFLLog(@"WaylandOutput",
+ @"handle_mode: output %u current mode → %dx%d physical",
+ output->server_output_id, width, height);
+ }
}
static void
handle_scale(void *data, struct wl_output *wl_output, int32_t scale)
{
- NSDebugLog(@"handle_scale");
struct output *output = data;
-
output->scale = scale;
+ NSDebugFLLog(@"WaylandOutput", @"handle_scale: output %u scale=%d",
+ output->server_output_id, scale);
}
+/* handle_done is called once all output properties for a configuration batch
+ * have been sent. It is the right place to:
+ * 1. Compute the effective (logical) output size.
+ * 2. Reposition any windows that fell outside the new bounds.
+ * 3. Notify AppKit so NSScreen reloads its geometry. */
static void
-handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width,
- int height, int refresh)
+handle_done(void *data, struct wl_output *wl_output)
{
- NSDebugLog(@"handle_mode");
struct output *output = data;
- if (flags & WL_OUTPUT_MODE_CURRENT)
- {
- output->width = width;
- output->height = height /*- 30*/;
- NSDebugLog(@"handle_mode output=%dx%d", width, height);
-
- // XXX - Should we implement this?
- // if (display->output_configure_handler)
- // (*display->output_configure_handler)
- // (output, display->user_data);
- //
- }
+ int old_ew = output->effective_width;
+ int old_eh = output->effective_height;
+
+ output_compute_effective_size(output);
+
+ NSDebugFLLog(@"WaylandOutput",
+ @"handle_done: output %u '%s' logical=%dx%d (phys=%dx%d scale=%d "
+ @"transform=%d) alloc=(%d,%d)",
+ output->server_output_id,
+ output->name ? output->name : "unknown",
+ output->effective_width, output->effective_height,
+ output->width, output->height, output->scale, output->transform,
+ output->alloc_x, output->alloc_y);
+
+ BOOL first_configure = !output->configured;
+ output->configured = YES;
+
+ /* Only reposition and notify if something actually changed (or first time). */
+ if (!first_configure
+ && output->effective_width == old_ew
+ && output->effective_height == old_eh)
+ return;
+
+ reposition_windows_for_output(output);
+ notify_screen_parameters_changed();
+}
+
+/* wl_output v4: human-readable connector name (e.g. "HDMI-A-1", "eDP-1"). */
+static void
+handle_name(void *data, struct wl_output *wl_output, const char *name)
+{
+ struct output *output = data;
+ if (output->name) free(output->name);
+ output->name = strdup(name ? name : "");
+ NSDebugFLLog(@"WaylandOutput", @"handle_name: output %u name='%s'",
+ output->server_output_id, output->name);
+}
+
+/* wl_output v4: human-readable description (e.g. "Samsung 27\" monitor"). */
+static void
+handle_description(void *data, struct wl_output *wl_output,
+ const char *description)
+{
+ struct output *output = data;
+ if (output->description) free(output->description);
+ output->description = strdup(description ? description : "");
+ NSDebugFLLog(@"WaylandOutput", @"handle_description: output %u desc='%s'",
+ output->server_output_id, output->description);
}
-const struct wl_output_listener output_listener
- = {handle_geometry, handle_mode, handle_done, handle_scale};
+const struct wl_output_listener output_listener = {
+ handle_geometry,
+ handle_mode,
+ handle_done,
+ handle_scale,
+ handle_name,
+ handle_description,
+};
diff --git a/Source/wayland/WaylandServer+Seat.m b/Source/wayland/WaylandServer+Seat.m
index d4cf73c2..892f645d 100644
--- a/Source/wayland/WaylandServer+Seat.m
+++ b/Source/wayland/WaylandServer+Seat.m
@@ -26,6 +26,7 @@
*/
#include "wayland/WaylandServer.h"
+#include
extern const struct wl_keyboard_listener keyboard_listener;
@@ -87,6 +88,17 @@
#endif
}
+/* wl_seat.name — sent by the compositor since wl_seat v2. We bound the seat
+ * at version 5 for frame/axis events, so this event now arrives at startup.
+ * An absent handler slot causes libwayland-client to abort with
+ * "listener function for opcode 1 of wl_seat is NULL". */
+static void
+seat_handle_name(void *data, struct wl_seat *seat, const char *name)
+{
+ NSDebugFLLog(@"WaylandIME", @"seat_handle_name: '%s'", name);
+}
+
const struct wl_seat_listener seat_listener = {
seat_handle_capabilities,
+ seat_handle_name,
};
diff --git a/Source/wayland/WaylandServer+Xdgshell.m b/Source/wayland/WaylandServer+Xdgshell.m
index 88314618..f55c2d88 100644
--- a/Source/wayland/WaylandServer+Xdgshell.m
+++ b/Source/wayland/WaylandServer+Xdgshell.m
@@ -29,6 +29,8 @@
#include
#include
#include
+#include
+#include
static void
xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface,
@@ -40,30 +42,16 @@
if (window->terminated == YES)
{
- // 'struct window' is defined in Headers/wayland/WaylandServer.
- //
- // window->terminated should only be true after
- // -[WaylandServer(WindowOps) termwindow:(int)win] sets this to true
- // after invoking -[WaylandServer destroyWindowShell:window] and
- // using wl_list_remove(&window->link);.
- //
- // We do not expect 'window' to be referenced again, since
- // -destroyWindowShell: invokes wl_display_dispatch_pending(...) and
- // wl_display_flush(...);. On the off chance it does, first, we may want
- // to patch every single invocation of get_window_with_id() that may
- // currently be ignoring the case where NULL may be returned, and
- // possibly crashing for that reason.
- //
- // But, a free here should on its own be fine as long as everyone
- // passes around the window ID and does not store a ptr to window itself.
- NSDebugLog(@"deleting window win=%d", window->window_id);
+ /* termwindow: already removed this window from wlconfig->window_list
+ * and set terminated = YES. We must NOT call wl_list_remove again —
+ * the node's prev/next pointers point to themselves after the first
+ * remove, and a second remove would corrupt the list.
+ * This configure arrived in-flight after termwindow; free the struct. */
+ NSDebugLog(@"deleting window win=%d (deferred free)", window->window_id);
free(window);
return;
}
- NSEvent *ev = nil;
- NSWindow *nswindow = GSWindowWithNumber(window->window_id);
-
// NSDebugLog(@"Acknowledging surface configure %p %d (window_id=%d)",
// xdg_surface, serial, window->window_id);
@@ -76,22 +64,11 @@
window->width, window->height)
:window->window_id];
}
-
- if (window->wlconfig->pointer.focus
- && window->wlconfig->pointer.focus->window_id == window->window_id)
- {
- ev = [NSEvent otherEventWithType:NSAppKitDefined
- location:NSZeroPoint
- modifierFlags:0
- timestamp:0
- windowNumber:(int) window->window_id
- context:GSCurrentContext()
- subtype:GSAppKitWindowFocusIn
- data1:0
- data2:0];
-
- [nswindow sendEvent:ev];
- }
+ /* Keyboard focus is now handled exclusively by keyboard_handle_enter/leave,
+ which correctly tracks what the compositor has granted. Sending
+ GSAppKitWindowFocusIn here based on pointer position was wrong: it could
+ steal key-window status from a modal dialog simply because the mouse
+ cursor happened to be over the reconfigured window. */
}
static void
@@ -145,15 +122,73 @@
NSDebugLog(@"[%d] xdg_popup_configure [%d,%d %dx%d]", window->window_id, x, y,
width, height);
+
+ /* The compositor reports the popup's actual position (in parent surface
+ * coords) and size. Sync window->pos_x/pos_y so GNUstep's NSWindow frame
+ * matches where the compositor actually placed the popup. Without this,
+ * locationForSubmenu: uses a stale frame and submenus appear offset. */
+ if (window->parent_id)
+ {
+ struct window *parent
+ = get_window_with_id(window->wlconfig, window->parent_id);
+ if (parent && window->output)
+ {
+ window->pos_x = parent->pos_x + x;
+ window->pos_y = parent->pos_y + y;
+
+ if (width > 0)
+ window->width = width;
+ if (height > 0)
+ window->height = height;
+
+ NSWindow *nswin = GSWindowWithNumber(window->window_id);
+ if (nswin)
+ {
+ NSEvent *ev = [NSEvent
+ otherEventWithType:NSAppKitDefined
+ location:NSMakePoint(0, 0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:window->window_id
+ context:GSCurrentContext()
+ subtype:GSAppKitWindowMoved
+ data1:window->pos_x
+ data2:WaylandToNS(window, window->pos_y)];
+ [nswin sendEvent:ev];
+ }
+ }
+ }
}
static void
xdg_popup_done(void *data, struct xdg_popup *xdg_popup)
{
struct window *window = data;
- window->terminated = YES;
+
+ /* Destroy the popup role (required by the protocol after popup_done),
+ * then destroy the xdg_surface and wl_surface in the correct order.
+ * NULL all pointers immediately so that destroySurfaceRole: (called
+ * when GNUstep eventually orders the window out) does not attempt a
+ * second destroy on already-freed Wayland proxy objects. A double
+ * destroy causes a Wayland protocol error, which disconnects the
+ * compositor session and closes every window including modal dialogs. */
xdg_popup_destroy(xdg_popup);
- wl_surface_destroy(window->surface);
+ window->popup = NULL;
+
+ if (window->xdg_surface)
+ {
+ xdg_surface_destroy(window->xdg_surface);
+ window->xdg_surface = NULL;
+ }
+
+ if (window->surface)
+ {
+ wl_surface_destroy(window->surface);
+ window->surface = NULL;
+ }
+
+ window->configured = NO;
+ window->terminated = YES;
}
static void
diff --git a/Source/wayland/WaylandServer.m b/Source/wayland/WaylandServer.m
index d3fd1b8e..7b92d6f9 100644
--- a/Source/wayland/WaylandServer.m
+++ b/Source/wayland/WaylandServer.m
@@ -46,10 +46,14 @@
#include
#include "wayland/WaylandServer.h"
+#include "wayland/WaylandOpenGL.h"
+#include "wayland/WaylandInputServer.h"
+#include "cairo/WaylandCairoShmSurface.h"
extern const struct wl_output_listener output_listener;
-
extern const struct wl_seat_listener seat_listener;
+extern const struct wl_data_device_listener data_device_listener;
+extern const struct zwp_text_input_v3_listener text_input_v3_listener;
static void
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
@@ -67,6 +71,10 @@
extern const struct xdg_popup_listener xdg_popup_listener;
+extern const struct zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener;
+
+static BOOL handlesWindowDecorations = NO;
+
static void
handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version)
@@ -111,10 +119,14 @@
memset(output, 0, sizeof(struct output));
output->wlconfig = wlconfig;
output->scale = 1;
+ /* Bind at version 4 to receive name/description events in addition to
+ * the v2 done/scale events. Fall back to whatever the compositor
+ * offers if it is older. */
+ uint32_t out_v = (version < 4) ? version : 4;
output->output
- = wl_registry_bind(registry, name, &wl_output_interface, 2);
+ = wl_registry_bind(registry, name, &wl_output_interface, out_v);
output->server_output_id = name;
- NSDebugLog(@"wayland: found output interface");
+ NSDebugLog(@"wayland: found output interface (version %u)", out_v);
wl_list_insert(wlconfig->output_list.prev, &output->link);
wlconfig->output_count++;
wl_output_add_listener(output->output, &output_listener, output);
@@ -122,16 +134,103 @@
else if (strcmp(interface, wl_seat_interface.name) == 0)
{
wlconfig->pointer.wlpointer = NULL;
- wlconfig->seat_version = version;
+ /* Bind at v5+ to receive wl_pointer.frame and axis-source/stop/discrete
+ * events needed for correct per-frame scroll accumulation. */
+ uint32_t seat_v = (version < 5) ? version : 5;
+ wlconfig->seat_version = seat_v;
wlconfig->seat
- = wl_registry_bind(wlconfig->registry, name, &wl_seat_interface, 1);
+ = wl_registry_bind(wlconfig->registry, name, &wl_seat_interface, seat_v);
NSDebugLog(@"wayland: found seat interface");
wl_seat_add_listener(wlconfig->seat, &seat_listener, wlconfig);
}
+ else if (strcmp(interface, wl_subcompositor_interface.name) == 0)
+ {
+ wlconfig->subcompositor
+ = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
+ NSDebugLog(@"wayland: found subcompositor interface");
+ }
+ else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0)
+ {
+ wlconfig->decoration_manager
+ = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1);
+ NSDebugLog(@"wayland: found xdg-decoration-manager interface");
+ }
+ else if (strcmp(interface, wl_data_device_manager_interface.name) == 0)
+ {
+ uint32_t v = (version < 3) ? version : 3;
+ wlconfig->data_device_manager_version = v;
+ wlconfig->data_device_manager
+ = wl_registry_bind(registry, name, &wl_data_device_manager_interface, v);
+ NSDebugLog(@"wayland: found wl_data_device_manager (version %u)", v);
+ }
+ else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0)
+ {
+ wlconfig->text_input_manager
+ = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1);
+ NSDebugLog(@"wayland: found zwp_text_input_manager_v3");
+ }
}
-static void handle_global_remove(void *data, struct wl_registry *registry,
- uint32_t name) {}
+static void
+handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
+{
+ WaylandConfig *wlconfig = data;
+
+ /* Find the output with this global name and remove it. */
+ struct output *output;
+ struct output *found = NULL;
+ wl_list_for_each(output, &wlconfig->output_list, link)
+ {
+ if (output->server_output_id == name)
+ {
+ found = output;
+ break;
+ }
+ }
+
+ if (found == NULL)
+ return;
+
+ NSDebugLog(@"wayland: global removed: output %u '%s'",
+ found->server_output_id, found->name ? found->name : "unknown");
+
+ /* Reassign any windows on the removed output to the first remaining output
+ * so that coordinate conversions (which dereference window->output) do not
+ * crash. */
+ struct output *fallback = NULL;
+ wl_list_for_each(output, &wlconfig->output_list, link)
+ {
+ if (output != found) { fallback = output; break; }
+ }
+
+ struct window *window;
+ wl_list_for_each(window, &wlconfig->window_list, link)
+ {
+ if (window->output == found)
+ {
+ window->output = fallback; /* may be NULL if last output removed */
+ NSDebugLog(@"wayland: window %d reassigned from removed output %u",
+ window->window_id, name);
+ }
+ }
+
+ /* Remove from the list and free resources. */
+ wl_list_remove(&found->link);
+ wlconfig->output_count--;
+
+ wl_output_destroy(found->output);
+ if (found->make) free(found->make);
+ if (found->model) free(found->model);
+ if (found->name) free(found->name);
+ if (found->description) free(found->description);
+ free(found);
+
+ /* Notify AppKit that the screen list changed. */
+ if (NSApp != nil)
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName: NSApplicationDidChangeScreenParametersNotification
+ object: NSApp];
+}
static const struct wl_registry_listener registry_listener = {
handle_global, handle_global_remove};
@@ -200,6 +299,32 @@ - (id)_initWaylandContext
wl_display_dispatch(wlconfig->display);
wl_display_roundtrip(wlconfig->display);
+ /* Create text_input once both text_input_manager and seat are available. */
+ if (wlconfig->text_input_manager && wlconfig->seat)
+ {
+ wlconfig->text_input = zwp_text_input_manager_v3_get_text_input(
+ wlconfig->text_input_manager, wlconfig->seat);
+ if (wlconfig->text_input)
+ {
+ zwp_text_input_v3_add_listener(wlconfig->text_input,
+ &text_input_v3_listener, wlconfig);
+ NSDebugLog(@"wayland: zwp_text_input_v3 created");
+ }
+ }
+
+ /* Get a data device now that both seat and data_device_manager are bound. */
+ if (wlconfig->data_device_manager && wlconfig->seat)
+ {
+ wlconfig->data_device = wl_data_device_manager_get_data_device(
+ wlconfig->data_device_manager, wlconfig->seat);
+ if (wlconfig->data_device)
+ {
+ wl_data_device_add_listener(wlconfig->data_device,
+ &data_device_listener, wlconfig);
+ NSDebugLog(@"wayland: wl_data_device created");
+ }
+ }
+
if (!wlconfig->compositor)
{
[NSException raise:NSWindowServerCommunicationException
@@ -217,6 +342,27 @@ - (id)_initWaylandContext
@"compositor must support the stable XDG Shell protocol"];
}
+ /* Determine decoration mode. Default: use SSD if the compositor supports it.
+ The user can override with GSBackHandlesWindowDecorations in defaults. */
+ NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
+ if ([defs objectForKey: @"GSBackHandlesWindowDecorations"])
+ {
+ handlesWindowDecorations
+ = [defs boolForKey: @"GSBackHandlesWindowDecorations"];
+ }
+ else
+ {
+ handlesWindowDecorations = (wlconfig->decoration_manager != NULL);
+ }
+ NSDebugLog(@"wayland: handlesWindowDecorations=%s (decoration_manager=%s)",
+ handlesWindowDecorations ? "YES" : "NO",
+ wlconfig->decoration_manager ? "available" : "not available");
+
+
+ inputServer = [[WaylandInputServer allocWithZone: [self zone]]
+ initWithDelegate: nil name: @"WaylandInput"];
+ [(WaylandInputServer *)inputServer setWlconfig: wlconfig];
+
return self;
}
@@ -264,12 +410,18 @@ - (id)initWithAttributes:(NSDictionary *)info
- (void)dealloc
{
NSDebugLog(@"Destroying Wayland Server");
+ if (wlconfig->decoration_manager)
+ {
+ zxdg_decoration_manager_v1_destroy(wlconfig->decoration_manager);
+ wlconfig->decoration_manager = NULL;
+ }
+ DESTROY(inputServer);
[super dealloc];
}
- (BOOL)handlesWindowDecorations
{
- return NO;
+ return handlesWindowDecorations;
}
- (void)restrictWindow:(int)win toImage:(NSImage *)image
@@ -279,16 +431,32 @@ - (void)restrictWindow:(int)win toImage:(NSImage *)image
- (NSRect)boundsForScreen:(int)screen
{
- NSDebugLog(@"boundsForScreen: %d", screen);
struct output *output;
+ /* Find the output whose server_output_id matches the requested screen. */
wl_list_for_each(output, &wlconfig->output_list, link)
{
- NSDebugLog(@"screen found: %dx%d", output->width, output->height);
- return NSMakeRect(0, 0, output->width, output->height);
+ if ((int)output->server_output_id == screen)
+ {
+ int ew = output->configured ? output->effective_width : output->width;
+ int eh = output->configured ? output->effective_height : output->height;
+ NSDebugLog(@"boundsForScreen: %d → logical=%dx%d (phys=%dx%d scale=%d)",
+ screen, ew, eh, output->width, output->height, output->scale);
+ return NSMakeRect(output->alloc_x, output->alloc_y, ew, eh);
+ }
+ }
+
+ /* Fallback: return the first output's bounds. */
+ wl_list_for_each(output, &wlconfig->output_list, link)
+ {
+ int ew = output->configured ? output->effective_width : output->width;
+ int eh = output->configured ? output->effective_height : output->height;
+ NSDebugLog(@"boundsForScreen: %d not found, using first output %dx%d",
+ screen, ew, eh);
+ return NSMakeRect(0, 0, ew, eh);
}
- NSDebugLog(@"can't find screen");
+ NSDebugLog(@"boundsForScreen: no outputs available");
return NSZeroRect;
}
@@ -330,8 +498,8 @@ - (void *)serverDevice
- (void *)windowDevice:(int)win
{
- NSDebugLog(@"windowDevice");
- return NULL;
+ NSDebugLog(@"windowDevice: %d", win);
+ return get_window_with_id(wlconfig, win);
}
- (void)beep
@@ -339,6 +507,74 @@ - (void)beep
NSDebugLog(@"beep");
}
+- glContextClass
+{
+ return [WaylandGLContext class];
+}
+
+- glPixelFormatClass
+{
+ return [WaylandGLPixelFormat class];
+}
+
+@end
+
+@implementation WaylandServer (InputMethod)
+
+- (NSString *) inputMethodStyle
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer inputMethodStyle] : nil;
+}
+
+- (NSString *) fontSize: (int *)size
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer fontSize: size] : nil;
+}
+
+- (BOOL) clientWindowRect: (NSRect *)rect
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer clientWindowRect: rect] : NO;
+}
+
+- (BOOL) statusArea: (NSRect *)rect
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer statusArea: rect] : NO;
+}
+
+- (BOOL) preeditArea: (NSRect *)rect
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer preeditArea: rect] : NO;
+}
+
+- (BOOL) preeditSpot: (NSPoint *)p
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer preeditSpot: p] : NO;
+}
+
+- (BOOL) setStatusArea: (NSRect *)rect
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer setStatusArea: rect] : NO;
+}
+
+- (BOOL) setPreeditArea: (NSRect *)rect
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer setPreeditArea: rect] : NO;
+}
+
+- (BOOL) setPreeditSpot: (NSPoint *)p
+{
+ return inputServer
+ ? [(WaylandInputServer *) inputServer setPreeditSpot: p] : NO;
+}
+
@end
@implementation
@@ -404,6 +640,8 @@ - (int)window:(NSRect)
window->moving = NO;
window->resizing = NO;
window->ignoreMouse = NO;
+ window->usesOpenGL = NO;
+ window->global_pos_known = NO;
// FIXME is this needed?
if (window->pos_x < 0)
@@ -448,6 +686,14 @@ - (void)termwindow:(int)win
NSDebugLog(@"termwindow: win=%d", win);
struct window *window = get_window_with_id(wlconfig, win);
+ /* Clear any stale focus references to avoid dangling pointers. */
+ if (wlconfig->keyboard_focus == window)
+ wlconfig->keyboard_focus = NULL;
+ if (wlconfig->pointer.focus == window)
+ wlconfig->pointer.focus = NULL;
+ if (wlconfig->pointer.captured == window)
+ wlconfig->pointer.captured = NULL;
+
[self destroyWindowShell:window];
// FIXME should wait for buffer release before detroying it
//
@@ -556,7 +802,27 @@ - (void)orderwindow:(int)op:(int)otherWin:(int)win
- (void)movewindow:(NSPoint)loc:(int)win
{
- NSDebugLog(@"movewindow");
+ NSDebugLog(@"[%d] movewindow: %f,%f", win, loc.x, loc.y);
+ struct window *window = get_window_with_id(wlconfig, win);
+ if (!window)
+ return;
+
+ window->pos_x = loc.x;
+ window->pos_y = NSToWayland(window, (int) loc.y);
+
+ /* Layer-shell surfaces are positioned via top/left margins from their
+ anchor point (ANCHOR_TOP | ANCHOR_LEFT), so we can reposition them
+ by updating the margins and committing. XDG toplevels are positioned
+ by the compositor and cannot be moved from the client side. */
+ if (window->layer_surface)
+ {
+ zwlr_layer_surface_v1_set_margin(window->layer_surface,
+ (int32_t) window->pos_y,
+ 0, 0,
+ (int32_t) window->pos_x);
+ wl_surface_commit(window->surface);
+ wl_display_flush(wlconfig->display);
+ }
}
- (NSRect) _OSFrameToWFrame: (NSRect)o for: (void*)win
@@ -669,8 +935,35 @@ - (void)setParentWindow:(int)parentWin forChildWindow:(int)childWin
NSDebugLog(@"setParentWindow: parent=%d child=%d", parentWin, childWin);
struct window *parent = get_window_with_id(wlconfig, parentWin);
struct window *child = get_window_with_id(wlconfig, childWin);
+ if (!parent || !child)
+ {
+ return;
+ }
+
+ if (child->level == NSPopUpMenuWindowLevel)
+ {
+ /* NSPopUpMenuWindowLevel is a NOOP in createSurfaceShell; create the
+ * xdg_popup here so the compositor handles grab and dismiss. */
+ [self createPopupShell:child withParentShell:parent];
+ return;
+ }
+
+ if (child->level == NSSubmenuWindowLevel)
+ {
+ /* Submenus are created by createSubMenuShell (layer shell preferred).
+ * Only fall back to xdg_popup if no role has been assigned yet. */
+ if (![self windowSurfaceHasRole:child])
+ [self createPopupShell:child withParentShell:parent];
+ return;
+ }
- [self createPopupShell:child withParentShell:parent];
+ /* Panels, dialogs, and other transient toplevels: record the parent and
+ * call xdg_toplevel_set_parent. Never use xdg_popup here — xdg_popup
+ * auto-dismisses when pointer focus leaves the surface, which closes
+ * dialogs as soon as the mouse moves to another window. */
+ child->parent_id = parentWin;
+ if (child->toplevel && parent->toplevel)
+ xdg_toplevel_set_parent(child->toplevel, parent->toplevel);
}
- (void)setwindowlevel:(int)level:(int)win
@@ -714,6 +1007,16 @@ - (void)flushwindowrect:(NSRect)rect:(int)win
NSDebugLog(@"[%d] flushwindowrect: %f,%f %fx%f", win, NSMinX(rect),
NSMinY(rect), NSWidth(rect), NSHeight(rect));
struct window *window = get_window_with_id(wlconfig, win);
+ if (window == NULL)
+ {
+ return;
+ }
+
+ if (window->usesOpenGL)
+ {
+ NSDebugLog(@"[%d] skipping cairo flush for OpenGL-backed window", win);
+ return;
+ }
[[GSCurrentContext() class] handleExposeRect:rect forDriver:window->wcs];
}
@@ -783,7 +1086,59 @@ - (void)createSurfaceShell:(struct window *)window
break;
case NSPopUpMenuWindowLevel:
NSDebugLog(@"[%d] NSPopUpMenuWindowLevel", win);
- [self createPopup:window];
+ /* Use layer shell so the menu can extend beyond the parent window's
+ * surface without losing pointer events. xdg_popup clips event
+ * delivery to the parent surface bounds on many compositors, which
+ * breaks tracking when the menu extends outside the parent window.
+ * NSPopUpButton popups go through setParentWindow:forChildWindow:
+ * before orderwindow and already have a surface role by the time
+ * createSurfaceShell is reached, so this path is only taken for
+ * right-click context menus (displayTransient).
+ *
+ * Before creating the surface, translate window->pos_x/y from
+ * GNUstep's assumed coordinates into accurate output-relative
+ * coordinates by adding the delta between the key window's inferred
+ * global origin (saved_pos_x/y, tracked via cursor enter events) and
+ * GNUstep's assumed origin (pos_x/y). Then notify GNUstep of the
+ * corrected frame so that locationForSubmenu: and other screen-
+ * coordinate queries work correctly for any submenus. */
+ if (wlconfig->layer_shell)
+ {
+ NSWindow *keyWin = [NSApp keyWindow];
+ if (keyWin && (int)[keyWin windowNumber] != win)
+ {
+ struct window *kwin =
+ get_window_with_id(wlconfig, (int)[keyWin windowNumber]);
+ if (kwin && kwin->global_pos_known)
+ {
+ float dx = kwin->saved_pos_x - kwin->pos_x;
+ float dy = kwin->saved_pos_y - kwin->pos_y;
+ window->pos_x += dx;
+ window->pos_y += dy;
+ NSWindow *nswin = GSWindowWithNumber(win);
+ if (nswin)
+ {
+ NSEvent *ev =
+ [NSEvent otherEventWithType:NSAppKitDefined
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:win
+ context:GSCurrentContext()
+ subtype:GSAppKitWindowMoved
+ data1:window->pos_x
+ data2:WaylandToNS(window,
+ window->pos_y)];
+ [nswin sendEvent:ev];
+ }
+ }
+ }
+ [self createLayerShell:window
+ withLayerType:ZWLR_LAYER_SHELL_V1_LAYER_TOP
+ withNamespace:@"gnustep-popup"];
+ }
+ else
+ [self createTopLevel:window];
break;
case NSScreenSaverWindowLevel:
NSDebugLog(@"[%d] NSScreenSaverWindowLevel", win);
@@ -836,7 +1191,10 @@ - (void)createTopLevel:(struct window *)window
return;
}
- wl_surface_set_user_data(window->surface, window);
+ window->surface_binding.window = window;
+ window->surface_binding.offset_x = 0.0f;
+ window->surface_binding.offset_y = 0.0f;
+ wl_surface_set_user_data(window->surface, &window->surface_binding);
if (window->xdg_surface == NULL)
{
window->xdg_surface
@@ -848,6 +1206,26 @@ - (void)createTopLevel:(struct window *)window
xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener,
window);
+ /* Apply transient-parent relationship.
+ *
+ * Priority 1: explicit parent from setParentWindow: (sheets, child panels).
+ * Priority 2: implicit parent for modal panels — set to the keyboard-focused
+ * window so the Ambrosia compositor can protect the dialog from focus
+ * steals even when no explicit addChildWindow: call was made (e.g. the
+ * standalone [NSOpenPanel runModal] case). */
+ {
+ struct window *parent = NULL;
+ if (window->parent_id)
+ parent = get_window_with_id(wlconfig, window->parent_id);
+ else if (window->level == NSModalPanelWindowLevel
+ && wlconfig->keyboard_focus != NULL
+ && wlconfig->keyboard_focus != window)
+ parent = wlconfig->keyboard_focus;
+
+ if (parent && parent->toplevel)
+ xdg_toplevel_set_parent(window->toplevel, parent->toplevel);
+ }
+
xdg_surface_set_window_geometry(window->xdg_surface, 0, 0, window->width,
window->height);
}
@@ -883,7 +1261,10 @@ - (void)createLayerShell:(struct window *)window
}
window->surface = wl_compositor_create_surface(wlconfig->compositor);
- wl_surface_set_user_data(window->surface, window);
+ window->surface_binding.window = window;
+ window->surface_binding.offset_x = 0.0f;
+ window->surface_binding.offset_y = 0.0f;
+ wl_surface_set_user_data(window->surface, &window->surface_binding);
const char *cString = [namespace UTF8String];
window->layer_surface
@@ -952,40 +1333,33 @@ - (void)createSubMenuShell:(struct window *)window
if ([self windowSurfaceHasRole:window])
{
- // if the role is already assigned skip
return;
}
+ /* Use layer shell so the submenu can extend beyond any parent surface
+ * without losing pointer events. xdg_popup clips event delivery to
+ * the parent surface bounds, which breaks tracking when the submenu
+ * extends to the right or below the parent menu's surface. */
if (wlconfig->layer_shell)
{
- // the preferred way to create a submenu is to use an overlay
[self createLayerShell:window
- withLayerType:ZWLR_LAYER_SHELL_V1_LAYER_TOP
- withNamespace:@"gnustep-submenu"];
+ withLayerType:ZWLR_LAYER_SHELL_V1_LAYER_TOP
+ withNamespace:@"gnustep-submenu"];
return;
}
- else
- {
- NSDebugLog(@"layer shell not supported, fallback to xdg popup");
- }
- // if the layer shell is not available then we use the xdg popup
- // for that we need a parent toplevel window
+ /* No layer shell: fall back to xdg_popup under the nearest parent
+ * menu surface. */
+ NSDebugLog(@"layer shell not supported, fallback to xdg popup");
struct window *rootwindow = window;
struct window *parentwindow = rootwindow;
- while (rootwindow = [self getSuperMenuWindow:parentwindow])
+ while ((rootwindow = [self getSuperMenuWindow:parentwindow]))
{
parentwindow = rootwindow;
}
if (!parentwindow)
- {
- return;
- }
+ return;
NSDebugLog(@"new popup: %d parent id: %d", win, parentwindow->window_id);
- NSDebugLog(@"parent: %d [%f,%f %fx%f]", parentwindow->window_id,
- parentwindow->pos_x, parentwindow->pos_y, parentwindow->width,
- parentwindow->height);
-
[self createPopupShell:window withParentShell:parentwindow];
}
@@ -999,9 +1373,10 @@ - (void)createPopupShell:(struct window *)child
{
NSDebugLog(@"createPopupShell");
- if (parent->toplevel == NULL && parent->layer_surface == NULL)
+ if (parent->toplevel == NULL && parent->layer_surface == NULL
+ && parent->popup == NULL)
{
- NSDebugLog(@"parent surface %d is not toplevel", parent->window_id);
+ NSDebugLog(@"parent surface %d has no surface role", parent->window_id);
return;
}
if ([self windowSurfaceHasRole:child])
@@ -1009,8 +1384,13 @@ - (void)createPopupShell:(struct window *)child
[self destroySurfaceRole:child];
}
+ child->parent_id = parent->window_id;
+
child->surface = wl_compositor_create_surface(wlconfig->compositor);
- wl_surface_set_user_data(child->surface, child);
+ child->surface_binding.window = child;
+ child->surface_binding.offset_x = 0.0f;
+ child->surface_binding.offset_y = 0.0f;
+ wl_surface_set_user_data(child->surface, &child->surface_binding);
NSWindow *nswin = (GSWindowWithNumber(child->window_id));
CGFloat x = nswin.frame.origin.x;
@@ -1036,6 +1416,12 @@ - (void)createPopupShell:(struct window *)child
child->popup = xdg_surface_get_popup(child->xdg_surface, parent->xdg_surface,
positioner);
+ /* Grab pointer/keyboard so the compositor auto-dismisses the popup on
+ * outside clicks and delivers events to it. Only grab when we have a
+ * valid event serial (i.e. the popup was triggered by user input). */
+ if (wlconfig->event_serial)
+ xdg_popup_grab(child->popup, wlconfig->seat, wlconfig->event_serial);
+
if (parent->layer_surface)
{
zwlr_layer_surface_v1_get_popup(parent->layer_surface, child->popup);
@@ -1082,11 +1468,22 @@ - (void)destroySurfaceRole:(struct window *)window
xdg_surface_destroy(window->xdg_surface);
window->xdg_surface = NULL;
}
+ /* Destroy the base wl_surface last. Role objects (xdg_surface,
+ * layer_surface) must be destroyed first per the Wayland protocol.
+ * The cairo SHM surface (window->wcs) is an ObjC object that holds a
+ * reference to the wl_surface via pbuffer->owner_surface; clear that
+ * pointer so the release callback does not write to a freed proxy. */
if (window->wcs)
{
- // [window->wcs destroySurface];
+ [(WaylandCairoShmSurface *)window->wcs clearOwnerSurface];
}
- window->configured = NO;
+ if (window->surface)
+ {
+ wl_surface_destroy(window->surface);
+ window->surface = NULL;
+ }
+ window->configured = NO;
+ window->buffer_needs_attach = YES;
}
- (void)destroyWindowShell:(struct window *)window
diff --git a/Source/wayland/protocols/xdg-decoration-unstable-v1.xml b/Source/wayland/protocols/xdg-decoration-unstable-v1.xml
new file mode 100644
index 00000000..023589ad
--- /dev/null
+++ b/Source/wayland/protocols/xdg-decoration-unstable-v1.xml
@@ -0,0 +1,176 @@
+
+
+
+ Copyright © 2018 Simon Ser
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+
+
+
+ This interface allows a compositor to announce support for server-side
+ decorations.
+
+ A window decoration is a set of window controls as deemed appropriate by
+ the party managing them, such as user interface components used to move,
+ resize and change a window's state.
+
+ A client can use this protocol to request being decorated by a supporting
+ compositor.
+
+ If compositor and client do not negotiate the use of a server-side
+ decoration using this protocol, clients continue to self-decorate as they
+ see fit.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+
+
+
+
+ Destroy the decoration manager. This doesn't destroy objects created
+ with the manager.
+
+
+
+
+
+ Create a new decoration object associated with the given toplevel.
+
+ For objects of version 1, creating an xdg_toplevel_decoration from an
+ xdg_toplevel which has a buffer attached or committed is a client
+ error, and any attempts by a client to attach or manipulate a buffer
+ prior to the first xdg_toplevel_decoration.configure event must also be
+ treated as errors.
+
+ For objects of version 2 or newer, creating an xdg_toplevel_decoration
+ from an xdg_toplevel which has a buffer attached or committed is
+ allowed. The initial decoration mode of the surface if a buffer is
+ already attached depends on whether a xdg_toplevel_decoration object
+ has been associated with the surface or not prior to this request.
+
+ If an xdg_toplevel_decoration was associated with the surface, then
+ destroyed without a surface commit, the previous decoration mode is
+ retained.
+
+ If no xdg_toplevel_decoration was associated with the surface prior to
+ this request, or if a surface commit has been performed after a previous
+ xdg_toplevel_decoration object associated with the surface was
+ destroyed, the decoration mode is assumed to be client-side.
+
+
+
+
+
+
+
+
+ The decoration object allows the compositor to toggle server-side window
+ decorations for a toplevel surface. The client can request to switch to
+ another mode.
+
+ The xdg_toplevel_decoration object must be destroyed before its
+ xdg_toplevel.
+
+
+
+
+
+
+
+
+
+
+
+ Switch back to a mode without any server-side decorations at the next
+ commit, unless a new xdg_toplevel_decoration is created for the surface
+ first.
+
+
+
+
+
+ These values describe window decoration modes.
+
+
+
+
+
+
+
+ Set the toplevel surface decoration mode. This informs the compositor
+ that the client prefers the provided decoration mode.
+
+ After requesting a decoration mode, the compositor will respond by
+ emitting an xdg_surface.configure event. The client should then update
+ its content, drawing it without decorations if the received mode is
+ server-side decorations. The client must also acknowledge the configure
+ when committing the new content (see xdg_surface.ack_configure).
+
+ The compositor can decide not to use the client's mode and enforce a
+ different mode instead.
+
+ Clients whose decoration mode depend on the xdg_toplevel state may send
+ a set_mode request in response to an xdg_surface.configure event and wait
+ for the next xdg_surface.configure event to prevent unwanted state.
+ Such clients are responsible for preventing configure loops and must
+ make sure not to send multiple successive set_mode requests with the
+ same decoration mode.
+
+ If an invalid mode is supplied by the client, the invalid_mode protocol
+ error is raised by the compositor.
+
+
+
+
+
+
+ Unset the toplevel surface decoration mode. This informs the compositor
+ that the client doesn't prefer a particular decoration mode.
+
+ This request has the same semantics as set_mode.
+
+
+
+
+
+ The configure event configures the effective decoration mode. The
+ configured state should not be applied immediately. Clients must send an
+ ack_configure in response to this event. See xdg_surface.configure and
+ xdg_surface.ack_configure for details.
+
+ A configure event can be sent at any time. The specified mode must be
+ obeyed by the client.
+
+
+
+
+
diff --git a/Source/wayland/text-input-unstable-v3-protocol.c b/Source/wayland/text-input-unstable-v3-protocol.c
new file mode 100644
index 00000000..c723e407
--- /dev/null
+++ b/Source/wayland/text-input-unstable-v3-protocol.c
@@ -0,0 +1,93 @@
+/* Generated by wayland-scanner 1.24.0 */
+
+/*
+ * Copyright © 2012, 2013 Intel Corporation
+ * Copyright © 2015, 2016 Jan Arne Petersen
+ * Copyright © 2017, 2018 Red Hat, Inc.
+ * Copyright © 2018 Purism SPC
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+#include
+#include
+#include
+#include "wayland-util.h"
+
+extern const struct wl_interface wl_seat_interface;
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface zwp_text_input_v3_interface;
+
+static const struct wl_interface *text_input_unstable_v3_types[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &wl_surface_interface,
+ &wl_surface_interface,
+ &zwp_text_input_v3_interface,
+ &wl_seat_interface,
+};
+
+static const struct wl_message zwp_text_input_v3_requests[] = {
+ { "destroy", "", text_input_unstable_v3_types + 0 },
+ { "enable", "", text_input_unstable_v3_types + 0 },
+ { "disable", "", text_input_unstable_v3_types + 0 },
+ { "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 },
+ { "set_text_change_cause", "u", text_input_unstable_v3_types + 0 },
+ { "set_content_type", "uu", text_input_unstable_v3_types + 0 },
+ { "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 },
+ { "commit", "", text_input_unstable_v3_types + 0 },
+ { "set_available_actions", "2a", text_input_unstable_v3_types + 0 },
+ { "show_input_panel", "2", text_input_unstable_v3_types + 0 },
+ { "hide_input_panel", "2", text_input_unstable_v3_types + 0 },
+};
+
+static const struct wl_message zwp_text_input_v3_events[] = {
+ { "enter", "o", text_input_unstable_v3_types + 4 },
+ { "leave", "o", text_input_unstable_v3_types + 5 },
+ { "preedit_string", "?sii", text_input_unstable_v3_types + 0 },
+ { "commit_string", "?s", text_input_unstable_v3_types + 0 },
+ { "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 },
+ { "done", "u", text_input_unstable_v3_types + 0 },
+ { "action", "2uu", text_input_unstable_v3_types + 0 },
+ { "language", "2s", text_input_unstable_v3_types + 0 },
+ { "preedit_hint", "2uuu", text_input_unstable_v3_types + 0 },
+};
+
+WL_EXPORT const struct wl_interface zwp_text_input_v3_interface = {
+ "zwp_text_input_v3", 2,
+ 11, zwp_text_input_v3_requests,
+ 9, zwp_text_input_v3_events,
+};
+
+static const struct wl_message zwp_text_input_manager_v3_requests[] = {
+ { "destroy", "", text_input_unstable_v3_types + 0 },
+ { "get_text_input", "no", text_input_unstable_v3_types + 6 },
+};
+
+WL_EXPORT const struct wl_interface zwp_text_input_manager_v3_interface = {
+ "zwp_text_input_manager_v3", 2,
+ 2, zwp_text_input_manager_v3_requests,
+ 0, NULL,
+};
+
diff --git a/Source/wayland/xdg-decoration-unstable-v1-protocol.c b/Source/wayland/xdg-decoration-unstable-v1-protocol.c
new file mode 100644
index 00000000..cfac2d1b
--- /dev/null
+++ b/Source/wayland/xdg-decoration-unstable-v1-protocol.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2018 Simon Ser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include
+#include
+#include
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface xdg_toplevel_interface;
+extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
+
+static const struct wl_interface *xdg_decoration_unstable_v1_types[] = {
+ NULL,
+ &zxdg_toplevel_decoration_v1_interface,
+ &xdg_toplevel_interface,
+};
+
+static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
+ { "destroy", "", xdg_decoration_unstable_v1_types + 0 },
+ { "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 },
+};
+
+WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
+ "zxdg_decoration_manager_v1", 2,
+ 2, zxdg_decoration_manager_v1_requests,
+ 0, NULL,
+};
+
+static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
+ { "destroy", "", xdg_decoration_unstable_v1_types + 0 },
+ { "set_mode", "u", xdg_decoration_unstable_v1_types + 0 },
+ { "unset_mode", "", xdg_decoration_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
+ { "configure", "u", xdg_decoration_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
+ "zxdg_toplevel_decoration_v1", 2,
+ 3, zxdg_toplevel_decoration_v1_requests,
+ 1, zxdg_toplevel_decoration_v1_events,
+};
diff --git a/config.h.in b/config.h.in
index 18ed752c..56f572b2 100644
--- a/config.h.in
+++ b/config.h.in
@@ -44,25 +44,25 @@
/* Define to 1 if you have the header file. */
#undef HAVE_INTTYPES_H
-/* Define to 1 if you have the `wayland-client' library (-lwayland-client). */
+/* Define to 1 if you have the 'wayland-client' library (-lwayland-client). */
#undef HAVE_LIBWAYLAND_CLIENT
-/* Define to 1 if you have the `Xext' library (-lXext). */
+/* Define to 1 if you have the 'Xext' library (-lXext). */
#undef HAVE_LIBXEXT
-/* Define to 1 if you have the `Xft' library (-lXft). */
+/* Define to 1 if you have the 'Xft' library (-lXft). */
#undef HAVE_LIBXFT
-/* Define to 1 if you have the `xkbcommon' library (-lxkbcommon). */
+/* Define to 1 if you have the 'xkbcommon' library (-lxkbcommon). */
#undef HAVE_LIBXKBCOMMON
-/* Define to 1 if you have the `Xmu' library (-lXmu). */
+/* Define to 1 if you have the 'Xmu' library (-lXmu). */
#undef HAVE_LIBXMU
-/* Define to 1 if you have the `Xt' library (-lXt). */
+/* Define to 1 if you have the 'Xt' library (-lXt). */
#undef HAVE_LIBXT
-/* Define to 1 if you have the `shmctl' function. */
+/* Define to 1 if you have the 'shmctl' function. */
#undef HAVE_SHMCTL
/* Define to 1 if you have the header file. */
@@ -80,7 +80,7 @@
/* Define to 1 if you have the header file. */
#undef HAVE_STRING_H
-/* Define to 1 if you have the `syslog' function. */
+/* Define to 1 if you have the 'syslog' function. */
#undef HAVE_SYSLOG
/* Define to 1 if you have the header file. */
@@ -95,7 +95,7 @@
/* Define to 1 if you have the header file. */
#undef HAVE_UNISTD_H
-/* Define to 1 if you have the `usleep' function. */
+/* Define to 1 if you have the 'usleep' function. */
#undef HAVE_USLEEP
/* Define if you have XftDrawStringUtf8 */
@@ -141,7 +141,7 @@
/* Define to enable Xshape support */
#undef HAVE_XSHAPE
-/* Define to 1 if you have the `Xutf8LookupString' function. */
+/* Define to 1 if you have the 'Xutf8LookupString' function. */
#undef HAVE_XUTF8LOOKUPSTRING
/* Define to the address where bug reports for this package should be sent. */
@@ -162,7 +162,7 @@
/* Define to the version of this package. */
#undef PACKAGE_VERSION
-/* Define to 1 if all of the C90 standard headers exist (not just the ones
+/* Define to 1 if all of the C89 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */
#undef STDC_HEADERS
diff --git a/configure b/configure
index 555301dd..09052453 100755
--- a/configure
+++ b/configure
@@ -1,9 +1,9 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.73.
+# Generated by GNU Autoconf 2.72.
#
#
-# Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation,
+# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation,
# Inc.
#
#
@@ -135,7 +135,7 @@ case $# in # ((
esac
# Admittedly, this is quite paranoid, since all the known shells bail
# out after a failed 'exec'.
-printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2
+printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2
exit 255
fi
# We don't want this to propagate to other subprocesses.
@@ -262,7 +262,7 @@ case $# in # ((
esac
# Admittedly, this is quite paranoid, since all the known shells bail
# out after a failed 'exec'.
-printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2
+printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2
exit 255
fi
@@ -1554,9 +1554,9 @@ test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
configure
-generated by GNU Autoconf 2.73
+generated by GNU Autoconf 2.72
-Copyright (C) 2026 Free Software Foundation, Inc.
+Copyright (C) 2023 Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
_ACEOF
@@ -1596,7 +1596,7 @@ printf '%s\n' "$ac_try_echo"; } >&5
then :
ac_retval=0
else case e in #(
- e) printf '%s\n' "$as_me: failed program was:" >&5
+ e) printf "%s\n" "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1 ;;
@@ -1635,7 +1635,7 @@ printf '%s\n' "$ac_try_echo"; } >&5
then :
ac_retval=0
else case e in #(
- e) printf '%s\n' "$as_me: failed program was:" >&5
+ e) printf "%s\n" "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1 ;;
@@ -1713,7 +1713,7 @@ printf '%s\n' "$ac_try_echo"; } >&5
then :
ac_retval=0
else case e in #(
- e) printf '%s\n' "$as_me: failed program was:" >&5
+ e) printf "%s\n" "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1 ;;
@@ -1817,7 +1817,7 @@ This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by $as_me, which was
-generated by GNU Autoconf 2.73. Invocation command line was
+generated by GNU Autoconf 2.72. Invocation command line was
$ $0$ac_configure_args_raw
@@ -2082,8 +2082,8 @@ esac
printf '%s\n' "$as_me: loading site script $ac_site_file" >&6;}
sed 's/^/| /' "$ac_site_file" >&5
. "$ac_site_file" \
- || { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "failed to load site script $ac_site_file
See 'config.log' for more details" "$LINENO" 5; }
fi
@@ -2642,12 +2642,12 @@ for ac_var in $ac_precious_vars; do
eval ac_new_val=\$ac_env_${ac_var}_value
case $ac_old_set,$ac_new_set in
set,)
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5
-printf '%s\n' "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5
+printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;}
ac_cache_corrupted=: ;;
,set)
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5
-printf '%s\n' "$as_me: error: '$ac_var' was not set in the previous run" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5
+printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;}
ac_cache_corrupted=: ;;
,);;
*)
@@ -2662,18 +2662,18 @@ printf '%s\n' "$as_me: error: '$ac_var' was not set in the previous run" >&2;}
ac_new_val_w="$ac_new_val_w $ac_val"
done
if test "$ac_old_val_w" != "$ac_new_val_w"; then
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5
-printf '%s\n' "$as_me: error: '$ac_var' has changed since the previous run:" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5
+printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;}
ac_cache_corrupted=:
else
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5
-printf '%s\n' "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5
+printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;}
eval $ac_var=\$ac_old_val
fi
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5
-printf '%s\n' "$as_me: former value: '$ac_old_val'" >&2;}
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5
-printf '%s\n' "$as_me: current value: '$ac_new_val'" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5
+printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5
+printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;}
fi;;
esac
# Pass precious variables to config.status.
@@ -2689,10 +2689,10 @@ printf '%s\n' "$as_me: current value: '$ac_new_val'" >&2;}
fi
done
if $ac_cache_corrupted; then
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
-printf '%s\n' "$as_me: error: changes in the environment can compromise the build" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;}
as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file'
and start over" "$LINENO" 5
fi
@@ -3360,8 +3360,8 @@ fi
fi
-test -z "$CC" && { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "no acceptable C compiler found in \$PATH
See 'config.log' for more details" "$LINENO" 5; }
@@ -3478,13 +3478,13 @@ printf '%s\n' "no" >&6; }
printf '%s\n' "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
-{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error 77 "C compiler cannot create executables
See 'config.log' for more details" "$LINENO" 5; }
else case e in #(
- e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-printf '%s\n' "yes" >&6; } ;;
+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; } ;;
esac
fi
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
@@ -3523,8 +3523,8 @@ for ac_file in conftest.exe conftest conftest.*; do
esac
done
else case e in #(
- e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of executables: cannot compile and link
See 'config.log' for more details" "$LINENO" 5; } ;;
esac
@@ -3584,8 +3584,8 @@ printf '%s\n' "$ac_try_echo"; } >&5
if test "$cross_compiling" = maybe; then
cross_compiling=yes
else
- { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error 77 "cannot run C compiled programs.
If you meant to cross compile, use '--host'.
See 'config.log' for more details" "$LINENO" 5; }
@@ -3637,11 +3637,11 @@ then :
esac
done
else case e in #(
- e) printf '%s\n' "$as_me: failed program was:" >&5
+ e) printf "%s\n" "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
-{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of object files: cannot compile
See 'config.log' for more details" "$LINENO" 5; } ;;
esac
@@ -3862,16 +3862,16 @@ fi
if test "x$ac_cv_prog_cc_c11" = xno
then :
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
-printf '%s\n' "unsupported" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
else case e in #(
e) if test "x$ac_cv_prog_cc_c11" = x
then :
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
-printf '%s\n' "none needed" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
else case e in #(
- e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5
-printf '%s\n' "$ac_cv_prog_cc_c11" >&6; }
+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5
+printf "%s\n" "$ac_cv_prog_cc_c11" >&6; }
CC="$CC $ac_cv_prog_cc_c11" ;;
esac
fi
@@ -3911,16 +3911,16 @@ fi
if test "x$ac_cv_prog_cc_c99" = xno
then :
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
-printf '%s\n' "unsupported" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
else case e in #(
e) if test "x$ac_cv_prog_cc_c99" = x
then :
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
-printf '%s\n' "none needed" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
else case e in #(
- e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
-printf '%s\n' "$ac_cv_prog_cc_c99" >&6; }
+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
+printf "%s\n" "$ac_cv_prog_cc_c99" >&6; }
CC="$CC $ac_cv_prog_cc_c99" ;;
esac
fi
@@ -3960,16 +3960,16 @@ fi
if test "x$ac_cv_prog_cc_c89" = xno
then :
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
-printf '%s\n' "unsupported" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
else case e in #(
e) if test "x$ac_cv_prog_cc_c89" = x
then :
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
-printf '%s\n' "none needed" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
else case e in #(
- e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
-printf '%s\n' "$ac_cv_prog_cc_c89" >&6; }
+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+printf "%s\n" "$ac_cv_prog_cc_c89" >&6; }
CC="$CC $ac_cv_prog_cc_c89" ;;
esac
fi
@@ -4110,8 +4110,8 @@ if $ac_preproc_ok
then :
else case e in #(
- e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
See 'config.log' for more details" "$LINENO" 5; } ;;
esac
@@ -4426,7 +4426,7 @@ then :
ac_x_libraries=
else case e in #(
e) LIBS=$ac_save_LIBS
-for ac_dir in `printf '%s\n' "$ac_x_includes $ac_x_header_dirs" | sed s/include/lib/g`
+for ac_dir in `printf "%s\n" "$ac_x_includes $ac_x_header_dirs" | sed s/include/lib/g`
do
# Don't even attempt the hair of trying to link an X program!
for ac_extension in a so sl dylib la dll; do
@@ -4534,8 +4534,8 @@ then :
printf '%s\n' "yes" >&6; }
X_LIBS="$X_LIBS -R $x_libraries"
else case e in #(
- e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: neither works" >&5
-printf '%s\n' "neither works" >&6; } ;;
+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: neither works" >&5
+printf "%s\n" "neither works" >&6; } ;;
esac
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
@@ -4584,7 +4584,7 @@ if ac_fn_c_try_link "$LINENO"
then :
else case e in #(
- e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dnet_ntoa in -ldnet" >&5
+ e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dnet_ntoa in -ldnet" >&5
printf %s "checking for dnet_ntoa in -ldnet... " >&6; }
if test ${ac_cv_lib_dnet_dnet_ntoa+y}
then :
@@ -5305,10 +5305,10 @@ Alternatively, you may set the environment variables XEXT_CFLAGS
and XEXT_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf '%s\n' "no" >&6; }
- { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
@@ -5441,10 +5441,10 @@ Alternatively, you may set the environment variables XT_CFLAGS
and XT_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf '%s\n' "no" >&6; }
- { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
@@ -5577,10 +5577,10 @@ Alternatively, you may set the environment variables XMU_CFLAGS
and XMU_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf '%s\n' "no" >&6; }
- { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
@@ -5864,7 +5864,7 @@ fi
# Old X11 support
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for X11 function prototypes" >&5
printf %s "checking for X11 function prototypes... " >&6; }
-{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5
printf %s "checking for egrep -e... " >&6; }
if test ${ac_cv_path_EGREP_TRADITIONAL+y}
then :
@@ -5901,7 +5901,7 @@ case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #(
cat "conftest.in" "conftest.in" >"conftest.tmp"
mv "conftest.tmp" "conftest.in"
cp "conftest.in" "conftest.nl"
- printf '%s\n' 'EGREP_TRADITIONAL' >> "conftest.nl"
+ printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl"
"$ac_path_EGREP_TRADITIONAL" -E 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
as_fn_arith $ac_count + 1 && ac_count=$as_val
@@ -5963,7 +5963,7 @@ case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #(
cat "conftest.in" "conftest.in" >"conftest.tmp"
mv "conftest.tmp" "conftest.in"
cp "conftest.in" "conftest.nl"
- printf '%s\n' 'EGREP_TRADITIONAL' >> "conftest.nl"
+ printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl"
"$ac_path_EGREP_TRADITIONAL" 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
as_fn_arith $ac_count + 1 && ac_count=$as_val
@@ -5994,8 +5994,8 @@ esac
fi ;;
esac
fi
-{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP_TRADITIONAL" >&5
-printf '%s\n' "$ac_cv_path_EGREP_TRADITIONAL" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP_TRADITIONAL" >&5
+printf "%s\n" "$ac_cv_path_EGREP_TRADITIONAL" >&6; }
EGREP_TRADITIONAL=$ac_cv_path_EGREP_TRADITIONAL
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -6136,10 +6136,10 @@ Alternatively, you may set the environment variables FREETYPE_CFLAGS
and FREETYPE_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf '%s\n' "no" >&6; }
- { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
-printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
@@ -8089,7 +8089,280 @@ printf '%s\n' "$as_me: WARNING: can't find freetype, required for graphics=cairo
if test $BUILD_SERVER = win32; then
BUILD_GRAPHICS=winlib
elif test $BUILD_SERVER = wayland; then
- as_fn_error $? "wayland backend requires cairo" "$LINENO" 5
+ for ac_header in wayland-util.h
+do :
+ ac_fn_c_check_header_compile "$LINENO" "wayland-util.h" "ac_cv_header_wayland_util_h" "$ac_includes_default"
+if test "x$ac_cv_header_wayland_util_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_WAYLAND_UTIL_H 1" >>confdefs.h
+
+else case e in #(
+ e) as_fn_error $? "**** No wayland-util.h. Install libwayland-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+done
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wl_display_flush in -lwayland-client" >&5
+printf %s "checking for wl_display_flush in -lwayland-client... " >&6; }
+if test ${ac_cv_lib_wayland_client_wl_display_flush+y}
+then :
+ printf %s "(cached) " >&6
+else case e in #(
+ e) ac_check_lib_save_LIBS=$LIBS
+LIBS="-lwayland-client $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply.
+ The 'extern "C"' is for builds by C++ compilers;
+ although this is not generally supported in C code supporting it here
+ has little cost and some practical benefit (sr 110532). */
+#ifdef __cplusplus
+extern "C"
+#endif
+char wl_display_flush (void);
+int
+main (void)
+{
+return wl_display_flush ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_wayland_client_wl_display_flush=yes
+else case e in #(
+ e) ac_cv_lib_wayland_client_wl_display_flush=no ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wayland_client_wl_display_flush" >&5
+printf "%s\n" "$ac_cv_lib_wayland_client_wl_display_flush" >&6; }
+if test "x$ac_cv_lib_wayland_client_wl_display_flush" = xyes
+then :
+ printf "%s\n" "#define HAVE_LIBWAYLAND_CLIENT 1" >>confdefs.h
+
+ LIBS="-lwayland-client $LIBS"
+
+else case e in #(
+ e) as_fn_error $? "**** No wl_display_flush in libwayland-client. Install correct version of libwayland-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+ for ac_header in xkbcommon/xkbcommon.h
+do :
+ ac_fn_c_check_header_compile "$LINENO" "xkbcommon/xkbcommon.h" "ac_cv_header_xkbcommon_xkbcommon_h" "$ac_includes_default"
+if test "x$ac_cv_header_xkbcommon_xkbcommon_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_XKBCOMMON_XKBCOMMON_H 1" >>confdefs.h
+
+else case e in #(
+ e) as_fn_error $? "**** No xkbcommon/xkbcommon.h. Required for wayland. Install libxkbcommon-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+done
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for xkb_context_new in -lxkbcommon" >&5
+printf %s "checking for xkb_context_new in -lxkbcommon... " >&6; }
+if test ${ac_cv_lib_xkbcommon_xkb_context_new+y}
+then :
+ printf %s "(cached) " >&6
+else case e in #(
+ e) ac_check_lib_save_LIBS=$LIBS
+LIBS="-lxkbcommon $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply.
+ The 'extern "C"' is for builds by C++ compilers;
+ although this is not generally supported in C code supporting it here
+ has little cost and some practical benefit (sr 110532). */
+#ifdef __cplusplus
+extern "C"
+#endif
+char xkb_context_new (void);
+int
+main (void)
+{
+return xkb_context_new ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_xkbcommon_xkb_context_new=yes
+else case e in #(
+ e) ac_cv_lib_xkbcommon_xkb_context_new=no ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xkbcommon_xkb_context_new" >&5
+printf "%s\n" "$ac_cv_lib_xkbcommon_xkb_context_new" >&6; }
+if test "x$ac_cv_lib_xkbcommon_xkb_context_new" = xyes
+then :
+ printf "%s\n" "#define HAVE_LIBXKBCOMMON 1" >>confdefs.h
+
+ LIBS="-lxkbcommon $LIBS"
+
+else case e in #(
+ e) as_fn_error $? "**** No xkb_context_new in libxkbcommon. Install correct version of libxkbcommon-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+
+ for ac_header in EGL/egl.h
+do :
+ ac_fn_c_check_header_compile "$LINENO" "EGL/egl.h" "ac_cv_header_EGL_egl_h" "$ac_includes_default"
+if test "x$ac_cv_header_EGL_egl_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_EGL_EGL_H 1" >>confdefs.h
+
+else case e in #(
+ e) as_fn_error $? "**** No EGL/egl.h. Required for wayland. Install libegl-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+done
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for eglGetDisplay in -lEGL" >&5
+printf %s "checking for eglGetDisplay in -lEGL... " >&6; }
+if test ${ac_cv_lib_EGL_eglGetDisplay+y}
+then :
+ printf %s "(cached) " >&6
+else case e in #(
+ e) ac_check_lib_save_LIBS=$LIBS
+LIBS="-lEGL $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply.
+ The 'extern "C"' is for builds by C++ compilers;
+ although this is not generally supported in C code supporting it here
+ has little cost and some practical benefit (sr 110532). */
+#ifdef __cplusplus
+extern "C"
+#endif
+char eglGetDisplay (void);
+int
+main (void)
+{
+return eglGetDisplay ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_EGL_eglGetDisplay=yes
+else case e in #(
+ e) ac_cv_lib_EGL_eglGetDisplay=no ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_EGL_eglGetDisplay" >&5
+printf "%s\n" "$ac_cv_lib_EGL_eglGetDisplay" >&6; }
+if test "x$ac_cv_lib_EGL_eglGetDisplay" = xyes
+then :
+ printf "%s\n" "#define HAVE_LIBEGL 1" >>confdefs.h
+
+ LIBS="-lEGL $LIBS"
+
+else case e in #(
+ e) as_fn_error $? "**** No eglGetDisplay in libEGL. Install correct version of libegl-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+
+ for ac_header in GLES2/gl2.h
+do :
+ ac_fn_c_check_header_compile "$LINENO" "GLES2/gl2.h" "ac_cv_header_GLES2_gl2_h" "$ac_includes_default"
+if test "x$ac_cv_header_GLES2_gl2_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_GLES2_GL2_H 1" >>confdefs.h
+
+else case e in #(
+ e) as_fn_error $? "**** No GLES2/gl2.h. Required for wayland. Install libgles2-mesa-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+done
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for glClear in -lGLESv2" >&5
+printf %s "checking for glClear in -lGLESv2... " >&6; }
+if test ${ac_cv_lib_GLESv2_glClear+y}
+then :
+ printf %s "(cached) " >&6
+else case e in #(
+ e) ac_check_lib_save_LIBS=$LIBS
+LIBS="-lGLESv2 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply.
+ The 'extern "C"' is for builds by C++ compilers;
+ although this is not generally supported in C code supporting it here
+ has little cost and some practical benefit (sr 110532). */
+#ifdef __cplusplus
+extern "C"
+#endif
+char glClear (void);
+int
+main (void)
+{
+return glClear ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_GLESv2_glClear=yes
+else case e in #(
+ e) ac_cv_lib_GLESv2_glClear=no ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_GLESv2_glClear" >&5
+printf "%s\n" "$ac_cv_lib_GLESv2_glClear" >&6; }
+if test "x$ac_cv_lib_GLESv2_glClear" = xyes
+then :
+ printf "%s\n" "#define HAVE_LIBGLESV2 1" >>confdefs.h
+
+ LIBS="-lGLESv2 $LIBS"
+
+else case e in #(
+ e) as_fn_error $? "**** No glClear in libGLESv2. Install correct version of libgles2-mesa-dev or equivalent." "$LINENO" 5 ;;
+esac
+fi
+
+
+ CAIRO_LIBS="$CAIRO_LIBS $XFT_LIBS -lEGL -lGLESv2"
+ CAIRO_CFLAGS="$CAIRO_CFLAGS"
+ LIBS="-lwayland-client -lwayland-cursor -lwayland-egl -lxkbcommon $LIBS"
else
BUILD_GRAPHICS=xlib
fi
@@ -8506,7 +8779,44 @@ cat >confcache <<\_ACEOF
_ACEOF
-ac_cache_dump |
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+ for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+
+ (set) 2>&1 |
+ case $as_nl`(ac_space=' '; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ # 'set' does not quote correctly, so add quotes: double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \.
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;; #(
+ *)
+ # 'set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+) |
sed '
/^ac_cv_env_/b end
t clear
@@ -8941,7 +9251,7 @@ cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1
# values after options handling.
ac_log="
This file was extended by $as_me, which was
-generated by GNU Autoconf 2.73. Invocation command line was
+generated by GNU Autoconf 2.72. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
CONFIG_HEADERS = $CONFIG_HEADERS
@@ -9005,10 +9315,10 @@ cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
config.status
-configured by $0, generated by GNU Autoconf 2.73,
+configured by $0, generated by GNU Autoconf 2.72,
with options \\"\$ac_cs_config\\"
-Copyright (C) 2026 Free Software Foundation, Inc.
+Copyright (C) 2023 Free Software Foundation, Inc.
This config.status script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it."
@@ -9641,9 +9951,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
{ ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
{ ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
"$ac_tmp/out"`; test -z "$ac_out"; } &&
- { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir'
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir'
which seems to be undefined. Please make sure it is defined" >&5
-printf '%s\n' "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir'
+printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir'
which seems to be undefined. Please make sure it is defined" >&2;}
rm -f "$ac_tmp/stdin"
diff --git a/configure.ac b/configure.ac
index 7742d8df..f76898eb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,7 +26,7 @@
builtin(include, pkg.m4)dnl
AC_INIT
-AC_PREREQ([2.73])
+AC_PREREQ([2.72])
AC_CONFIG_SRCDIR([back.make.in])
AC_CONFIG_HEADERS([config.h])