summaryrefslogtreecommitdiff
path: root/src/wayland/popup.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
commit927ae621c9a3d46dbc401b8e9ea7bbed1bf2e97b (patch)
treeaeb00c16770dcf5a36d28c1167cc39c6759059fb /src/wayland/popup.c
initial: Yet Another Surface Library (YASL) 0.1.0
A minimal, modern desktop window surface library in C. Supports Wayland on Linux and Cocoa/Metal on macOS with presentation timing, fractional scaling, IME, and CSD.
Diffstat (limited to 'src/wayland/popup.c')
-rw-r--r--src/wayland/popup.c193
1 files changed, 193 insertions, 0 deletions
diff --git a/src/wayland/popup.c b/src/wayland/popup.c
new file mode 100644
index 0000000..e809a43
--- /dev/null
+++ b/src/wayland/popup.c
@@ -0,0 +1,193 @@
+#include "internal.h"
+
+static void popup_configure(void *data, struct xdg_popup *popup,
+ int32_t x, int32_t y, int32_t width, int32_t height)
+{
+ (void)data; (void)popup; (void)x; (void)y; (void)width; (void)height;
+}
+
+static void popup_done(void *data, struct xdg_popup *popup)
+{
+ (void)popup;
+ surface_fail(data);
+}
+
+static void popup_repositioned(void *data, struct xdg_popup *popup, uint32_t token)
+{
+ (void)data; (void)popup; (void)token;
+}
+
+static const struct xdg_popup_listener popup_listener = {
+ .configure = popup_configure,
+ .popup_done = popup_done,
+ .repositioned = popup_repositioned,
+};
+
+static bool popup_spec_valid(const struct yasl_popup_spec *spec)
+{
+ return spec && spec->size.x > 0 && spec->size.y > 0 &&
+ spec->content_offset.x >= 0 && spec->content_offset.y >= 0 &&
+ spec->content_size.x >= 0 && spec->content_size.y >= 0 &&
+ spec->content_offset.x + spec->content_size.x <= spec->size.x &&
+ spec->content_offset.y + spec->content_size.y <= spec->size.y;
+}
+
+static bool popup_apply_spec(struct yasl_surface *w, const struct yasl_popup_spec *spec)
+{
+ uint32_t scale = w->parent->scale_120;
+ struct yasl_vector2i logical = {
+ .x = logical_px_ceil(spec->size.x, scale),
+ .y = logical_px_ceil(spec->size.y, scale),
+ };
+ int width = device_px(logical.x, scale);
+ int height = device_px(logical.y, scale);
+ size_t bytes;
+ if (logical.x <= 0 || logical.y <= 0 || !buffer_size(width, height, &bytes))
+ return false;
+ struct yasl_vector2i content = spec->content_size;
+ if (!content.x) content.x = spec->size.x - spec->content_offset.x;
+ if (!content.y) content.y = spec->size.y - spec->content_offset.y;
+ bool rescaled = scale != w->scale_120;
+ w->scale_120 = w->pending_scale_120 = scale;
+ w->scale_factor = (double)scale / YASL_SCALE_UNIT;
+ w->width = width;
+ w->height = height;
+ w->logical_width = logical.x;
+ w->logical_height = logical.y;
+ w->content = (struct yasl_rect2i){
+ .x = spec->content_offset.x, .y = spec->content_offset.y,
+ .width = content.x, .height = content.y
+ };
+ for (int i = 0; i < NBUF; i++)
+ w->buffers[i].presented = false;
+ if (w->viewport)
+ wp_viewport_set_destination(w->viewport, logical.x, logical.y);
+ else if ((rescaled || !w->configured) && scale % YASL_SCALE_UNIT == 0 &&
+ wl_proxy_get_version((struct wl_proxy *)w->surface) >=
+ WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION)
+ wl_surface_set_buffer_scale(w->surface, (int32_t)(scale / YASL_SCALE_UNIT));
+ return true;
+}
+
+static void popup_set_geometry(struct yasl_surface *w)
+{
+ uint32_t scale = w->scale_120;
+ int gx = yasl_scale_to_logical(w->content.x, scale);
+ int gy = yasl_scale_to_logical(w->content.y, scale);
+ int gw = yasl_scale_to_logical(w->content.width, scale);
+ int gh = yasl_scale_to_logical(w->content.height, scale);
+ if (gw < 1) gw = 1;
+ if (gh < 1) gh = 1;
+ if (gx + gw > w->logical_width)
+ gx = w->logical_width - gw > 0 ? w->logical_width - gw : 0;
+ if (gy + gh > w->logical_height)
+ gy = w->logical_height - gh > 0 ? w->logical_height - gh : 0;
+ xdg_surface_set_window_geometry(w->xdg_surface, gx, gy, gw, gh);
+}
+
+static struct xdg_positioner *popup_positioner(struct yasl_surface *w,
+ const struct yasl_popup_spec *spec)
+{
+ struct yasl_surface *parent = w->parent;
+ struct xdg_positioner *pos = xdg_wm_base_create_positioner(w->conn->wm_base);
+ if (!pos)
+ return NULL;
+ uint32_t scale = parent->scale_120;
+ int gw = yasl_scale_to_logical(w->content.width, scale);
+ int gh = yasl_scale_to_logical(w->content.height, scale);
+ xdg_positioner_set_size(pos, gw > 0 ? gw : 1, gh > 0 ? gh : 1);
+ struct yasl_vector2i at = {
+ .x = spec->anchor.x - parent->content.x,
+ .y = spec->anchor.y - parent->content.y,
+ };
+ int aw = yasl_scale_to_logical(spec->anchor.width, scale);
+ int ah = yasl_scale_to_logical(spec->anchor.height, scale);
+ xdg_positioner_set_anchor_rect(pos,
+ yasl_scale_to_logical(at.x, scale),
+ yasl_scale_to_logical(at.y, scale),
+ aw > 0 ? aw : 1, ah > 0 ? ah : 1);
+ xdg_positioner_set_anchor(pos, spec->beside ? XDG_POSITIONER_ANCHOR_TOP_RIGHT
+ : XDG_POSITIONER_ANCHOR_TOP_LEFT);
+ xdg_positioner_set_gravity(pos, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+ xdg_positioner_set_constraint_adjustment(pos,
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X |
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y |
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X |
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
+ if (xdg_positioner_get_version(pos) >= XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION)
+ xdg_positioner_set_reactive(pos);
+ return pos;
+}
+
+struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent,
+ const struct yasl_popup_spec *spec)
+{
+ struct yasl_display *c = parent ? parent->conn : NULL;
+ struct seat *seat = c ? conn_active_seat(c) : NULL;
+ if (!c || c->lost || parent->closed || !parent->xdg_surface || !seat ||
+ !seat->input_serial || !popup_spec_valid(spec))
+ return NULL;
+
+ struct yasl_surface *w = calloc(1, sizeof(*w));
+ if (!w)
+ return NULL;
+ w->conn = c;
+ w->conn_next = c->surfaces;
+ c->surfaces = w;
+ w->parent = parent;
+ yasl_surface_set_callbacks(w, NULL, NULL);
+ w->clock.refresh_ns = parent->clock.refresh_ns;
+
+ w->surface = wl_compositor_create_surface(c->compositor);
+ if (!w->surface || surface_init_scaling(w) < 0)
+ goto err;
+ w->xdg_surface = xdg_wm_base_get_xdg_surface(c->wm_base, w->surface);
+ if (!w->xdg_surface)
+ goto err;
+ xdg_surface_add_listener(w->xdg_surface, &shell_surface_listener, w);
+ if (!popup_apply_spec(w, spec))
+ goto err;
+ struct xdg_positioner *pos = popup_positioner(w, spec);
+ if (!pos)
+ goto err;
+ w->popup = xdg_surface_get_popup(w->xdg_surface, parent->xdg_surface, pos);
+ xdg_positioner_destroy(pos);
+ if (!w->popup)
+ goto err;
+ xdg_popup_add_listener(w->popup, &popup_listener, w);
+ popup_set_geometry(w);
+ xdg_popup_grab(w->popup, seat->proxy, seat->input_serial);
+ w->want_draw = true;
+ wl_surface_commit(w->surface);
+ return w;
+
+err:
+ yasl_surface_destroy(w);
+ return NULL;
+}
+
+bool yasl_surface_popup_reposition(struct yasl_surface *w, const struct yasl_popup_spec *spec)
+{
+ if (!w || !w->popup || w->closed || !w->conn || w->conn->lost ||
+ !popup_spec_valid(spec) ||
+ xdg_popup_get_version(w->popup) < XDG_POPUP_REPOSITION_SINCE_VERSION)
+ return false;
+ struct yasl_popup_spec old = {
+ .size = { .x = w->width, .y = w->height },
+ .content_offset = { .x = w->content.x, .y = w->content.y },
+ .content_size = { .x = w->content.width, .y = w->content.height },
+ };
+ if (!popup_apply_spec(w, spec)) {
+ popup_apply_spec(w, &old);
+ return false;
+ }
+ struct xdg_positioner *pos = popup_positioner(w, spec);
+ if (!pos)
+ return false;
+ popup_set_geometry(w);
+ static uint32_t token;
+ xdg_popup_reposition(w->popup, pos, ++token);
+ xdg_positioner_destroy(pos);
+ yasl_surface_schedule_redraw(w);
+ return true;
+}