From de61d0640698d8c7b9ffaa095ff80094e73bee75 Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Tue, 30 Jun 2020 23:18:50 +0200 Subject: [PATCH] desktop: add new view abstraction with xdg shell implementation --- desktop/view.c | 157 ++++++++++++++++++++++++++++++++++++++++ desktop/view.h | 91 ++++++++++++++++++++++++ desktop/xdg_shell.c | 170 ++++++++++++++++++++++++++++++++++++++++++++ desktop/xdg_shell.h | 23 ++++++ meson.build | 4 ++ 5 files changed, 445 insertions(+) create mode 100644 desktop/view.c create mode 100644 desktop/view.h create mode 100644 desktop/xdg_shell.c create mode 100644 desktop/xdg_shell.h diff --git a/desktop/view.c b/desktop/view.c new file mode 100644 index 0000000..e4c6348 --- /dev/null +++ b/desktop/view.c @@ -0,0 +1,157 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018-2020 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include + +#include "output.h" +#include "view.h" + +static bool +cage_view_extends_output_layout(struct cg_view *view, struct wlr_box *output_box) +{ + assert(view->impl->get_geometry != NULL); + + int width, height; + view->impl->get_geometry(view, &width, &height); + + return (output_box->height < height || output_box->width < width); +} + +bool +cage_view_is_primary(struct cg_view *view) +{ + assert(view != NULL); + assert(view->impl->is_primary != NULL); + return view->impl->is_primary(view); +} + +static void +cage_view_maximize(struct cg_view *view, struct wlr_box *output_box) +{ + assert(view->impl->maximize != NULL); + + view->impl->maximize(view, output_box->width, output_box->height); +} + +static void +cage_view_center(struct cg_view *view, struct wlr_box *output_box) +{ + // No-op placeholder +} + +void +cage_view_position(struct cg_view *view) +{ + assert(view != NULL); + + struct wlr_box output_box = {0}; + cage_output_get_geometry(view->output, &output_box); + + if (cage_view_is_primary(view) || cage_view_extends_output_layout(view, &output_box)) { + cage_view_maximize(view, &output_box); + } else { + cage_view_center(view, &output_box); + } +} + +void +cage_view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *user_data) +{ + assert(view != NULL); + assert(view->impl->for_each_surface != NULL); + view->impl->for_each_surface(view, iterator, user_data); +} + +char * +cage_view_get_title(struct cg_view *view) +{ + assert(view != NULL); + assert(view->impl->get_title != NULL); + + const char *title = view->impl->get_title(view); + if (!title) { + return NULL; + } + return strndup(title, strlen(title)); +} + +void +cage_view_activate(struct cg_view *view, bool activate) +{ + assert(view != NULL); + assert(view->impl->activate != NULL); + view->impl->activate(view, activate); +} + +bool +cage_view_is_mapped(struct cg_view *view) +{ + assert(view != NULL); + return view->wlr_surface != NULL; +} + +void +cage_view_unmap(struct cg_view *view) +{ + assert(view != NULL); + assert(cage_view_is_mapped(view)); + + wl_signal_emit(&view->events.unmap, view); + + wl_list_remove(&view->link); + wl_list_init(&view->link); + + view->wlr_surface = NULL; + + assert(!cage_view_is_mapped(view)); +} + +void +cage_view_map(struct cg_view *view, struct wlr_surface *surface) +{ + assert(view != NULL); + assert(surface != NULL); + assert(!cage_view_is_mapped(view)); + + view->wlr_surface = surface; + + wl_list_insert(&view->output->views, &view->link); + cage_view_position(view); + + wl_signal_emit(&view->events.map, view); + + assert(cage_view_is_mapped(view)); +} + +void +cage_view_fini(struct cg_view *view) +{ + assert(view != NULL); + assert(!cage_view_is_mapped(view)); + view->output = NULL; +} + +void +cage_view_init(struct cg_view *view, enum cg_view_type type, const struct cg_view_impl *impl, struct cg_output *output) +{ + assert(view != NULL); + assert(impl != NULL); + assert(output != NULL); + + view->type = type; + view->impl = impl; + view->output = output; + + wl_signal_init(&view->events.map); + wl_signal_init(&view->events.unmap); +} diff --git a/desktop/view.h b/desktop/view.h new file mode 100644 index 0000000..532c105 --- /dev/null +++ b/desktop/view.h @@ -0,0 +1,91 @@ +#ifndef CG_VIEW_H +#define CG_VIEW_H + +#include +#include +#include + +#include "output.h" + +enum cg_view_type { + CAGE_XDG_SHELL_VIEW, +}; + +struct cg_view { + enum cg_view_type type; + const struct cg_view_impl *impl; + + struct wl_list link; // cg_output::views + struct wlr_surface *wlr_surface; + struct cg_output *output; + + struct { + /** + * Proxy wlr_surface's map signal to the compositor. + * Note that cg_view has already taken care of setting up the + * view; only the compositor-specific handling such as focus + * changing need to be implemented. + */ + struct wl_signal map; + + /** + * Proxy wlr_surface's unmap signal to the compositor. + * Note that cg_view will take care of unmapping the view; only + * the compositor-specific handling such as focus changing need + * to be implemented. + */ + struct wl_signal unmap; + } events; +}; + +struct cg_view_impl { + /** + * Get the width and height of a view. + */ + void (*get_geometry)(struct cg_view *view, int *width_out, int *height_out); + + /** + * Maximize the given view on its output. + */ + void (*maximize)(struct cg_view *view, int output_width, int output_height); + + /** + * A primary view is a main application window. That is, it is a toplevel window + * that has no parent. For example, a dialog window is not a primary view, nor is + * e.g. a toolbox window. + * + * Cage uses this heuristic to decide which views to maximize on their outputs + * and which views to display as dialogs, with the caveat that dialogs that extend + * their output are maximized regardless. + */ + bool (*is_primary)(struct cg_view *view); + + /** + * Get a view's title. + */ + char *(*get_title)(struct cg_view *view); + + /** + * Execute `iterator` on every surface belonging to this view. + */ + void (*for_each_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *user_data); + + /** + * Activate a view. + */ + void (*activate)(struct cg_view *view, bool activate); +}; + +bool cage_view_is_primary(struct cg_view *view); +void cage_view_position(struct cg_view *view); +void cage_view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *user_data); +char *cage_view_get_title(struct cg_view *view); +void cage_view_activate(struct cg_view *view, bool activate); +bool cage_view_is_mapped(struct cg_view *view); +void cage_view_unmap(struct cg_view *view); +void cage_view_map(struct cg_view *view, struct wlr_surface *surface); +void cage_view_fini(struct cg_view *view); +void cage_view_init(struct cg_view *view, enum cg_view_type type, const struct cg_view_impl *impl, + struct cg_output *output); + +#endif diff --git a/desktop/xdg_shell.c b/desktop/xdg_shell.c new file mode 100644 index 0000000..fe19e7d --- /dev/null +++ b/desktop/xdg_shell.c @@ -0,0 +1,170 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018-2020 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include +#include + +#include "view.h" +#include "xdg_shell.h" + +static struct cg_xdg_shell_view * +xdg_shell_view_from_view(struct cg_view *view) +{ + return (struct cg_xdg_shell_view *) view; +} + +static void +for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *user_data) +{ + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + wlr_xdg_surface_for_each_surface(xdg_shell_view->xdg_surface, iterator, user_data); +} + +static void +get_geometry(struct cg_view *view, int *width_out, int *height_out) +{ + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + struct wlr_box geometry; + + wlr_xdg_surface_get_geometry(xdg_shell_view->xdg_surface, &geometry); + *width_out = geometry.width; + *height_out = geometry.height; +} + +static void +maximize(struct cg_view *view, int output_width, int output_height) +{ + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_surface, output_width, output_height); + wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_surface, true); +} + +static bool +is_primary(struct cg_view *view) +{ + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + struct wlr_xdg_surface *xdg_surface = xdg_shell_view->xdg_surface; + + struct wlr_xdg_surface *parent = xdg_surface->toplevel->parent; + enum wlr_xdg_surface_role role = xdg_surface->role; + + return parent == NULL && role == WLR_XDG_SURFACE_ROLE_TOPLEVEL; +} + +static char * +get_title(struct cg_view *view) +{ + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + return xdg_shell_view->xdg_surface->toplevel->title; +} + +static void +activate(struct cg_view *view, bool activate) +{ + struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_surface, activate); +} + +static void +handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *user_data) +{ + struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap); + struct cg_view *view = &xdg_shell_view->view; + + assert(cage_view_is_mapped(view)); + + wl_list_remove(&xdg_shell_view->commit.link); + wl_list_remove(&xdg_shell_view->request_fullscreen.link); + + cage_view_unmap(view); + + assert(!cage_view_is_mapped(view)); +} + +static void +handle_xdg_shell_surface_request_fullscreen(struct wl_listener *listener, void *user_data) +{ + struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen); + struct wlr_xdg_toplevel_set_fullscreen_event *event = user_data; + wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_surface, event->fullscreen); +} + +static void +handle_xdg_shell_surface_commit(struct wl_listener *listener, void *user_data) +{ +} + +static void +handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *user_data) +{ + struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, destroy); + struct cg_view *view = &xdg_shell_view->view; + + if (cage_view_is_mapped(view)) { + handle_xdg_shell_surface_unmap(listener, user_data); + } + + wl_list_remove(&xdg_shell_view->map.link); + wl_list_remove(&xdg_shell_view->unmap.link); + wl_list_remove(&xdg_shell_view->destroy.link); + xdg_shell_view->xdg_surface = NULL; + + cage_view_fini(view); + free(xdg_shell_view); +} + +static void +handle_xdg_shell_surface_map(struct wl_listener *listener, void *user_data) +{ + struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map); + struct cg_view *view = &xdg_shell_view->view; + + assert(!cage_view_is_mapped(view)); + + xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit; + wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, &xdg_shell_view->commit); + xdg_shell_view->request_fullscreen.notify = handle_xdg_shell_surface_request_fullscreen; + wl_signal_add(&xdg_shell_view->xdg_surface->toplevel->events.request_fullscreen, + &xdg_shell_view->request_fullscreen); + + cage_view_map(view, xdg_shell_view->xdg_surface->surface); + + assert(cage_view_is_mapped(view)); +} + +static const struct cg_view_impl xdg_shell_view_impl = { + .for_each_surface = for_each_surface, + .get_geometry = get_geometry, + .maximize = maximize, + .is_primary = is_primary, + .get_title = get_title, + .activate = activate, +}; + +void +cage_xdg_shell_view_init(struct cg_xdg_shell_view *xdg_shell_view, struct wlr_xdg_surface *xdg_surface, + struct cg_output *output) +{ + assert(xdg_shell_view != NULL); + assert(xdg_surface != NULL); + assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + + cage_view_init(&xdg_shell_view->view, CAGE_XDG_SHELL_VIEW, &xdg_shell_view_impl, output); + xdg_shell_view->xdg_surface = xdg_surface; + xdg_shell_view->xdg_surface->data = xdg_shell_view; + + xdg_shell_view->map.notify = handle_xdg_shell_surface_map; + wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); + xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap; + wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap); + xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy; + wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy); +} diff --git a/desktop/xdg_shell.h b/desktop/xdg_shell.h new file mode 100644 index 0000000..fa6aa65 --- /dev/null +++ b/desktop/xdg_shell.h @@ -0,0 +1,23 @@ +#ifndef CG_XDG_SHELL_H +#define CG_XDG_SHELL_H + +#include + +#include "output.h" +#include "view.h" + +struct cg_xdg_shell_view { + struct cg_view view; + struct wlr_xdg_surface *xdg_surface; + + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener request_fullscreen; +}; + +void cage_xdg_shell_view_init(struct cg_xdg_shell_view *xdg_shell_view, struct wlr_xdg_surface *wlr_xdg_surface, + struct cg_output *output); + +#endif diff --git a/meson.build b/meson.build index 33b882d..e333242 100644 --- a/meson.build +++ b/meson.build @@ -121,6 +121,8 @@ endif cageng_sources = [ 'desktop/output.c', + 'desktop/view.c', + 'desktop/xdg_shell.c', 'cageng.c', ] @@ -129,6 +131,8 @@ cageng_headers = [ output: 'config.h', configuration: conf_data), 'desktop/output.h', + 'desktop/view.h', + 'desktop/xdg_shell.h', 'serverng.h', ]