From 786e28bdaccb8506834e862c9f36d6c9c0e7a4a7 Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Mon, 31 Dec 2018 19:52:47 +0100 Subject: [PATCH 1/4] Properly track mapping and unmapping We shouldn't render a window before it is mapped (obviously), but we render all windows in the view list. Hence, only insert the window once it is mapped. We could run into the case where a window is destroyed without being in the window list, so we now track unmapping again and remove windows from the list when they get unmapped. --- view.c | 10 ++++++++-- view.h | 2 ++ xdg_shell.c | 13 +++++++++---- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/view.c b/view.c index 8999ac3..04d833f 100644 --- a/view.c +++ b/view.c @@ -56,6 +56,13 @@ view_is_primary(struct cg_view *view) return view->is_primary(view); } +void +view_unmap(struct cg_view *view) +{ + wl_list_remove(&view->link); + view->wlr_surface = NULL; +} + void view_map(struct cg_view *view, struct wlr_surface *surface) { @@ -67,6 +74,7 @@ view_map(struct cg_view *view, struct wlr_surface *surface) view_center(view); } + wl_list_insert(&view->server->views, &view->link); seat_set_focus(view->server->seat, view); } @@ -76,7 +84,6 @@ view_destroy(struct cg_view *view) struct cg_server *server = view->server; bool terminate = view_is_primary(view); - wl_list_remove(&view->link); free(view); /* If this was our primary view, exit. */ @@ -91,7 +98,6 @@ cg_view_create(struct cg_server *server) struct cg_view *view = calloc(1, sizeof(struct cg_view)); view->server = server; - wl_list_insert(&server->views, &view->link); return view; } diff --git a/view.h b/view.h index 91efd54..5878778 100644 --- a/view.h +++ b/view.h @@ -25,6 +25,7 @@ struct cg_view { }; struct wl_listener destroy; + struct wl_listener unmap; struct wl_listener map; // TODO: allow applications to go to fullscreen from maximized? // struct wl_listener request_fullscreen; @@ -39,6 +40,7 @@ void view_activate(struct cg_view *view, bool activate); void view_maximize(struct cg_view *view); void view_center(struct cg_view *view); bool view_is_primary(struct cg_view *view); +void view_unmap(struct cg_view *view); void view_map(struct cg_view *view, struct wlr_surface *surface); void view_destroy(struct cg_view *view); struct cg_view *cg_view_create(struct cg_server *server); diff --git a/xdg_shell.c b/xdg_shell.c index a990946..c36980d 100644 --- a/xdg_shell.c +++ b/xdg_shell.c @@ -41,6 +41,13 @@ is_primary(struct cg_view *view) return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */ } +static void +handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *data) +{ + struct cg_view *view = wl_container_of(listener, view, unmap); + view_unmap(view); +} + static void handle_xdg_shell_surface_map(struct wl_listener *listener, void *data) { @@ -61,16 +68,14 @@ handle_xdg_shell_surface_new(struct wl_listener *listener, void *data) struct cg_server *server = wl_container_of(listener, server, new_xdg_shell_surface); struct wlr_xdg_surface *xdg_surface = data; - if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - return; - } - struct cg_view *view = cg_view_create(server); view->type = CAGE_XDG_SHELL_VIEW; view->xdg_surface = xdg_surface; view->map.notify = handle_xdg_shell_surface_map; wl_signal_add(&xdg_surface->events.map, &view->map); + view->unmap.notify = handle_xdg_shell_surface_unmap; + wl_signal_add(&xdg_surface->events.unmap, &view->unmap); view->destroy.notify = handle_xdg_shell_surface_destroy; wl_signal_add(&xdg_surface->events.destroy, &view->destroy); From 48f8f69556807b1146806313c3fce6838c127d38 Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Mon, 31 Dec 2018 20:44:20 +0100 Subject: [PATCH 2/4] view_get_geometry: only out width and height This is the only thing we need, as we don't use a view's x and y coordinates for placing windows. --- view.c | 8 ++++---- view.h | 2 +- xdg_shell.c | 8 ++++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/view.c b/view.c index 04d833f..0b11ae2 100644 --- a/view.c +++ b/view.c @@ -43,11 +43,11 @@ view_center(struct cg_view *view) int output_width, output_height; wlr_output_effective_resolution(output, &output_width, &output_height); - struct wlr_box geom; - view->get_geometry(view, &geom); + int width, height; + view->get_geometry(view, &width, &height); - view->x = (output_width - geom.width) / 2; - view->y = (output_height - geom.height) / 2; + view->x = (output_width - width) / 2; + view->y = (output_height - height) / 2; } bool diff --git a/view.h b/view.h index 5878778..749693c 100644 --- a/view.h +++ b/view.h @@ -32,7 +32,7 @@ struct cg_view { void (*activate)(struct cg_view *view, bool activate); void (*maximize)(struct cg_view *view, int output_width, int output_height); - void (*get_geometry)(struct cg_view *view, struct wlr_box *geom); + void (*get_geometry)(struct cg_view *view, int *width_out, int *height_out); bool (*is_primary)(struct cg_view *view); }; diff --git a/xdg_shell.c b/xdg_shell.c index c36980d..7babb2c 100644 --- a/xdg_shell.c +++ b/xdg_shell.c @@ -28,9 +28,13 @@ maximize(struct cg_view *view, int output_width, int output_height) } static void -get_geometry(struct cg_view *view, struct wlr_box *geom) +get_geometry(struct cg_view *view, int *width_out, int *height_out) { - wlr_xdg_surface_get_geometry(view->xdg_surface, geom); + struct wlr_box geom; + + wlr_xdg_surface_get_geometry(view->xdg_surface, &geom); + *width_out = geom.width; + *height_out = geom.height; } static bool From a34c726a1cfdf50d21f20a58c4cb8cc30fa1ca19 Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Mon, 31 Dec 2018 17:24:21 +0100 Subject: [PATCH 3/4] Add XWayland support With Cage becoming more popular since its mention on Phoronix and therefore getting more use-cases than just my own project, add XWayland support. The refactoring of 2cf40f7 makes this much easier. Note that this is a no-cost addition for those of us not using XWayland as it is a compile-time option that needs to be explicitly enabled by adding `-Dxwayland=true` to your meson command. --- cage.c | 49 +++++++++++++++++++++++++++ meson.build | 20 +++++++++++ meson_options.txt | 1 + output.c | 10 ++++++ seat.c | 22 ++++++++++-- seat.h | 3 ++ server.h | 9 +++++ view.h | 8 +++++ xwayland.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++ xwayland.h | 8 +++++ 10 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 meson_options.txt create mode 100644 xwayland.c create mode 100644 xwayland.h diff --git a/cage.c b/cage.c index f07c856..e28be76 100644 --- a/cage.c +++ b/cage.c @@ -8,6 +8,8 @@ #define _POSIX_C_SOURCE 200112L +#include "config.h" + #include #include #include @@ -17,15 +19,24 @@ #include #include #include +#if CAGE_HAS_XWAYLAND +#include +#endif #include #include #include #include +#if CAGE_HAS_XWAYLAND +#include +#endif #include "output.h" #include "seat.h" #include "server.h" #include "xdg_shell.h" +#if CAGE_HAS_XWAYLAND +#include "xwayland.h" +#endif static bool spawn_primary_client(char *argv[], pid_t *pid_out) @@ -69,6 +80,9 @@ main(int argc, char *argv[]) struct wlr_compositor *compositor = NULL; struct wlr_data_device_manager *data_device_mgr = NULL; struct wlr_xdg_shell *xdg_shell = NULL; +#if CAGE_HAS_XWAYLAND + struct wlr_xwayland *xwayland = NULL; +#endif int ret = 0; if (argc < 2) { @@ -146,6 +160,31 @@ main(int argc, char *argv[]) server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new; wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface); +#if CAGE_HAS_XWAYLAND + xwayland = wlr_xwayland_create(server.wl_display, compositor, true); + server.new_xwayland_surface.notify = handle_xwayland_surface_new; + wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface); + + struct wlr_xcursor_manager *xcursor_manager = + wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE); + if (!xcursor_manager) { + wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager"); + ret = 1; + goto end; + } + if (wlr_xcursor_manager_load(xcursor_manager, 1)) { + wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme"); + } + struct wlr_xcursor *xcursor = + wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1); + if (xcursor) { + struct wlr_xcursor_image *image = xcursor->images[0]; + wlr_xwayland_set_cursor(xwayland, image->buffer, + image->width * 4, image->width, image->height, + image->hotspot_x, image->hotspot_y); + } +#endif + const char *socket = wl_display_add_socket_auto(server.wl_display); if (!socket) { wlr_log_errno(WLR_ERROR, "Unable to open Wayland socket"); @@ -165,6 +204,12 @@ main(int argc, char *argv[]) "Clients may not be able to connect"); } +#if CAGE_HAS_XWAYLAND + if (xwayland) { + wlr_xwayland_set_seat(xwayland, server.seat->seat); + } +#endif + pid_t pid; if (!spawn_primary_client(argv + 1, &pid)) { ret = 1; @@ -178,6 +223,10 @@ main(int argc, char *argv[]) end: cg_seat_destroy(server.seat); +#if CAGE_HAS_XWAYLAND + wlr_xwayland_destroy(xwayland); + wlr_xcursor_manager_destroy(xcursor_manager); +#endif wlr_xdg_shell_destroy(xdg_shell); wlr_data_device_manager_destroy(data_device_mgr); wlr_compositor_destroy(compositor); diff --git a/meson.build b/meson.build index f65688a..d77e2cf 100644 --- a/meson.build +++ b/meson.build @@ -51,6 +51,9 @@ server_protos = declare_dependency( sources: server_protos_headers, ) +conf_data = configuration_data() +conf_data.set10('CAGE_HAS_XWAYLAND', get_option('xwayland')) + cage_sources = [ 'cage.c', 'output.c', @@ -60,6 +63,9 @@ cage_sources = [ ] cage_headers = [ + configure_file(input: 'config.h.in', + output: 'config.h', + configuration: conf_data), 'output.h', 'seat.h', 'server.h', @@ -67,6 +73,11 @@ cage_headers = [ 'xdg_shell.h', ] +if conf_data.get('CAGE_HAS_XWAYLAND', 0) == 1 + cage_sources += 'xwayland.c' + cage_headers += 'xwayland.h' +endif + executable( meson.project_name(), cage_sources + cage_headers, @@ -78,3 +89,12 @@ executable( ], install: true, ) + +summary = [ + '', + 'Cage @0@'.format(meson.project_version()), + '', + ' xwayland: @0@'.format(conf_data.get('CAGE_HAS_XWAYLAND', false)), + '' +] +message('\n'.join(summary)) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..87763ff --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications') diff --git a/output.c b/output.c index 99f92e3..1a26a21 100644 --- a/output.c +++ b/output.c @@ -8,6 +8,8 @@ #define _POSIX_C_SOURCE 200112L +#include "config.h" + #include #include #include @@ -19,6 +21,9 @@ #include #include #include +#if CAGE_HAS_XWAYLAND +#include +#endif #include "output.h" #include "server.h" @@ -76,6 +81,11 @@ view_for_each_surface(struct cg_view *view, struct render_data *rdata, case CAGE_XDG_SHELL_VIEW: wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator, rdata); break; +#ifdef CAGE_HAS_XWAYLAND + case CAGE_XWAYLAND_VIEW: + wlr_surface_for_each_surface(view->wlr_surface, iterator, rdata); + break; +#endif default: wlr_log(WLR_ERROR, "Unrecognized view type: %d", view->type); } diff --git a/seat.c b/seat.c index 5379e15..dc2786d 100644 --- a/seat.c +++ b/seat.c @@ -6,6 +6,8 @@ * See the LICENSE file accompanying this file. */ +#include "config.h" + #include #include #include @@ -14,15 +16,15 @@ #include #include #include +#if CAGE_HAS_XWAYLAND +#include +#endif #include "output.h" #include "seat.h" #include "server.h" #include "view.h" -#define DEFAULT_XCURSOR "left_ptr" -#define XCURSOR_SIZE 24 - static inline bool have_dialogs_open(struct cg_server *server) { @@ -52,6 +54,13 @@ view_at(struct cg_view *view, double lx, double ly, view_sx, view_sy, &_sx, &_sy); break; +#ifdef CAGE_HAS_XWAYLAND + case CAGE_XWAYLAND_VIEW: + _surface = wlr_surface_surface_at(view->wlr_surface, + view_sx, view_sy, + &_sx, &_sy); + break; +#endif default: wlr_log(WLR_ERROR, "Unrecognized view type: %d", view->type); } @@ -501,6 +510,13 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view) return; } +#if CAGE_HAS_XWAYLAND + if (view->type == CAGE_XWAYLAND_VIEW && + !wlr_xwayland_or_surface_wants_focus(view->xwayland_surface)) { + return; + } +#endif + if (prev_view) { view_activate(prev_view, false); } diff --git a/seat.h b/seat.h index e7304c0..afb7809 100644 --- a/seat.h +++ b/seat.h @@ -10,6 +10,9 @@ #include "server.h" #include "view.h" +#define DEFAULT_XCURSOR "left_ptr" +#define XCURSOR_SIZE 24 + struct cg_seat { struct wlr_seat *seat; struct cg_server *server; diff --git a/server.h b/server.h index 20dbdcf..dd912a9 100644 --- a/server.h +++ b/server.h @@ -1,9 +1,14 @@ #ifndef CG_SERVER_H #define CG_SERVER_H +#include "config.h" + #include #include #include +#ifdef CAGE_HAS_XWAYLAND +#include +#endif #include "output.h" #include "seat.h" @@ -20,6 +25,10 @@ struct cg_server { struct wlr_output_layout *output_layout; struct cg_output *output; struct wl_listener new_output; + +#if CAGE_HAS_XWAYLAND + struct wl_listener new_xwayland_surface; +#endif }; #endif diff --git a/view.h b/view.h index 749693c..373fd26 100644 --- a/view.h +++ b/view.h @@ -1,6 +1,8 @@ #ifndef CG_VIEW_H #define CG_VIEW_H +#include "config.h" + #include #include #include @@ -11,6 +13,9 @@ enum cg_view_type { CAGE_XDG_SHELL_VIEW, +#ifdef CAGE_HAS_XWAYLAND + CAGE_XWAYLAND_VIEW, +#endif }; struct cg_view { @@ -22,6 +27,9 @@ struct cg_view { enum cg_view_type type; union { struct wlr_xdg_surface *xdg_surface; +#ifdef CAGE_HAS_XWAYLAND + struct wlr_xwayland_surface *xwayland_surface; +#endif }; struct wl_listener destroy; diff --git a/xwayland.c b/xwayland.c new file mode 100644 index 0000000..804eb13 --- /dev/null +++ b/xwayland.c @@ -0,0 +1,86 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include +#include +#include +#include + +#include "server.h" +#include "view.h" + +static void +activate(struct cg_view *view, bool activate) +{ + wlr_xwayland_surface_activate(view->xwayland_surface, activate); +} + +static void +maximize(struct cg_view *view, int output_width, int output_height) +{ + wlr_xwayland_surface_configure(view->xwayland_surface, 0, 0, output_width, output_height); + wlr_xwayland_surface_set_maximized(view->xwayland_surface, true); +} + +static void +get_geometry(struct cg_view *view, int *width_out, int *height_out) +{ + *width_out = view->xwayland_surface->surface->current.width; + *height_out = view->xwayland_surface->surface->current.height; +} + +static bool +is_primary(struct cg_view *view) +{ + struct wlr_xwayland_surface *parent = view->xwayland_surface->parent; + return parent == NULL; +} + +static void +handle_xwayland_surface_unmap(struct wl_listener *listener, void *data) +{ + struct cg_view *view = wl_container_of(listener, view, unmap); + view_unmap(view); +} + +static void +handle_xwayland_surface_map(struct wl_listener *listener, void *data) +{ + struct cg_view *view = wl_container_of(listener, view, map); + view_map(view, view->xwayland_surface->surface); +} + +static void +handle_xwayland_surface_destroy(struct wl_listener *listener, void *data) +{ + struct cg_view *view = wl_container_of(listener, view, destroy); + view_destroy(view); +} + +void +handle_xwayland_surface_new(struct wl_listener *listener, void *data) +{ + struct cg_server *server = wl_container_of(listener, server, new_xwayland_surface); + struct wlr_xwayland_surface *xwayland_surface = data; + + struct cg_view *view = cg_view_create(server); + view->type = CAGE_XWAYLAND_VIEW; + view->xwayland_surface = xwayland_surface; + + view->map.notify = handle_xwayland_surface_map; + wl_signal_add(&xwayland_surface->events.map, &view->map); + view->unmap.notify = handle_xwayland_surface_unmap; + wl_signal_add(&xwayland_surface->events.unmap, &view->unmap); + view->destroy.notify = handle_xwayland_surface_destroy; + wl_signal_add(&xwayland_surface->events.destroy, &view->destroy); + + view->activate = activate; + view->maximize = maximize; + view->get_geometry = get_geometry; + view->is_primary = is_primary; +} diff --git a/xwayland.h b/xwayland.h new file mode 100644 index 0000000..524bc66 --- /dev/null +++ b/xwayland.h @@ -0,0 +1,8 @@ +#ifndef XWAYLAND_H +#define XWAYLAND_H + +#include + +void handle_xwayland_surface_new(struct wl_listener *listener, void *data); + +#endif From e721808c8893be6393983f3e9ad645635447192c Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Wed, 2 Jan 2019 20:58:39 +0100 Subject: [PATCH 4/4] Update copyright to include 2019 --- LICENSE | 2 +- README.md | 2 +- cage.c | 2 +- output.c | 2 +- seat.c | 2 +- xwayland.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index 84526f2..41a8d9f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018 Jente Hidskes +Copyright (c) 2018-2019 Jente Hidskes Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 13f5263..3caaa3f 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,4 @@ Please see [LICENSE](https://github.com/Hjdskes/cage/blob/master/LICENSE) on [GitHub](https://github.com/Hjdskes/cage). -Copyright © 2018 Jente Hidskes +Copyright © 2018-2019 Jente Hidskes diff --git a/cage.c b/cage.c index e28be76..b243a39 100644 --- a/cage.c +++ b/cage.c @@ -1,7 +1,7 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018 Jente Hidskes + * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ diff --git a/output.c b/output.c index 1a26a21..1a560a7 100644 --- a/output.c +++ b/output.c @@ -1,7 +1,7 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018 Jente Hidskes + * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ diff --git a/seat.c b/seat.c index dc2786d..d209e6e 100644 --- a/seat.c +++ b/seat.c @@ -1,7 +1,7 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018 Jente Hidskes + * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ diff --git a/xwayland.c b/xwayland.c index 804eb13..4cb57a7 100644 --- a/xwayland.c +++ b/xwayland.c @@ -1,7 +1,7 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018 Jente Hidskes + * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */