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 f07c856..b243a39 100644 --- a/cage.c +++ b/cage.c @@ -1,13 +1,15 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018 Jente Hidskes + * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ #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..1a560a7 100644 --- a/output.c +++ b/output.c @@ -1,13 +1,15 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018 Jente Hidskes + * Copyright (C) 2018-2019 Jente Hidskes * * See the LICENSE file accompanying this file. */ #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..d209e6e 100644 --- a/seat.c +++ b/seat.c @@ -1,11 +1,13 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018 Jente Hidskes + * Copyright (C) 2018-2019 Jente Hidskes * * 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.c b/view.c index 8999ac3..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 @@ -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..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,16 +27,20 @@ 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; + struct wl_listener unmap; struct wl_listener map; // TODO: allow applications to go to fullscreen from maximized? // struct wl_listener request_fullscreen; 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); }; @@ -39,6 +48,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..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 @@ -41,6 +45,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 +72,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); diff --git a/xwayland.c b/xwayland.c new file mode 100644 index 0000000..4cb57a7 --- /dev/null +++ b/xwayland.c @@ -0,0 +1,86 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2018-2019 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