diff options
Diffstat (limited to 'src/wayland/surface.c')
| -rw-r--r-- | src/wayland/surface.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/src/wayland/surface.c b/src/wayland/surface.c new file mode 100644 index 0000000..da334e4 --- /dev/null +++ b/src/wayland/surface.c @@ -0,0 +1,430 @@ +#include "internal.h" +#include <assert.h> + +static void display_free(struct yasl_display *c); + +static void display_cancel_read(struct yasl_display *c) +{ + if (!c->reading) + return; + wl_display_cancel_read(c->wl); + c->reading = false; +} + +static void report_display_error(struct yasl_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 yasl_display *c) +{ + c->depth++; +} + +void display_leave(struct yasl_display *c) +{ + if (--c->depth > 0) + return; + keyboard_settle_focus(c); + while (c->dead) { + struct yasl_surface *w = c->dead; + c->dead = w->dead_next; + free(w); + } +} + +static void display_lose(struct yasl_display *c) +{ + if (c->lost) + return; + c->lost = true; + report_display_error(c); + display_cancel_read(c); + display_enter(c); + for (struct yasl_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 yasl_surface *w) +{ + if (w->closed) + return; + w->closed = true; + if (w->cbs->close) + w->cbs->close(w, w->ud); +} + +static int display_ready(int fd, short revents, void *ud) +{ + (void)fd; + struct yasl_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 (wl_display_dispatch_pending(c->wl) < 0 || + (revents & (POLLERR | POLLHUP | POLLNVAL))) + display_lose(c); + display_leave(c); + return 0; +} + +void yasl_display_dispatch(struct yasl_display *c) +{ + if (!c || c->lost || c->reading) + return; + display_enter(c); + if (wl_display_dispatch_pending(c->wl) < 0) + display_lose(c); + display_leave(c); +} + +static void display_arm(struct yasl_display *c) +{ + while (!c->reading && wl_display_prepare_read(c->wl) != 0) { + if (wl_display_dispatch_pending(c->wl) < 0) { + display_lose(c); + return; + } + } + c->reading = true; + short events = POLLIN; + if (wl_display_flush(c->wl) < 0) { + if (errno != EAGAIN) { + display_lose(c); + return; + } + events |= POLLOUT; + } + if (c->loop) + yasl_loop_set_fd_events(c->loop, c->watch, events); +} + +void yasl_display_flush(struct yasl_display *c) +{ + if (!c || c->lost) + return; + display_enter(c); + for (struct yasl_surface *w = c->surfaces, *next; w; w = next) { + next = w->conn_next; + surface_draw_frame(w); + } + if (!c->lost) + display_arm(c); + display_leave(c); +} + +static void display_free(struct yasl_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); + free(c->app_id); + free(c); +} + +struct yasl_display *yasl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) +{ + struct yasl_display *c = calloc(1, sizeof(*c)); + if (!c) return NULL; + c->loop = loop; + if (spec) { + c->cbs = spec->cbs; + 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->registry = wl_display_get_registry(c->wl); + if (!c->registry) goto err; + wl_registry_add_listener(c->registry, ®istry_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(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 yasl_display_close(struct yasl_display *c) +{ + if (!c) return; + assert(!c->surfaces && "destroy every surface before its display"); + display_free(c); +} + +uint32_t yasl_display_caps(const struct yasl_display *c) +{ + if (!c) return 0; + uint32_t caps = YASL_CAP_POPUPS | YASL_CAP_CSD; + 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 yasl_display *c, struct yasl_surface *w) +{ + w->conn = c; + w->conn_next = c->surfaces; + c->surfaces = w; +} + +static void conn_unlink(struct yasl_surface *w) +{ + struct yasl_display *c = w->conn; + if (!c) return; + for (struct yasl_surface **p = &c->surfaces; *p; p = &(*p)->conn_next) { + if (*p == w) { + *p = w->conn_next; + break; + } + } + if (c->paste_target == w) + c->paste_target = NULL; + for (struct seat *s = c->seats; s; s = s->next) + seat_forget_surface(s, w); + w->conn = NULL; +} + +struct yasl_surface *conn_surface_for(struct yasl_display *c, struct wl_surface *s) +{ + for (struct yasl_surface *w = c->surfaces; w && s; w = w->conn_next) + if (w->surface == s) + return w; + return NULL; +} + +static bool spec_valid(const struct yasl_surface_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 yasl_surface *yasl_surface_create_toplevel(struct yasl_display *c, + const struct yasl_surface_spec *spec) +{ + if (!c || c->lost || !spec_valid(spec)) + return NULL; + int width = spec->size.x, height = spec->size.y; + display_cancel_read(c); + struct yasl_surface *w = calloc(1, sizeof(*w)); + if (!w) return NULL; + display_enter(c); + conn_link(c, w); + yasl_surface_set_callbacks(w, NULL, NULL); + 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; + 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: + yasl_surface_destroy(w); + display_leave(c); + return NULL; +} + +void yasl_surface_set_callbacks(struct yasl_surface *w, const struct yasl_surface_cbs *cbs, void *ud) +{ + if (w && !w->closed) { + static const struct yasl_surface_cbs no_callbacks = {0}; + w->cbs = cbs ? cbs : &no_callbacks; + w->ud = ud; + } +} + +void yasl_surface_destroy(struct yasl_surface *w) +{ + if (!w || w->destroyed) return; + for (struct yasl_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_destroy(w->surface); + struct yasl_display *c = w->conn; + conn_unlink(w); + static const struct yasl_surface_cbs no_callbacks = {0}; + *w = (struct yasl_surface){ + .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 yasl_display *yasl_surface_display(const struct yasl_surface *w) +{ + return w ? w->conn : NULL; +} + +void yasl_surface_schedule_redraw(struct yasl_surface *w) +{ + if (w) w->want_draw = true; +} + +struct yasl_vector2i yasl_surface_size(const struct yasl_surface *w) +{ + return w ? (struct yasl_vector2i){ .x = w->width, .y = w->height } : (struct yasl_vector2i){0}; +} + +uint32_t yasl_surface_scale_120(const struct yasl_surface *w) +{ + return w ? w->scale_120 : YASL_SCALE_UNIT; +} + +void yasl_surface_set_background_color(struct yasl_surface *w, uint32_t bg) +{ + (void)w; (void)bg; +} + +bool yasl_surface_start_interactive_move(struct yasl_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 yasl_surface_start_interactive_resize(struct yasl_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 yasl_surface_show_window_menu(struct yasl_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 *yasl_display_get_native_handle(struct yasl_display *d) +{ + return d ? d->wl : NULL; +} + +void *yasl_surface_get_native_handle(struct yasl_surface *s) +{ + return s ? s->surface : NULL; +} |
