From a617d62354a0151146a1b15ae544a6ba8f5ee666 Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Sun, 23 Feb 2020 22:20:51 +0100 Subject: [PATCH 1/3] Add wlr-layer-shell-unstable-v1 protocol --- meson.build | 23 +- protocols/meson.build | 39 +++ protocols/wlr-layer-shell-unstable-v1.xml | 390 ++++++++++++++++++++++ 3 files changed, 430 insertions(+), 22 deletions(-) create mode 100644 protocols/meson.build create mode 100644 protocols/wlr-layer-shell-unstable-v1.xml diff --git a/meson.build b/meson.build index f8ccd76..48d0284 100644 --- a/meson.build +++ b/meson.build @@ -41,28 +41,7 @@ wayland_server = dependency('wayland-server') xkbcommon = dependency('xkbcommon') math = cc.find_library('m') -wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') -wayland_scanner = find_program('wayland-scanner') -wayland_scanner_server = generator( - wayland_scanner, - output: '@BASENAME@-protocol.h', - arguments: ['server-header', '@INPUT@', '@OUTPUT@'], -) - -server_protocols = [ - [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], -] - -server_protos_headers = [] - -foreach p : server_protocols - xml = join_paths(p) - server_protos_headers += wayland_scanner_server.process(xml) -endforeach - -server_protos = declare_dependency( - sources: server_protos_headers, -) +subdir('protocols') have_xwayland = wlroots.get_variable(pkgconfig: 'have_xwayland', internal: 'have_xwayland') == 'true' diff --git a/protocols/meson.build b/protocols/meson.build new file mode 100644 index 0000000..140419c --- /dev/null +++ b/protocols/meson.build @@ -0,0 +1,39 @@ +wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') +wayland_scanner = find_program('wayland-scanner', native: true) + +wayland_scanner_server_header = generator( + wayland_scanner, + output: '@BASENAME@-protocol.h', + arguments: ['server-header', '@INPUT@', '@OUTPUT@'], +) + +wayland_scanner_private_code = generator( + wayland_scanner, + output: '@BASENAME@-protocol.c', + arguments: ['private-code', '@INPUT@', '@OUTPUT@'], +) + +server_protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + ['wlr-layer-shell-unstable-v1.xml'], +] + +wl_protos_headers = [] +wl_protos_src = [] + +foreach p : server_protocols + xml = join_paths(p) + wl_protos_headers += wayland_scanner_server_header.process(xml) + wl_protos_src += wayland_scanner_private_code.process(xml) +endforeach + +lib_server_protos = static_library( + 'server_protos', + wl_protos_headers + wl_protos_src, + dependencies: wayland_server.partial_dependency(compile_args: true), +) + +server_protos = declare_dependency( + link_with: lib_server_protos, + sources: wl_protos_headers, +) diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + From 0e946d5a4faa5d00c252c89324833d91bb7f4de4 Mon Sep 17 00:00:00 2001 From: Jente Hidskes Date: Sun, 23 Feb 2020 22:20:25 +0100 Subject: [PATCH 2/3] Add initial layer shell skeleton --- cage.c | 13 ++++++++++++- layer_shell_v1.c | 27 +++++++++++++++++++++++++++ layer_shell_v1.h | 8 ++++++++ meson.build | 2 ++ server.h | 3 +++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 layer_shell_v1.c create mode 100644 layer_shell_v1.h diff --git a/cage.c b/cage.c index 9b7c510..9a0c26a 100644 --- a/cage.c +++ b/cage.c @@ -1,7 +1,7 @@ /* * Cage: A Wayland kiosk. * - * Copyright (C) 2018-2020 Jente Hidskes + * Copyright (C) 2018-2021 Jente Hidskes * * See the LICENSE file accompanying this file. */ @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ #endif #include "idle_inhibit_v1.h" +#include "layer_shell_v1.h" #include "output.h" #include "seat.h" #include "server.h" @@ -461,6 +463,15 @@ main(int argc, char *argv[]) goto end; } + server.layer_shell_v1 = wlr_layer_shell_v1_create(server.wl_display, 4); + if (!server.layer_shell_v1) { + wlr_log(WLR_ERROR, "Unable to create the layer shell"); + ret = 1; + goto end; + } + server.new_layer_shell_v1_surface.notify = handle_layer_shell_v1_surface_new; + wl_signal_add(&server.layer_shell_v1->events.new_surface, &server.new_layer_shell_v1_surface); + if (!wlr_export_dmabuf_manager_v1_create(server.wl_display)) { wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager"); ret = 1; diff --git a/layer_shell_v1.c b/layer_shell_v1.c new file mode 100644 index 0000000..c66ef05 --- /dev/null +++ b/layer_shell_v1.c @@ -0,0 +1,27 @@ +/* + * Cage: A Wayland kiosk. + * + * Copyright (C) 2021 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#include "layer_shell_v1.h" +#include "server.h" + +#include +#include +#include + +void +handle_layer_shell_v1_surface_new(struct wl_listener *listener, void *data) +{ + struct cg_server *server = wl_container_of(listener, server, new_layer_shell_v1_surface); + struct wlr_layer_surface_v1 *layer_surface = data; + + wlr_log(WLR_DEBUG, "New layer shell surface: namespace %s layer %d anchor %d size %dx%d margin %d,%d,%d,%d", + layer_surface->namespace, layer_surface->pending.layer, layer_surface->pending.anchor, + layer_surface->pending.desired_width, layer_surface->pending.desired_height, + layer_surface->pending.margin.top, layer_surface->pending.margin.right, + layer_surface->pending.margin.bottom, layer_surface->pending.margin.left); +} diff --git a/layer_shell_v1.h b/layer_shell_v1.h new file mode 100644 index 0000000..d7ade6e --- /dev/null +++ b/layer_shell_v1.h @@ -0,0 +1,8 @@ +#ifndef CG_LAYER_SHELL_V1_H +#define CG_LAYER_SHELL_V1_H + +#include + +void handle_layer_shell_v1_surface_new(struct wl_listener *listener, void *data); + +#endif diff --git a/meson.build b/meson.build index 48d0284..ed1f959 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,7 @@ endif cage_sources = [ 'cage.c', 'idle_inhibit_v1.c', + 'layer_shell_v1.c', 'output.c', 'seat.c', 'view.c', @@ -103,6 +104,7 @@ cage_headers = [ output: 'config.h', configuration: conf_data), 'idle_inhibit_v1.h', + 'layer_shell_v1.h', 'output.h', 'seat.h', 'server.h', diff --git a/server.h b/server.h index 00c2a61..95a083b 100644 --- a/server.h +++ b/server.h @@ -52,6 +52,9 @@ struct cg_server { struct wl_listener new_virtual_keyboard; struct wl_listener new_virtual_pointer; + + struct wlr_layer_shell_v1 *layer_shell_v1; + struct wl_listener new_layer_shell_v1_surface; #if CAGE_HAS_XWAYLAND struct wl_listener new_xwayland_surface; #endif From 8a11a66d876246663d25a4e789b495be954486c3 Mon Sep 17 00:00:00 2001 From: Sungjoon Moon Date: Wed, 22 Oct 2025 16:13:00 +0900 Subject: [PATCH 3/3] layer-shell: initial layer shell v1 implementation --- cage.c | 1 + layer_shell_v1.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++ layer_shell_v1.h | 34 ++++++ output.c | 120 +++++++++++++++++- output.h | 12 ++ seat.c | 59 +++++++-- seat.h | 5 + view.c | 21 ++-- 8 files changed, 538 insertions(+), 24 deletions(-) diff --git a/cage.c b/cage.c index 9a0c26a..47f9e1a 100644 --- a/cage.c +++ b/cage.c @@ -617,6 +617,7 @@ main(int argc, char *argv[]) wl_list_remove(&server.new_virtual_pointer.link); wl_list_remove(&server.new_virtual_keyboard.link); + wl_list_remove(&server.new_layer_shell_v1_surface.link); wl_list_remove(&server.output_manager_apply.link); wl_list_remove(&server.output_manager_test.link); wl_list_remove(&server.xdg_toplevel_decoration.link); diff --git a/layer_shell_v1.c b/layer_shell_v1.c index c66ef05..589c650 100644 --- a/layer_shell_v1.c +++ b/layer_shell_v1.c @@ -6,13 +6,263 @@ * See the LICENSE file accompanying this file. */ +#define _POSIX_C_SOURCE 200809L + #include "layer_shell_v1.h" +#include "output.h" +#include "seat.h" #include "server.h" +#include +#include +#include #include +#include #include +#include +#include +#include #include +static struct wlr_scene_tree * +get_layer_scene(struct cg_output *output, enum zwlr_layer_shell_v1_layer type) +{ + switch (type) { + case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: + return output->layers.shell_background; + case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: + return output->layers.shell_bottom; + case ZWLR_LAYER_SHELL_V1_LAYER_TOP: + return output->layers.shell_top; + case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: + return output->layers.shell_overlay; + } + + wlr_log(WLR_ERROR, "Invalid layer shell layer: %d", type); + return NULL; +} + +static void popup_handle_destroy(struct wl_listener *listener, void *data); +static void popup_handle_new_popup(struct wl_listener *listener, void *data); +static void popup_handle_commit(struct wl_listener *listener, void *data); + +static void +popup_unconstrain(struct cg_layer_popup *popup) +{ + struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; + struct cg_output *output = popup->toplevel->output; + + if (!output) { + return; + } + + int lx, ly; + wlr_scene_node_coords(&popup->toplevel->scene->tree->node, &lx, &ly); + + struct wlr_box output_box; + wlr_output_layout_get_box(output->server->output_layout, output->wlr_output, &output_box); + + struct wlr_box output_toplevel_sx_box = { + .x = output_box.x - lx, + .y = output_box.y - ly, + .width = output->wlr_output->width, + .height = output->wlr_output->height, + }; + + wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); +} + +static void +popup_handle_commit(struct wl_listener *listener, void *data) +{ + struct cg_layer_popup *popup = wl_container_of(listener, popup, commit); + if (popup->wlr_popup->base->initial_commit) { + popup_unconstrain(popup); + } +} + +static struct cg_layer_popup * +create_popup(struct wlr_xdg_popup *wlr_popup, struct cg_layer_surface *toplevel, struct wlr_scene_tree *parent) +{ + struct cg_layer_popup *popup = calloc(1, sizeof(*popup)); + if (popup == NULL) { + return NULL; + } + + popup->toplevel = toplevel; + popup->wlr_popup = wlr_popup; + popup->scene = wlr_scene_xdg_surface_create(parent, wlr_popup->base); + + if (!popup->scene) { + free(popup); + return NULL; + } + + popup->destroy.notify = popup_handle_destroy; + wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + popup->new_popup.notify = popup_handle_new_popup; + wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); + popup->commit.notify = popup_handle_commit; + wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + + return popup; +} + +static void +popup_handle_destroy(struct wl_listener *listener, void *data) +{ + struct cg_layer_popup *popup = wl_container_of(listener, popup, destroy); + + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->new_popup.link); + wl_list_remove(&popup->commit.link); + free(popup); +} + +static void +popup_handle_new_popup(struct wl_listener *listener, void *data) +{ + struct cg_layer_popup *popup = wl_container_of(listener, popup, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + create_popup(wlr_popup, popup->toplevel, popup->scene); +} + +static void +handle_new_popup(struct wl_listener *listener, void *data) +{ + struct cg_layer_surface *layer_surface = wl_container_of(listener, layer_surface, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + create_popup(wlr_popup, layer_surface, layer_surface->popups); +} + +static void +handle_surface_commit(struct wl_listener *listener, void *data) +{ + struct cg_layer_surface *surface = wl_container_of(listener, surface, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; + uint32_t committed = layer_surface->current.committed; + + if (!surface->output) { + return; + } + + if (layer_surface->initial_commit) { + wlr_fractional_scale_v1_notify_scale(layer_surface->surface, layer_surface->output->scale); + wlr_surface_set_preferred_buffer_scale(layer_surface->surface, ceil(layer_surface->output->scale)); + } + + if (layer_surface->initialized && committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { + enum zwlr_layer_shell_v1_layer layer_type = layer_surface->current.layer; + struct wlr_scene_tree *output_layer = get_layer_scene(surface->output, layer_type); + if (output_layer) { + wlr_scene_node_reparent(&surface->scene->tree->node, output_layer); + wlr_scene_node_reparent(&surface->popups->node, &surface->output->server->scene->tree); + } + } + + bool mapped_changed = layer_surface->surface->mapped != surface->mapped; + if (!layer_surface->initial_commit && !committed && !mapped_changed) { + return; + } + + surface->mapped = layer_surface->surface->mapped; + arrange_layers(surface->output); +} + +static void +handle_map(struct wl_listener *listener, void *data) +{ + struct cg_layer_surface *surface = wl_container_of(listener, surface, map); + struct wlr_layer_surface_v1 *layer_surface = surface->scene->layer_surface; + + if (!surface->output) { + return; + } + + struct cg_seat *seat = surface->output->server->seat; + + wlr_scene_node_set_enabled(&surface->scene->tree->node, true); + + if (layer_surface->current.keyboard_interactive && + (layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY || + layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP)) { + if (!seat->focused_layer || seat->focused_layer->current.layer >= layer_surface->current.layer) { + seat_set_focus_layer(seat, layer_surface); + } + } + + arrange_layers(surface->output); +} + +static void +handle_unmap(struct wl_listener *listener, void *data) +{ + struct cg_layer_surface *surface = wl_container_of(listener, surface, unmap); + + wlr_scene_node_set_enabled(&surface->scene->tree->node, false); + + if (!surface->output) { + return; + } + + struct cg_seat *seat = surface->output->server->seat; + + if (seat->focused_layer == surface->layer_surface) { + seat_set_focus_layer(seat, NULL); + } + + arrange_layers(surface->output); +} + +static void +handle_node_destroy(struct wl_listener *listener, void *data) +{ + struct cg_layer_surface *layer = wl_container_of(listener, layer, node_destroy); + + if (layer->output) { + arrange_layers(layer->output); + } + + wlr_scene_node_destroy(&layer->popups->node); + + wl_list_remove(&layer->map.link); + wl_list_remove(&layer->unmap.link); + wl_list_remove(&layer->surface_commit.link); + wl_list_remove(&layer->node_destroy.link); + wl_list_remove(&layer->new_popup.link); + + layer->layer_surface->data = NULL; + + wl_list_remove(&layer->link); + free(layer); +} + +static struct cg_layer_surface * +create_layer_surface(struct wlr_scene_layer_surface_v1 *scene, struct cg_output *output) +{ + struct cg_layer_surface *surface = calloc(1, sizeof(*surface)); + if (!surface) { + wlr_log(WLR_ERROR, "Could not allocate a layer surface"); + return NULL; + } + + struct wlr_scene_tree *popups = wlr_scene_tree_create(&output->server->scene->tree); + if (!popups) { + wlr_log(WLR_ERROR, "Could not allocate a layer popup node"); + free(surface); + return NULL; + } + + surface->tree = scene->tree; + surface->scene = scene; + surface->layer_surface = scene->layer_surface; + surface->popups = popups; + surface->layer_surface->data = surface; + surface->output = output; + + return surface; +} + void handle_layer_shell_v1_surface_new(struct wl_listener *listener, void *data) { @@ -24,4 +274,64 @@ handle_layer_shell_v1_surface_new(struct wl_listener *listener, void *data) layer_surface->pending.desired_width, layer_surface->pending.desired_height, layer_surface->pending.margin.top, layer_surface->pending.margin.right, layer_surface->pending.margin.bottom, layer_surface->pending.margin.left); + + if (!layer_surface->output) { + struct cg_seat *seat = server->seat; + if (seat && seat->focused_output) { + layer_surface->output = seat->focused_output; + } else if (!wl_list_empty(&server->outputs)) { + struct cg_output *output = wl_container_of(server->outputs.next, output, link); + layer_surface->output = output->wlr_output; + } else { + wlr_log(WLR_ERROR, "No output to assign layer surface '%s' to", layer_surface->namespace); + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + } + + struct cg_output *output = layer_surface->output->data; + if (!output) { + wlr_log(WLR_ERROR, "Layer surface has no output data"); + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + enum zwlr_layer_shell_v1_layer layer_type = layer_surface->pending.layer; + struct wlr_scene_tree *output_layer = get_layer_scene(output, layer_type); + if (!output_layer) { + wlr_log(WLR_ERROR, "Invalid layer %d for layer surface '%s'", layer_type, layer_surface->namespace); + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + struct wlr_scene_layer_surface_v1 *scene_surface = + wlr_scene_layer_surface_v1_create(output_layer, layer_surface); + if (!scene_surface) { + wlr_log(WLR_ERROR, "Could not allocate a layer_surface_v1"); + return; + } + + struct cg_layer_surface *surface = create_layer_surface(scene_surface, output); + if (!surface) { + wlr_layer_surface_v1_destroy(layer_surface); + wlr_log(WLR_ERROR, "Could not allocate a layer surface"); + return; + } + + wl_list_insert(&output->layer_surfaces, &surface->link); + + wlr_fractional_scale_v1_notify_scale(surface->layer_surface->surface, layer_surface->output->scale); + wlr_surface_set_preferred_buffer_scale(surface->layer_surface->surface, ceil(layer_surface->output->scale)); + + surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&layer_surface->surface->events.commit, &surface->surface_commit); + surface->map.notify = handle_map; + wl_signal_add(&layer_surface->surface->events.map, &surface->map); + surface->unmap.notify = handle_unmap; + wl_signal_add(&layer_surface->surface->events.unmap, &surface->unmap); + surface->new_popup.notify = handle_new_popup; + wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup); + + surface->node_destroy.notify = handle_node_destroy; + wl_signal_add(&scene_surface->tree->node.events.destroy, &surface->node_destroy); } diff --git a/layer_shell_v1.h b/layer_shell_v1.h index d7ade6e..cf66f69 100644 --- a/layer_shell_v1.h +++ b/layer_shell_v1.h @@ -1,7 +1,41 @@ #ifndef CG_LAYER_SHELL_V1_H #define CG_LAYER_SHELL_V1_H +#include #include +#include +#include + +struct cg_output; + +struct cg_layer_surface { + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener surface_commit; + struct wl_listener node_destroy; + struct wl_listener new_popup; + + bool mapped; + + struct wlr_scene_tree *popups; + + struct cg_output *output; + struct wl_list link; // cg_output::layer_surfaces + + struct wlr_scene_layer_surface_v1 *scene; + struct wlr_scene_tree *tree; + struct wlr_layer_surface_v1 *layer_surface; +}; + +struct cg_layer_popup { + struct wlr_xdg_popup *wlr_popup; + struct wlr_scene_tree *scene; + struct cg_layer_surface *toplevel; + + struct wl_listener destroy; + struct wl_listener new_popup; + struct wl_listener commit; +}; void handle_layer_shell_v1_surface_new(struct wl_listener *listener, void *data); diff --git a/output.c b/output.c index 093836b..458229f 100644 --- a/output.c +++ b/output.c @@ -21,6 +21,11 @@ #if WLR_HAS_X11_BACKEND #include #endif +#include "layer_shell_v1.h" +#include "output.h" +#include "seat.h" +#include "server.h" +#include "view.h" #include #include #include @@ -33,11 +38,6 @@ #include #include #include - -#include "output.h" -#include "seat.h" -#include "server.h" -#include "view.h" #if CAGE_HAS_XWAYLAND #include "xwayland.h" #endif @@ -205,12 +205,23 @@ output_destroy(struct cg_output *output) output->wlr_output->data = NULL; + struct cg_layer_surface *layer, *layer_tmp; + wl_list_for_each_safe (layer, layer_tmp, &output->layer_surfaces, link) { + layer->output = NULL; + wlr_layer_surface_v1_destroy(layer->layer_surface); + } + wl_list_remove(&output->destroy.link); wl_list_remove(&output->commit.link); wl_list_remove(&output->request_state.link); wl_list_remove(&output->frame.link); wl_list_remove(&output->link); + wlr_scene_node_destroy(&output->layers.shell_background->node); + wlr_scene_node_destroy(&output->layers.shell_bottom->node); + wlr_scene_node_destroy(&output->layers.shell_top->node); + wlr_scene_node_destroy(&output->layers.shell_overlay->node); + output_layout_remove(output); free(output); @@ -231,6 +242,18 @@ handle_output_destroy(struct wl_listener *listener, void *data) output_destroy(output); } +static struct wlr_scene_tree * +create_layer_for_output(struct cg_output *output) +{ + struct cg_server *server = output->server; + struct wlr_scene_tree *layer = wlr_scene_tree_create(&server->scene->tree); + if (layer == NULL) { + return NULL; + } + layer->node.data = output->wlr_output; + return layer; +} + void handle_new_output(struct wl_listener *listener, void *data) { @@ -252,6 +275,7 @@ handle_new_output(struct wl_listener *listener, void *data) wlr_output->data = output; output->server = server; + wl_list_init(&output->layer_surfaces); wl_list_insert(&server->outputs, &output->link); output->commit.notify = handle_output_commit; @@ -296,6 +320,11 @@ handle_new_output(struct wl_listener *listener, void *data) output_disable(next); } + output->layers.shell_background = create_layer_for_output(output); + output->layers.shell_bottom = create_layer_for_output(output); + output->layers.shell_top = create_layer_for_output(output); + output->layers.shell_overlay = create_layer_for_output(output); + if (!wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) { wlr_log(WLR_ERROR, "Cannot load XCursor theme for output '%s' with scale %f", wlr_output->name, wlr_output->scale); @@ -391,6 +420,87 @@ out: return ok; } +static void +arrange_surface(struct cg_output *output, const struct wlr_box *full_area, struct wlr_box *usable_area, + enum zwlr_layer_shell_v1_layer layer, bool exclusive) +{ + struct cg_layer_surface *surface; + wl_list_for_each (surface, &output->layer_surfaces, link) { + struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; + + if (layer_surface->current.layer != layer) { + continue; + } + + if (!layer_surface->initialized) { + continue; + } + + if ((layer_surface->current.exclusive_zone > 0) != exclusive) { + continue; + } + + wlr_scene_layer_surface_v1_configure(surface->scene, full_area, usable_area); + } +} + +void +arrange_layers(struct cg_output *output) +{ + struct wlr_box usable_area = {0}; + wlr_output_effective_resolution(output->wlr_output, &usable_area.width, &usable_area.height); + const struct wlr_box full_area = usable_area; + + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, true); + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_TOP, true); + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, true); + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, true); + + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, false); + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_TOP, false); + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, false); + arrange_surface(output, &full_area, &usable_area, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, false); + + if (!wlr_box_equal(&usable_area, &output->usable_area)) { + wlr_log(WLR_DEBUG, "Usable area changed, rearranging output"); + output->usable_area = usable_area; + view_position_all(output->server); + } + + enum zwlr_layer_shell_v1_layer layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); + struct cg_layer_surface *topmost = NULL; + for (size_t i = 0; i < nlayers; ++i) { + struct cg_layer_surface *surface; + wl_list_for_each_reverse (surface, &output->layer_surfaces, link) { + struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; + if (layer_surface->current.layer != layers_above_shell[i]) { + continue; + } + if (layer_surface->current.keyboard_interactive == + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE && + layer_surface->surface->mapped) { + topmost = surface; + break; + } + } + if (topmost != NULL) { + break; + } + } + + struct cg_seat *seat = output->server->seat; + if (topmost != NULL) { + seat_set_focus_layer(seat, topmost->layer_surface); + } else if (seat->focused_layer && seat->focused_layer->current.keyboard_interactive != + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { + seat_set_focus_layer(seat, NULL); + } +} + void handle_output_manager_apply(struct wl_listener *listener, void *data) { diff --git a/output.h b/output.h index fa72545..eaf47e0 100644 --- a/output.h +++ b/output.h @@ -17,6 +17,17 @@ struct cg_output { struct wl_listener destroy; struct wl_listener frame; + struct { + struct wlr_scene_tree *shell_background; + struct wlr_scene_tree *shell_bottom; + struct wlr_scene_tree *shell_top; + struct wlr_scene_tree *shell_overlay; + } layers; + + struct wlr_box usable_area; + + struct wl_list layer_surfaces; // cg_layer_surface::link + struct wl_list link; // cg_server::outputs }; @@ -25,5 +36,6 @@ void handle_output_manager_test(struct wl_listener *listener, void *data); void handle_output_layout_change(struct wl_listener *listener, void *data); void handle_new_output(struct wl_listener *listener, void *data); void output_set_window_title(struct cg_output *output, const char *title); +void arrange_layers(struct cg_output *output); #endif diff --git a/seat.c b/seat.c index 5f659a4..e32af18 100644 --- a/seat.c +++ b/seat.c @@ -74,17 +74,23 @@ desktop_view_at(struct cg_server *server, double lx, double ly, struct wlr_surfa /* Walk up the tree until we find a node with a data pointer. When done, * we've found the node representing the view. */ - while (!node->data) { + while (node) { + if (node->data) { + struct cg_view *candidate = node->data; + struct cg_view *view; + wl_list_for_each (view, &server->views, link) { + if (view == candidate) { + return view; + } + } + } if (!node->parent) { - node = NULL; break; } - node = &node->parent->node; } - assert(node != NULL); - return node->data; + return NULL; } static void @@ -104,7 +110,7 @@ press_cursor_button(struct cg_seat *seat, struct wlr_input_device *device, uint3 /* Focus that client if the button was pressed and it has no open dialogs. */ - if (view && !view_is_transient_for(current, view)) { + if (view && (!current || !view_is_transient_for(current, view))) { seat_set_focus(seat, view); } } @@ -625,8 +631,8 @@ process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, doubl struct wlr_seat *wlr_seat = seat->seat; struct wlr_surface *surface = NULL; - struct cg_view *view = desktop_view_at(seat->server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy); - if (!view) { + desktop_view_at(seat->server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy); + if (!surface) { wlr_seat_pointer_clear_focus(wlr_seat); } else { wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); @@ -787,8 +793,7 @@ handle_destroy(struct wl_listener *listener, void *data) struct cg_keyboard_group *group, *group_tmp; wl_list_for_each_safe (group, group_tmp, &seat->keyboard_groups, link) { - wlr_keyboard_group_destroy(group->wlr_group); - free(group); + keyboard_group_destroy(group); } struct cg_pointer *pointer, *pointer_tmp; wl_list_for_each_safe (pointer, pointer_tmp, &seat->pointers, link) { @@ -910,6 +915,7 @@ seat_destroy(struct cg_seat *seat) // Destroying the wlr seat will trigger the destroy handler on our seat, // which will in turn free it. + wlr_seat_destroy(seat->seat); } @@ -920,6 +926,11 @@ seat_get_focus(struct cg_seat *seat) if (!prev_surface) { return NULL; } + + if (seat->focused_layer && seat->focused_layer->surface == prev_surface) { + return NULL; + } + return view_from_wlr_surface(prev_surface); } @@ -973,6 +984,34 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view) process_cursor_motion(seat, -1, 0, 0, 0, 0); } +void +seat_set_focus_layer(struct cg_seat *seat, struct wlr_layer_surface_v1 *layer) +{ + if (seat->focused_layer == layer) { + return; + } + + seat->focused_layer = layer; + + if (!layer) { + struct cg_view *view = seat_get_focus(seat); + if (view) { + seat_set_focus(seat, view); + } + return; + } + + if (layer->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) { + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat); + if (keyboard) { + wlr_seat_keyboard_notify_enter(seat->seat, layer->surface, keyboard->keycodes, + keyboard->num_keycodes, &keyboard->modifiers); + } else { + wlr_seat_keyboard_notify_enter(seat->seat, layer->surface, NULL, 0, NULL); + } + } +} + void seat_center_cursor(struct cg_seat *seat) { diff --git a/seat.h b/seat.h index 4b7bfda..dbe96b0 100644 --- a/seat.h +++ b/seat.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -48,6 +49,9 @@ struct cg_seat { struct wl_listener request_set_cursor; struct wl_listener request_set_selection; struct wl_listener request_set_primary_selection; + + struct wlr_layer_surface_v1 *focused_layer; + struct wlr_output *focused_output; }; struct cg_keyboard_group { @@ -91,6 +95,7 @@ struct cg_seat *seat_create(struct cg_server *server, struct wlr_backend *backen void seat_destroy(struct cg_seat *seat); struct cg_view *seat_get_focus(struct cg_seat *seat); void seat_set_focus(struct cg_seat *seat, struct cg_view *view); +void seat_set_focus_layer(struct cg_seat *seat, struct wlr_layer_surface_v1 *layer); void seat_center_cursor(struct cg_seat *seat); #endif diff --git a/view.c b/view.c index 8cbeb5e..038c5a0 100644 --- a/view.c +++ b/view.c @@ -115,8 +115,6 @@ view_unmap(struct cg_view *view) { wl_list_remove(&view->link); - wlr_scene_node_destroy(&view->scene_tree->node); - view->wlr_surface->data = NULL; view->wlr_surface = NULL; } @@ -124,12 +122,16 @@ view_unmap(struct cg_view *view) void view_map(struct cg_view *view, struct wlr_surface *surface) { - view->scene_tree = wlr_scene_subsurface_tree_create(&view->server->scene->tree, surface); if (!view->scene_tree) { - wl_resource_post_no_memory(surface->resource); - return; + view->scene_tree = wlr_scene_subsurface_tree_create(&view->server->scene->tree, surface); + if (!view->scene_tree) { + wl_resource_post_no_memory(surface->resource); + return; + } + } + if (view->scene_tree) { + view->scene_tree->node.data = view; } - view->scene_tree->node.data = view; view->wlr_surface = surface; surface->data = view; @@ -137,11 +139,12 @@ view_map(struct cg_view *view, struct wlr_surface *surface) #if CAGE_HAS_XWAYLAND /* We shouldn't position override-redirect windows. They set their own (x,y) coordinates in handle_wayland_surface_map. */ - if (view->type != CAGE_XWAYLAND_VIEW || xwayland_view_should_manage(view)) -#endif - { + if (view->type != CAGE_XWAYLAND_VIEW || xwayland_view_should_manage(view)) { view_position(view); } +#else + view_position(view); +#endif wl_list_insert(&view->server->views, &view->link); seat_set_focus(view->server->seat, view);