summaryrefslogtreecommitdiff
path: root/src/platform/wayland/surface.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 13:44:13 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 13:44:13 +0300
commitc059e3aa7f93da470f052a5cd8ee0d8dc969e679 (patch)
tree63ffa378287ed2c606eda2e940501b9edc3d2b8b /src/platform/wayland/surface.c
parent87d0bc50d53dc216efb2b04495f090616318bc5c (diff)
rename and simplification
Diffstat (limited to 'src/platform/wayland/surface.c')
-rw-r--r--src/platform/wayland/surface.c507
1 files changed, 507 insertions, 0 deletions
diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c
new file mode 100644
index 0000000..1820642
--- /dev/null
+++ b/src/platform/wayland/surface.c
@@ -0,0 +1,507 @@
+#include "internal.h"
+#include <assert.h>
+
+static void display_free(struct wayland_display *c);
+
+static void display_cancel_read(struct wayland_display *c)
+{
+ if (!c->reading)
+ return;
+ wl_display_cancel_read(c->wl);
+ c->reading = false;
+}
+
+static void report_display_error(struct wayland_display *c)
+{
+ int err = wl_display_get_error(c->wl);
+ if (!err)
+ return;
+ uint32_t object_id = 0;
+ const struct wl_interface *iface = NULL;
+ uint32_t protocol_err = wl_display_get_protocol_error(c->wl, &iface, &object_id);
+ if (protocol_err)
+ fprintf(stderr, "[yasl] protocol error %u on %s (object %u)\n",
+ protocol_err, iface ? iface->name : "?", object_id);
+ else
+ fprintf(stderr, "[yasl] display error %d\n", err);
+}
+
+void display_enter(struct wayland_display *c)
+{
+ c->depth++;
+}
+
+void display_leave(struct wayland_display *c)
+{
+ if (--c->depth > 0)
+ return;
+ keyboard_settle_focus(c);
+ while (c->dead) {
+ struct wayland_surface *w = c->dead;
+ c->dead = w->dead_next;
+ free(w);
+ }
+}
+
+static void display_lose(struct wayland_display *c)
+{
+ if (c->lost)
+ return;
+ c->lost = true;
+ report_display_error(c);
+ display_cancel_read(c);
+ display_enter(c);
+ for (struct wayland_surface *w = c->surfaces, *next; w; w = next) {
+ next = w->conn_next;
+ surface_fail(w);
+ }
+ display_leave(c);
+ if (c->cbs && c->cbs->lost)
+ c->cbs->lost(c->ud);
+}
+
+void surface_fail(struct wayland_surface *w)
+{
+ if (w->closed)
+ return;
+ w->closed = true;
+ if (w->cbs->close)
+ w->cbs->close(&w->base, w->ud);
+}
+
+static int display_ready(int fd, short revents, void *ud)
+{
+ (void)fd;
+ struct wayland_display *c = ud;
+ if (c->lost)
+ return 0;
+ display_enter(c);
+ if (c->reading) {
+ c->reading = false;
+ if (!(revents & POLLIN))
+ wl_display_cancel_read(c->wl);
+ else if (wl_display_read_events(c->wl) < 0)
+ revents |= POLLERR;
+ }
+ if (revents & POLLOUT) {
+ if (wl_display_flush(c->wl) >= 0) {
+ if (c->loop && c->watch > 0)
+ yasl_loop_set_fd_events(c->loop, c->watch, POLLIN);
+ } else if (errno != EAGAIN) {
+ display_lose(c);
+ }
+ }
+ if (wl_display_dispatch_pending(c->wl) < 0 ||
+ (revents & (POLLERR | POLLHUP | POLLNVAL)))
+ display_lose(c);
+ display_leave(c);
+ return 0;
+}
+
+static bool display_arm(struct wayland_display *c)
+{
+ bool had_pending = false;
+ while (!c->reading && wl_display_prepare_read(c->wl) != 0) {
+ had_pending = true;
+ if (wl_display_dispatch_pending(c->wl) < 0) {
+ display_lose(c);
+ return false;
+ }
+ }
+ c->reading = true;
+ short events = POLLIN;
+ if (wl_display_flush(c->wl) < 0) {
+ if (errno != EAGAIN) {
+ display_cancel_read(c);
+ display_lose(c);
+ return false;
+ }
+ events |= POLLOUT;
+ }
+ if (c->loop && c->watch > 0)
+ yasl_loop_set_fd_events(c->loop, c->watch, events);
+ return had_pending;
+}
+
+static inline bool display_has_immediate_work(const struct wayland_display *c)
+{
+ if (unlikely(!c || c->lost)) return false;
+ for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) {
+ bool ready_draw = w->want_draw & (w->frame_cb == NULL) & w->configured;
+ bool has_input = (w->input.count > 0 || w->input.reset_pending);
+ if (unlikely((ready_draw | has_input) & !w->closed))
+ return true;
+ }
+ return false;
+}
+
+int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms)
+{
+ if (!c || c->lost)
+ return -1;
+
+ display_enter(c);
+
+ if (wl_display_dispatch_pending(c->wl) < 0) {
+ display_lose(c);
+ display_leave(c);
+ return -1;
+ }
+
+ display_arm(c);
+ if (c->lost) {
+ display_leave(c);
+ return -1;
+ }
+
+ if (timeout_ms != 0 && display_has_immediate_work(c)) {
+ timeout_ms = 0;
+ }
+
+ int n = 0;
+ if (c->loop) {
+ n = yasl_loop_step(c->loop, timeout_ms);
+ }
+
+ if (c->reading) {
+ display_cancel_read(c);
+ }
+
+ if (wl_display_dispatch_pending(c->wl) < 0) {
+ display_lose(c);
+ display_leave(c);
+ return -1;
+ }
+
+ display_leave(c);
+ return n;
+}
+
+void wayland_display_flush(struct wayland_display *c)
+{
+ if (!c || c->lost)
+ return;
+ display_enter(c);
+ for (struct wayland_surface *w = c->surfaces, *next; w; w = next) {
+ next = w->conn_next;
+ surface_draw_frame(w);
+ }
+ if (!c->lost && c->wl) {
+ if (wl_display_flush(c->wl) < 0 && errno != EAGAIN)
+ display_lose(c);
+ }
+ display_leave(c);
+}
+
+static void display_free(struct wayland_display *c)
+{
+ conn_drag_end(c);
+ if (c->loop && c->watch > 0) yasl_loop_del_fd(c->loop, c->watch);
+ if (c->wl) display_cancel_read(c);
+ while (c->seats)
+ seat_destroy(c->seats);
+ if (c->toplevel_drag_mgr) xdg_toplevel_drag_manager_v1_destroy(c->toplevel_drag_mgr);
+ if (c->primary_selection_mgr) zwp_primary_selection_device_manager_v1_destroy(c->primary_selection_mgr);
+ if (c->data_manager) wl_data_device_manager_destroy(c->data_manager);
+ if (c->cursor_shape_mgr) wp_cursor_shape_manager_v1_destroy(c->cursor_shape_mgr);
+ if (c->cursor_theme) wl_cursor_theme_destroy(c->cursor_theme);
+ if (c->tablet_mgr) zwp_tablet_manager_v2_destroy(c->tablet_mgr);
+ if (c->text_input_mgr) zwp_text_input_manager_v3_destroy(c->text_input_mgr);
+ if (c->tearing_mgr) wp_tearing_control_manager_v1_destroy(c->tearing_mgr);
+ if (c->fifo_mgr) wp_fifo_manager_v1_destroy(c->fifo_mgr);
+ if (c->commit_timing_mgr) wp_commit_timing_manager_v1_destroy(c->commit_timing_mgr);
+ if (c->presentation) wp_presentation_destroy(c->presentation);
+ if (c->xkb_ctx) xkb_context_unref(c->xkb_ctx);
+ if (c->scale_mgr) wp_fractional_scale_manager_v1_destroy(c->scale_mgr);
+ if (c->viewporter) wp_viewporter_destroy(c->viewporter);
+ if (c->decor_mgr) zxdg_decoration_manager_v1_destroy(c->decor_mgr);
+ conn_destroy_outputs(c);
+ if (c->wm_base) xdg_wm_base_destroy(c->wm_base);
+ if (c->shm) wl_shm_destroy(c->shm);
+ if (c->compositor) wl_compositor_destroy(c->compositor);
+ if (c->registry) wl_registry_destroy(c->registry);
+ if (c->wl) wl_display_disconnect(c->wl);
+ if (c->owns_loop && c->loop) {
+ yasl_loop_destroy(c->loop);
+ c->loop = NULL;
+ c->base.loop = NULL;
+ }
+ free(c->app_id);
+ free(c);
+}
+
+struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec)
+{
+ struct wayland_display *c = calloc(1, sizeof(*c));
+ if (!c) return NULL;
+ c->base.ops = &yasl_wayland_backend;
+ if (!loop) {
+ c->loop = yasl_loop_create();
+ c->owns_loop = true;
+ } else {
+ c->loop = loop;
+ }
+ c->base.loop = c->loop;
+ if (!c->loop) goto err;
+ if (spec) {
+ c->base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0};
+ c->cbs = &c->base.callbacks;
+ c->ud = spec->ud;
+ if (spec->app_id) c->app_id = strdup(spec->app_id);
+ }
+ c->xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ c->wl = c->xkb_ctx ? wl_display_connect(NULL) : NULL;
+ if (!c->wl) goto err;
+ c->base.native_handle = c->wl;
+
+ c->registry = wl_display_get_registry(c->wl);
+ if (!c->registry) goto err;
+ wl_registry_add_listener(c->registry, &registry_listener, c);
+ if (wl_display_roundtrip(c->wl) < 0 || !c->compositor || !c->shm || !c->wm_base)
+ goto err;
+
+ surface_init_cursor(c);
+ if (c->loop) {
+ c->watch = yasl_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c);
+ if (c->watch <= 0) { c->watch = 0; goto err; }
+ }
+ if (display_input_init(c) < 0)
+ goto err;
+ return c;
+
+err:
+ display_free(c);
+ return NULL;
+}
+
+void wayland_display_close(struct wayland_display *c)
+{
+ if (!c) return;
+ assert(!c->surfaces && "destroy every surface before its display");
+ display_free(c);
+}
+
+uint32_t wayland_display_caps(const struct wayland_display *c)
+{
+ if (!c) return 0;
+ uint32_t caps = YASL_CAP_POPUPS | YASL_CAP_CSD | YASL_CAP_FRAME_PACING;
+ if (c->tablet_mgr) caps |= YASL_CAP_TABLET;
+ struct seat *s = conn_active_seat(c);
+ const struct wl_data_device *device = s ? clipboard_data_device(s->clipboard) : NULL;
+ if (device && wl_data_device_get_version((struct wl_data_device *)device) >= 3)
+ caps |= YASL_CAP_DRAG;
+ if (c->toplevel_drag_mgr)
+ caps |= YASL_CAP_DRAG_CARRY;
+ if (c->primary_selection_mgr)
+ caps |= YASL_CAP_PRIMARY_SELECTION;
+ return caps;
+}
+
+static void conn_link(struct wayland_display *c, struct wayland_surface *w)
+{
+ w->base.display = &c->base;
+ w->conn = c;
+ w->conn_next = c->surfaces;
+ c->surfaces = w;
+}
+
+static void conn_unlink(struct wayland_surface *w)
+{
+ struct wayland_display *c = w->conn;
+ if (!c) return;
+ for (struct wayland_surface **p = &c->surfaces; *p; p = &(*p)->conn_next) {
+ if (*p == w) {
+ *p = w->conn_next;
+ break;
+ }
+ }
+ for (struct seat *s = c->seats; s; s = s->next)
+ seat_forget_surface(s, w);
+ w->conn = NULL;
+}
+
+
+static bool spec_valid(const struct yasl_window_spec *spec)
+{
+ size_t bytes;
+ return spec && spec->size.x > 0 && spec->size.y > 0 &&
+ buffer_size(spec->size.x, spec->size.y, &bytes);
+}
+
+struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *c,
+ const struct yasl_window_spec *spec, void *graphics_device)
+{
+ (void)graphics_device;
+ if (!c || c->lost || !spec_valid(spec))
+ return NULL;
+ int width = spec->size.x, height = spec->size.y;
+ display_cancel_read(c);
+ struct wayland_surface *w = calloc(1, sizeof(*w));
+ if (!w) return NULL;
+ display_enter(c);
+ conn_link(c, w);
+ wayland_surface_set_callbacks(w, NULL, NULL);
+ w->cursor = YASL_CURSOR_TEXT;
+ w->req_width = width;
+ w->req_height = height;
+ w->logical_width = width;
+ w->logical_height = height;
+ w->scale_120 = YASL_SCALE_UNIT;
+ w->pending_scale_120 = YASL_SCALE_UNIT;
+ w->scale_factor = 1.0;
+ w->size_pending = true;
+ w->clock.refresh_ns = c->outputs && c->outputs->refresh_ns
+ ? c->outputs->refresh_ns : YASL_REFRESH_DEFAULT_NS;
+
+ w->surface = wl_compositor_create_surface(c->compositor);
+ if (!w->surface) goto err;
+ w->base.native_handle = w->surface;
+ w->base.size = (struct yasl_vector2i){width, height};
+ w->base.scale_120 = YASL_SCALE_UNIT;
+ wl_surface_set_user_data(w->surface, w);
+ if (surface_init_scaling(w) < 0) goto err;
+ if (surface_init_shell(w, spec->title) < 0) goto err;
+ w->opaque = spec->fullscreen;
+ if (spec->fullscreen)
+ xdg_toplevel_set_fullscreen(w->toplevel, NULL);
+ if (spec->tearing && c->tearing_mgr) {
+ w->tearing_control = wp_tearing_control_manager_v1_get_tearing_control(
+ c->tearing_mgr, w->surface);
+ if (w->tearing_control)
+ wp_tearing_control_v1_set_presentation_hint(w->tearing_control,
+ WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC);
+ }
+
+ wl_surface_commit(w->surface);
+ if (wl_display_roundtrip(c->wl) < 0) {
+ display_lose(c);
+ goto err;
+ }
+ if (apply_pending_geometry(w) < 0) goto err;
+ if (w->closed) goto err;
+ display_leave(c);
+ return w;
+
+err:
+ wayland_surface_destroy(w);
+ display_leave(c);
+ return NULL;
+}
+
+void wayland_surface_set_callbacks(struct wayland_surface *w, const struct yasl_window_cbs *cbs, void *ud)
+{
+ if (w && !w->closed) {
+ w->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0};
+ w->cbs = &w->base.callbacks;
+ w->ud = ud;
+ }
+}
+
+void wayland_surface_destroy(struct wayland_surface *w)
+{
+ if (!w || w->destroyed) return;
+ for (struct wayland_surface *o = w->conn ? w->conn->surfaces : NULL; o; o = o->conn_next)
+ assert(o->parent != w && "destroy a popup before its parent");
+ input_queue_clear(&w->input);
+ free(w->ime_preedit);
+ free(w->ime_commit);
+ free(w->ime_shown);
+ if (w->tearing_control) wp_tearing_control_v1_destroy(w->tearing_control);
+ if (w->fifo) wp_fifo_v1_destroy(w->fifo);
+ if (w->commit_timer) wp_commit_timer_v1_destroy(w->commit_timer);
+ surface_feedback_destroy(w);
+ if (w->frame_cb) wl_callback_destroy(w->frame_cb);
+ for (int i = 0; i < NBUF; i++) buffer_destroy(&w->buffers[i]);
+ if (w->frac_scale) wp_fractional_scale_v1_destroy(w->frac_scale);
+ if (w->viewport) wp_viewport_destroy(w->viewport);
+ if (w->toplevel_decor) zxdg_toplevel_decoration_v1_destroy(w->toplevel_decor);
+ if (w->toplevel) xdg_toplevel_destroy(w->toplevel);
+ if (w->popup) xdg_popup_destroy(w->popup);
+ if (w->xdg_surface) xdg_surface_destroy(w->xdg_surface);
+ if (w->surface) {
+ wl_surface_set_user_data(w->surface, NULL);
+ wl_surface_destroy(w->surface);
+ }
+ struct wayland_display *c = w->conn;
+ conn_unlink(w);
+ static const struct yasl_window_cbs no_callbacks = {0};
+ *w = (struct wayland_surface){
+ .base = w->base,
+ .conn_next = w->conn_next,
+ .cbs = &no_callbacks,
+ .closed = true,
+ .destroyed = true,
+ };
+ if (c && c->depth > 0) {
+ w->dead_next = c->dead;
+ c->dead = w;
+ } else {
+ free(w);
+ }
+}
+
+struct wayland_display *wayland_surface_display(const struct wayland_surface *w)
+{
+ return w ? w->conn : NULL;
+}
+
+void wayland_surface_schedule_redraw(struct wayland_surface *w)
+{
+ if (!w || w->closed) return;
+ w->want_draw = true;
+ if (w->conn && w->conn->loop)
+ yasl_loop_kick(w->conn->loop);
+}
+
+struct yasl_geometry wayland_surface_geometry(const struct wayland_surface *w)
+{
+ return (struct yasl_geometry){
+ .size = w ? (struct yasl_vector2i){w->width, w->height} : (struct yasl_vector2i){0},
+ .scale_120 = w ? w->scale_120 : YASL_SCALE_UNIT,
+ };
+}
+
+void wayland_surface_set_background_color(struct wayland_surface *w, uint32_t bg)
+{
+ (void)w; (void)bg;
+}
+
+bool wayland_surface_start_interactive_move(struct wayland_surface *s)
+{
+ if (!s || !s->toplevel || !s->conn) return false;
+ struct seat *seat = conn_active_seat(s->conn);
+ if (!seat || !seat->proxy || !seat->press_serial) return false;
+ xdg_toplevel_move(s->toplevel, seat->proxy, seat->press_serial);
+ return true;
+}
+
+bool wayland_surface_start_interactive_resize(struct wayland_surface *s, enum yasl_resize_edge edge)
+{
+ if (!s || !s->toplevel || !s->conn || edge == YASL_EDGE_NONE) return false;
+ struct seat *seat = conn_active_seat(s->conn);
+ if (!seat || !seat->proxy || !seat->press_serial) return false;
+ xdg_toplevel_resize(s->toplevel, seat->proxy, seat->press_serial, (uint32_t)edge);
+ return true;
+}
+
+bool wayland_surface_show_window_menu(struct wayland_surface *s, struct yasl_vector2i pos)
+{
+ if (!s || !s->toplevel || !s->conn) return false;
+ struct seat *seat = conn_active_seat(s->conn);
+ if (!seat || !seat->proxy || !seat->press_serial) return false;
+ uint32_t scale = s->scale_120 ? s->scale_120 : YASL_SCALE_UNIT;
+ xdg_toplevel_show_window_menu(s->toplevel, seat->proxy, seat->press_serial,
+ yasl_scale_to_logical(pos.x, scale),
+ yasl_scale_to_logical(pos.y, scale));
+ return true;
+}
+
+void *wayland_display_get_native_handle(struct wayland_display *d)
+{
+ return d ? d->wl : NULL;
+}
+
+void *wayland_surface_get_native_handle(struct wayland_surface *s)
+{
+ return s ? s->surface : NULL;
+}