summaryrefslogtreecommitdiff
path: root/src/platform/wayland/surface.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/wayland/surface.c')
-rw-r--r--src/platform/wayland/surface.c347
1 files changed, 231 insertions, 116 deletions
diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c
index 31096f4..e04a594 100644
--- a/src/platform/wayland/surface.c
+++ b/src/platform/wayland/surface.c
@@ -3,6 +3,7 @@
#include <errno.h>
static void display_free(struct wayland_display *c);
+static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t interval_ns);
static void display_cancel_read(struct wayland_display *c)
{
@@ -12,19 +13,16 @@ static void display_cancel_read(struct wayland_display *c)
c->reading = false;
}
+/* A compositor going away is expected; a protocol error is a YASL bug worth reporting. */
static void report_display_error(struct wayland_display *c)
{
- int err = wl_display_get_error(c->wl);
- if (!err)
+ if (wl_display_get_error(c->wl) != EPROTO)
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);
+ fprintf(stderr, "[yasl] protocol error %u on %s (object %u)\n",
+ protocol_err, iface ? iface->name : "?", object_id);
}
void display_leave(struct wayland_display *c)
@@ -39,20 +37,32 @@ void display_leave(struct wayland_display *c)
}
}
+/* Windows outlive the connection. Its objects stay until the next dispatch, after
+ * the application has seen DISPLAY_LOST and released GPU objects built on them. */
static void display_lose(struct wayland_display *c)
{
if (c->lost)
return;
c->lost = true;
+ c->stale = true;
+ c->base.offline = true;
report_display_error(c);
display_cancel_read(c);
+ if (c->loop && c->watch > 0)
+ yasl_private_loop_del_fd(c->loop, c->watch);
+ c->watch = 0;
c->depth++;
+ conn_drag_end(c);
for (struct wayland_surface *w = c->surfaces, *next; w; w = next) {
next = w->conn_next;
- surface_fail(w);
+ if (w->parent)
+ surface_fail(w);
}
display_leave(c);
yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_LOST);
+ c->reconnect_delay_ns = YASL_PRIVATE_MS_TO_NS(100);
+ if (c->loop && !c->reconnect_timer)
+ c->reconnect_timer = yasl_private_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c);
}
void surface_fail(struct wayland_surface *w)
@@ -131,21 +141,28 @@ static inline bool display_has_immediate_work(const struct wayland_display *c)
int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms)
{
- if (!c || c->lost)
+ if (!c)
return -1;
c->depth++;
+ if (c->lost) {
+ /* Reconnect attempts run as a loop timer. */
+ int n = c->loop ? yasl_private_loop_step(c->loop, timeout_ms) : 0;
+ display_leave(c);
+ return n;
+ }
+ /* Losing the connection is reported as DISPLAY_LOST, not as a failed wait. */
if (wl_display_dispatch_pending(c->wl) < 0) {
display_lose(c);
display_leave(c);
- return -1;
+ return 0;
}
display_arm(c);
if (c->lost) {
display_leave(c);
- return -1;
+ return 0;
}
if (timeout_ms != 0 && display_has_immediate_work(c)) {
@@ -161,10 +178,10 @@ int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms)
display_cancel_read(c);
}
- if (wl_display_dispatch_pending(c->wl) < 0) {
+ if (c->wl && wl_display_dispatch_pending(c->wl) < 0) {
display_lose(c);
display_leave(c);
- return -1;
+ return 0;
}
display_leave(c);
@@ -187,40 +204,126 @@ void wayland_display_flush(struct wayland_display *c)
display_leave(c);
}
-static void display_free(struct wayland_display *c)
+#define RELEASE(field, destroy) do { if (c->field) { destroy(c->field); c->field = NULL; } } while (0)
+
+static void connection_release(struct wayland_display *c)
{
+ for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next)
+ surface_release_native(w);
conn_drag_end(c);
if (c->loop && c->watch > 0) yasl_private_loop_del_fd(c->loop, c->watch);
+ c->watch = 0;
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->relative_pointer_mgr) zwp_relative_pointer_manager_v1_destroy(c->relative_pointer_mgr);
- if (c->pointer_constraints) zwp_pointer_constraints_v1_destroy(c->pointer_constraints);
- if (c->system_bell) xdg_system_bell_v1_destroy(c->system_bell);
- 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);
+ RELEASE(toplevel_drag_mgr, xdg_toplevel_drag_manager_v1_destroy);
+ RELEASE(primary_selection_mgr, zwp_primary_selection_device_manager_v1_destroy);
+ RELEASE(data_manager, wl_data_device_manager_destroy);
+ RELEASE(cursor_shape_mgr, wp_cursor_shape_manager_v1_destroy);
+ RELEASE(relative_pointer_mgr, zwp_relative_pointer_manager_v1_destroy);
+ RELEASE(pointer_constraints, zwp_pointer_constraints_v1_destroy);
+ RELEASE(system_bell, xdg_system_bell_v1_destroy);
+ RELEASE(cursor_theme, wl_cursor_theme_destroy);
+ RELEASE(tablet_mgr, zwp_tablet_manager_v2_destroy);
+ RELEASE(text_input_mgr, zwp_text_input_manager_v3_destroy);
+ RELEASE(tearing_mgr, wp_tearing_control_manager_v1_destroy);
+ RELEASE(fifo_mgr, wp_fifo_manager_v1_destroy);
+ RELEASE(commit_timing_mgr, wp_commit_timing_manager_v1_destroy);
+ RELEASE(presentation, wp_presentation_destroy);
+ RELEASE(scale_mgr, wp_fractional_scale_manager_v1_destroy);
+ RELEASE(viewporter, wp_viewporter_destroy);
+ RELEASE(decor_mgr, zxdg_decoration_manager_v1_destroy);
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);
+ RELEASE(wm_base, xdg_wm_base_destroy);
+ RELEASE(shm, wl_shm_destroy);
+ RELEASE(compositor, wl_compositor_destroy);
+ RELEASE(registry, wl_registry_destroy);
#if YASL_HAS_EGL
if (c->egl_initialized) eglTerminate(c->egl_display);
+ c->egl_initialized = false;
+ c->egl_display = EGL_NO_DISPLAY;
+ c->egl_api = 0;
#endif
- if (c->wl) wl_display_disconnect(c->wl);
+ RELEASE(wl, wl_display_disconnect);
+ c->base.native_handle = NULL;
+ c->cursor_theme_scale = 0;
+ c->pres_clock_known = false;
+ c->active_seat = NULL;
+}
+
+static bool connection_open(struct wayland_display *c)
+{
+ c->wl = wl_display_connect(NULL);
+ if (!c->wl) return false;
+ c->base.native_handle = c->wl;
+#if YASL_HAS_EGL
+ c->egl_display = eglGetDisplay((EGLNativeDisplayType)c->wl);
+ if (c->egl_display != EGL_NO_DISPLAY) {
+ EGLint major = 0, minor = 0;
+ c->egl_initialized = eglInitialize(c->egl_display, &major, &minor) == EGL_TRUE;
+ }
+#endif
+ c->registry = wl_display_get_registry(c->wl);
+ if (!c->registry) return false;
+ wl_registry_add_listener(c->registry, &registry_listener, c);
+ if (wl_display_roundtrip(c->wl) < 0 || !c->compositor || !c->shm || !c->wm_base)
+ return false;
+ surface_init_cursor(c);
+ if (c->loop) {
+ c->watch = yasl_private_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c);
+ if (c->watch <= 0) { c->watch = 0; return false; }
+ }
+ return display_input_init(c) == 0;
+}
+
+/* Recreates every window on a new connection; one that cannot be rebuilt is closed. */
+static void display_rebuild(struct wayland_display *c)
+{
+ for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next) {
+ if (w->parent || w->closed || w->destroyed) continue;
+ w->req_width = w->req_height = 0;
+ if (surface_build_toplevel(w, w->base.state & YASL_PRIVATE_STATE_FULLSCREEN) < 0 && !c->lost)
+ surface_fail(w);
+ }
+#if YASL_HAS_EGL
+ for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next)
+ if (w->base.presenter == YASL_PRIVATE_PRESENTER_GL && !w->closed && !w->gl_built && !surface_build_gl(w))
+ surface_fail(w);
+#endif
+ for (struct wayland_surface *w = c->surfaces; w && !c->lost; w = w->conn_next)
+ if (!w->closed) yasl_private_surface_notify_refresh(&w->base);
+}
+
+static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t interval_ns)
+{
+ (void)id; (void)interval_ns;
+ struct wayland_display *c = ud;
+ c->depth++;
+ if (c->stale) {
+ connection_release(c);
+ c->stale = false;
+ }
+ if (!connection_open(c)) {
+ connection_release(c);
+ display_leave(c);
+ uint64_t limit = YASL_PRIVATE_MS_TO_NS(2000);
+ c->reconnect_delay_ns = c->reconnect_delay_ns * 2 < limit ? c->reconnect_delay_ns * 2 : limit;
+ return c->reconnect_delay_ns;
+ }
+ c->reconnect_timer = 0;
+ c->lost = false;
+ c->base.offline = false;
+ yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_RESTORED);
+ display_rebuild(c);
+ display_leave(c);
+ return 0;
+}
+
+static void display_free(struct wayland_display *c)
+{
+ connection_release(c);
+ if (c->loop && c->reconnect_timer) yasl_private_loop_del_timer(c->loop, c->reconnect_timer);
+ if (c->xkb_ctx) xkb_context_unref(c->xkb_ctx);
if (c->owns_loop && c->loop) {
yasl_private_loop_destroy(c->loop);
c->loop = NULL;
@@ -251,29 +354,7 @@ struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, con
c->base.app_id = c->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;
-#if YASL_HAS_EGL
- c->egl_display = eglGetDisplay((EGLNativeDisplayType)c->wl);
- if (c->egl_display != EGL_NO_DISPLAY) {
- EGLint major = 0, minor = 0;
- c->egl_initialized = eglInitialize(c->egl_display, &major, &minor) == EGL_TRUE;
- }
-#endif
-
- 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_private_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)
+ if (!c->xkb_ctx || !connection_open(c))
goto err;
return c;
@@ -343,11 +424,91 @@ static bool spec_valid(const struct yasl_private_window_spec *spec)
: buffer_size(spec->size.x, spec->size.y, &bytes);
}
+int surface_build_toplevel(struct wayland_surface *w, bool fullscreen)
+{
+ struct wayland_display *c = w->conn;
+ w->surface = wl_compositor_create_surface(c->compositor);
+ if (!w->surface) return -1;
+ w->base.native_handle = w->surface;
+ wl_surface_set_user_data(w->surface, w);
+ if (surface_init_scaling(w) < 0) return -1;
+ if (surface_init_shell(w, w->title) < 0) return -1;
+ if (w->base.creation_flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) {
+ xdg_toplevel_set_min_size(w->toplevel, w->logical_width, w->logical_height);
+ xdg_toplevel_set_max_size(w->toplevel, w->logical_width, w->logical_height);
+ }
+ if (fullscreen)
+ xdg_toplevel_set_fullscreen(w->toplevel, NULL);
+ else if (w->base.state & YASL_PRIVATE_STATE_MAXIMIZED)
+ xdg_toplevel_set_maximized(w->toplevel);
+ if (w->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);
+ }
+ w->size_pending = true;
+ wl_surface_commit(w->surface);
+ if (wl_display_roundtrip(c->wl) < 0) {
+ display_lose(c);
+ return -1;
+ }
+ if (apply_pending_geometry(w) < 0 || w->closed) return -1;
+ return 0;
+}
+
+void surface_release_native(struct wayland_surface *w)
+{
+ surface_unlock_pointer(w);
+ w->ime_seat = NULL;
+#define DROP(field, destroy) do { if (w->field) { destroy(w->field); w->field = NULL; } } while (0)
+ DROP(tearing_control, wp_tearing_control_v1_destroy);
+#if YASL_HAS_EGL
+ struct wayland_display *c = w->conn;
+ if (w->egl_context) {
+ if (eglGetCurrentContext() == w->egl_context)
+ eglMakeCurrent(c->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroyContext(c->egl_display, w->egl_context);
+ w->egl_context = 0;
+ }
+ if (w->egl_surface) eglDestroySurface(c->egl_display, w->egl_surface);
+ w->egl_surface = 0;
+ DROP(egl_window, wl_egl_window_destroy);
+ w->gl_built = false;
+#endif
+ DROP(fifo, wp_fifo_v1_destroy);
+ DROP(commit_timer, wp_commit_timer_v1_destroy);
+ surface_feedback_destroy(w);
+ DROP(frame_cb, wl_callback_destroy);
+ for (int i = 0; i < NBUF; i++) buffer_destroy(&w->buffers[i]);
+ DROP(frac_scale, wp_fractional_scale_v1_destroy);
+ DROP(viewport, wp_viewport_destroy);
+ DROP(toplevel_decor, zxdg_toplevel_decoration_v1_destroy);
+ DROP(toplevel, xdg_toplevel_destroy);
+ DROP(popup, xdg_popup_destroy);
+ DROP(xdg_surface, xdg_surface_destroy);
+ if (w->surface) {
+ wl_surface_set_user_data(w->surface, NULL);
+ wl_surface_destroy(w->surface);
+ w->surface = NULL;
+ }
+#undef DROP
+ w->base.native_handle = NULL;
+ /* A Vulkan surface built on the old wl_surface is gone; allow a new one. */
+ if (w->base.presenter == YASL_PRIVATE_PRESENTER_VULKAN)
+ w->base.presenter = YASL_PRIVATE_PRESENTER_NONE;
+ w->configured = w->has_pending_serial = w->has_shown = false;
+ w->frame_active = w->want_draw = false;
+ w->outputs = 0;
+}
+
struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *c,
const struct yasl_private_window_spec *spec, void *graphics_device)
{
(void)graphics_device;
- if (!c || c->lost || !spec_valid(spec))
+ if (c && c->lost) { errno = ENOTCONN; return NULL; }
+ if (!c || !spec_valid(spec))
return NULL;
int width = spec->size.x, height = spec->size.y;
display_cancel_read(c);
@@ -360,6 +521,7 @@ struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *
w->base.presenter = (spec->external_gpu && !(spec->flags & YASL_PRIVATE_SURFACE_CPU))
? YASL_PRIVATE_PRESENTER_NONE : YASL_PRIVATE_PRESENTER_CPU;
wayland_surface_set_callbacks(w, NULL, NULL);
+ wayland_surface_set_title(w, spec->title ? spec->title : "yasl_private_app");
w->cursor = YASL_PRIVATE_CURSOR_TEXT;
w->req_width = width;
w->req_height = height;
@@ -368,40 +530,13 @@ struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *
w->scale_120 = YASL_PRIVATE_SCALE_UNIT;
w->pending_scale_120 = YASL_PRIVATE_SCALE_UNIT;
w->scale_factor = 1.0;
- w->size_pending = true;
+ w->tearing = spec->tearing;
+ w->opaque = spec->fullscreen;
w->clock.refresh_ns = c->outputs && c->outputs->refresh_ns
? c->outputs->refresh_ns : YASL_PRIVATE_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_private_vector2i){width, height};
w->base.scale_120 = YASL_PRIVATE_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;
- if (spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) {
- xdg_toplevel_set_min_size(w->toplevel, width, height);
- xdg_toplevel_set_max_size(w->toplevel, width, height);
- }
- 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;
+ if (surface_build_toplevel(w, spec->fullscreen) < 0) goto err;
display_leave(c);
return w;
@@ -426,37 +561,17 @@ 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");
- surface_unlock_pointer(w);
+ surface_release_native(w);
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);
+ free(w->title);
+ struct wayland_display *c = w->conn;
#if YASL_HAS_EGL
- if (w->egl_context) {
- if (eglGetCurrentContext() == w->egl_context)
- eglMakeCurrent(w->conn->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroyContext(w->conn->egl_display, w->egl_context);
- }
- if (w->egl_surface) eglDestroySurface(w->conn->egl_display, w->egl_surface);
- if (w->egl_window) wl_egl_window_destroy(w->egl_window);
+ for (struct wayland_surface *o = c ? c->surfaces : NULL; o; o = o->conn_next)
+ if (o->gl_share == w) o->gl_share = NULL;
#endif
- 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_private_window_cbs no_callbacks = {0};
*w = (struct wayland_surface){