diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-11 21:53:42 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-11 21:53:42 +0300 |
| commit | 927ae621c9a3d46dbc401b8e9ea7bbed1bf2e97b (patch) | |
| tree | aeb00c16770dcf5a36d28c1167cc39c6759059fb /src/wayland/shell.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/shell.c')
| -rw-r--r-- | src/wayland/shell.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/wayland/shell.c b/src/wayland/shell.c new file mode 100644 index 0000000..ef72512 --- /dev/null +++ b/src/wayland/shell.c @@ -0,0 +1,398 @@ +#include "internal.h" + +static void wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial); +static const struct xdg_wm_base_listener wm_base_listener; + +static void presentation_clock_id(void *data, struct wp_presentation *presentation, + uint32_t clk_id) +{ + (void)presentation; + struct yasl_display *c = data; + struct timespec ts; + c->pres_clock = (clockid_t)clk_id; + c->pres_clock_known = clock_gettime(c->pres_clock, &ts) == 0; +} + +static uint64_t clock_ns(clockid_t clock) +{ + struct timespec ts; + if (clock_gettime(clock, &ts) != 0) + return 0; + return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; +} + +uint64_t conn_pres_to_mono(const struct yasl_display *c, uint64_t ns) +{ + if (c->pres_clock == CLOCK_MONOTONIC) + return ns; + uint64_t mono = clock_ns(CLOCK_MONOTONIC); + uint64_t pres = clock_ns(c->pres_clock); + return ns + mono - pres; +} + +uint64_t conn_mono_to_pres(const struct yasl_display *c, uint64_t ns) +{ + if (c->pres_clock == CLOCK_MONOTONIC) + return ns; + uint64_t mono = clock_ns(CLOCK_MONOTONIC); + uint64_t pres = clock_ns(c->pres_clock); + return ns + pres - mono; +} + +static const struct wp_presentation_listener presentation_listener = { + .clock_id = presentation_clock_id, +}; + +static void output_geometry(void *data, struct wl_output *output, + int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, + int32_t subpixel, const char *make, const char *model, int32_t transform) +{ + (void)data; (void)output; (void)x; (void)y; (void)physical_width; + (void)physical_height; (void)subpixel; (void)make; (void)model; + (void)transform; +} + +static void output_mode(void *data, struct wl_output *output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) +{ + (void)output; (void)width; (void)height; + struct wl_conn_output *o = data; + if ((flags & WL_OUTPUT_MODE_CURRENT) && refresh > 0) + o->pending_refresh_ns = (uint32_t)(1000000000000ull / (uint32_t)refresh); +} + +static void conn_follow_outputs(struct yasl_display *c) +{ + for (struct yasl_surface *w = c->surfaces; w; w = w->conn_next) + surface_follow_outputs(w); +} + +static void output_done(void *data, struct wl_output *output) +{ + (void)output; + struct wl_conn_output *o = data; + if (o->pending_scale == o->scale && o->pending_refresh_ns == o->refresh_ns) + return; + o->scale = o->pending_scale; + o->refresh_ns = o->pending_refresh_ns; + conn_follow_outputs(o->conn); +} + +static void output_scale(void *data, struct wl_output *output, int32_t factor) +{ + (void)output; + struct wl_conn_output *o = data; + if (factor > 0) + o->pending_scale = factor; +} + +static void output_name(void *data, struct wl_output *output, const char *name) +{ (void)data; (void)output; (void)name; } + +static void output_description(void *data, struct wl_output *output, const char *description) +{ (void)data; (void)output; (void)description; } + +static const struct wl_output_listener output_listener = { + .name = output_name, + .description = output_description, + .geometry = output_geometry, + .mode = output_mode, + .done = output_done, + .scale = output_scale, +}; + +static void conn_bind_output(struct yasl_display *c, struct wl_registry *registry, + uint32_t name, uint32_t version) +{ + struct wl_conn_output *o = calloc(1, sizeof(*o)); + if (!o) return; + o->proxy = wl_registry_bind(registry, name, &wl_output_interface, + version < 3 ? version : 3); + if (!o->proxy) { + free(o); + return; + } + o->conn = c; + o->name = name; + o->scale = o->pending_scale = 1; + o->mask = ~c->output_masks & (c->output_masks + 1); + c->output_masks |= o->mask; + struct wl_conn_output **tail = &c->outputs; + while (*tail) + tail = &(*tail)->next; + *tail = o; + wl_output_add_listener(o->proxy, &output_listener, o); +} + +static void output_destroy(struct wl_conn_output *o) +{ + if (wl_output_get_version(o->proxy) >= WL_OUTPUT_RELEASE_SINCE_VERSION) + wl_output_release(o->proxy); + else + wl_output_destroy(o->proxy); + o->conn->output_masks &= ~o->mask; + free(o); +} + +static bool conn_remove_output(struct yasl_display *c, uint32_t name) +{ + struct wl_conn_output **link = &c->outputs; + while (*link && (*link)->name != name) + link = &(*link)->next; + struct wl_conn_output *o = *link; + if (!o) + return false; + *link = o->next; + for (struct yasl_surface *w = c->surfaces; w; w = w->conn_next) + w->outputs &= ~o->mask; + output_destroy(o); + conn_follow_outputs(c); + return true; +} + +void conn_destroy_outputs(struct yasl_display *c) +{ + while (c->outputs) { + struct wl_conn_output *o = c->outputs; + c->outputs = o->next; + output_destroy(o); + } +} + +uint64_t conn_output_mask(const struct yasl_display *c, const struct wl_output *proxy) +{ + for (const struct wl_conn_output *o = c->outputs; o; o = o->next) + if (o->proxy == proxy) + return o->mask; + return 0; +} + +uint32_t conn_output_scale_120(const struct yasl_display *c, uint64_t outputs) +{ + int32_t scale = 0; + for (const struct wl_conn_output *o = c->outputs; o; o = o->next) + if ((o->mask & outputs) && o->scale > scale) + scale = o->scale; + return (uint32_t)scale * YASL_SCALE_UNIT; +} + +static void registry_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct yasl_display *c = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + c->compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, version < 6 ? version : 6); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + c->shm = wl_registry_bind(registry, name, + &wl_shm_interface, version < 2 ? version : 2); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + conn_bind_output(c, registry, name, version); + } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + c->data_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, + version < 3 ? version : 3); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + c->wm_base = wl_registry_bind(registry, name, + &xdg_wm_base_interface, version < 7 ? version : 7); + if (c->wm_base) + xdg_wm_base_add_listener(c->wm_base, &wm_base_listener, c); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + c->decor_mgr = wl_registry_bind(registry, name, + &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) { + c->primary_selection_mgr = wl_registry_bind(registry, name, + &zwp_primary_selection_device_manager_v1_interface, 1); + for (struct seat *s = c->seats; s; s = s->next) + if (s->clipboard) + clipboard_set_primary_manager(s->clipboard, c->primary_selection_mgr); + } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + c->cursor_shape_mgr = wl_registry_bind(registry, name, + &wp_cursor_shape_manager_v1_interface, 1); + } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + c->scale_mgr = wl_registry_bind(registry, name, + &wp_fractional_scale_manager_v1_interface, 1); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + c->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); + } else if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) { + c->tablet_mgr = wl_registry_bind(registry, name, + &zwp_tablet_manager_v2_interface, 1); + } else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { + c->text_input_mgr = wl_registry_bind(registry, name, + &zwp_text_input_manager_v3_interface, 1); + } else if (strcmp(interface, wp_tearing_control_manager_v1_interface.name) == 0) { + c->tearing_mgr = wl_registry_bind(registry, name, + &wp_tearing_control_manager_v1_interface, 1); + } else if (strcmp(interface, wp_presentation_interface.name) == 0) { + c->presentation = wl_registry_bind(registry, name, + &wp_presentation_interface, 1); + if (c->presentation) + wp_presentation_add_listener(c->presentation, &presentation_listener, c); + } else if (strcmp(interface, wp_fifo_manager_v1_interface.name) == 0) { + c->fifo_mgr = wl_registry_bind(registry, name, + &wp_fifo_manager_v1_interface, 1); + } else if (strcmp(interface, wp_commit_timing_manager_v1_interface.name) == 0) { + c->commit_timing_mgr = wl_registry_bind(registry, name, + &wp_commit_timing_manager_v1_interface, 1); + } else if (strcmp(interface, xdg_toplevel_drag_manager_v1_interface.name) == 0) { + c->toplevel_drag_mgr = wl_registry_bind(registry, name, + &xdg_toplevel_drag_manager_v1_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat_bind(c, registry, name, version); + } +} + +static void registry_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + (void)registry; + struct yasl_display *c = data; + if (c && !conn_remove_output(c, name)) + seat_remove(c, name); +} + +const struct wl_registry_listener registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove, +}; + +static void wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) +{ + (void)data; + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + .ping = wm_base_ping, +}; + +static void notify_state(struct yasl_surface *w) +{ + if (w->cbs && w->cbs->state) + w->cbs->state(w, yasl_surface_state(w), w->ud); +} + +static void toplevel_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + (void)toplevel; + struct yasl_surface *w = data; + + bool fs = false, max = false, act = false, susp = false, tiled = false; + uint32_t *s; + wl_array_for_each(s, states) { + switch (*s) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: fs = true; break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: max = true; break; + case XDG_TOPLEVEL_STATE_ACTIVATED: act = true; break; + case XDG_TOPLEVEL_STATE_SUSPENDED: susp = true; break; + case XDG_TOPLEVEL_STATE_TILED_LEFT: + case XDG_TOPLEVEL_STATE_TILED_RIGHT: + case XDG_TOPLEVEL_STATE_TILED_TOP: + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: tiled = true; break; + default: break; + } + } + + if (fs != w->fullscreen || max != w->maximized || + act != w->activated || susp != w->suspended) { + w->fullscreen = fs; + w->maximized = max; + w->activated = act; + w->suspended = susp; + notify_state(w); + } + + w->tiled = tiled; + w->req_width = width; + w->req_height = height; +} + +static void toplevel_close(void *data, struct xdg_toplevel *toplevel) +{ + (void)toplevel; + surface_fail(data); +} + +static void toplevel_configure_bounds(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height) +{ + (void)toplevel; + struct yasl_surface *w = data; + w->bound_width = width; + w->bound_height = height; + if ((width > 0 && w->logical_width > width) || + (height > 0 && w->logical_height > height)) { + w->size_pending = true; + yasl_surface_schedule_redraw(w); + } +} + +static void toplevel_wm_capabilities(void *data, struct xdg_toplevel *toplevel, + struct wl_array *capabilities) +{ + (void)data; (void)toplevel; (void)capabilities; +} + +static const struct xdg_toplevel_listener toplevel_listener = { + .configure = toplevel_configure, + .close = toplevel_close, + .configure_bounds = toplevel_configure_bounds, + .wm_capabilities = toplevel_wm_capabilities, +}; + +static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + struct yasl_surface *w = data; + (void)xdg_surface; + w->pending_serial = serial; + w->has_pending_serial = true; + w->size_pending = true; + w->configured = true; + yasl_surface_schedule_redraw(w); +} + +const struct xdg_surface_listener shell_surface_listener = { + .configure = xdg_surface_configure, +}; + +static void toplevel_decoration_configure(void *data, + struct zxdg_toplevel_decoration_v1 *toplevel_decor, uint32_t mode) +{ + (void)toplevel_decor; + struct yasl_surface *w = data; + bool was_server = w->server_decorated; + w->server_decorated = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + if (was_server != w->server_decorated) + notify_state(w); +} + +static const struct zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener = { + .configure = toplevel_decoration_configure, +}; + +int surface_init_shell(struct yasl_surface *w, const char *title) +{ + w->xdg_surface = xdg_wm_base_get_xdg_surface(w->conn->wm_base, w->surface); + if (!w->xdg_surface) return -1; + xdg_surface_add_listener(w->xdg_surface, &shell_surface_listener, w); + w->toplevel = xdg_surface_get_toplevel(w->xdg_surface); + if (!w->toplevel) return -1; + xdg_toplevel_add_listener(w->toplevel, &toplevel_listener, w); + + yasl_surface_set_title(w, title ? title : "yasl_app"); + + const char *app_id = w->conn->app_id ? w->conn->app_id : "yasl_app"; + xdg_toplevel_set_app_id(w->toplevel, app_id); + xdg_toplevel_set_min_size(w->toplevel, YASL_MIN_LOGICAL_PX, YASL_MIN_LOGICAL_PX); + + if (w->conn->decor_mgr) { + w->toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration( + w->conn->decor_mgr, w->toplevel); + if (!w->toplevel_decor) return -1; + zxdg_toplevel_decoration_v1_add_listener(w->toplevel_decor, + &toplevel_decoration_listener, w); + zxdg_toplevel_decoration_v1_set_mode(w->toplevel_decor, + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } + return 0; +} |
