From a4c22f7c4de1319e1e252d4a1d65d033fcb93ef2 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Wed, 30 Sep 2020 17:18:20 +0100 Subject: [PATCH] Add partial support for layer-shell We can now run swaybg and waybar --- README.md | 5 +- include/labwc.h | 5 + include/layers.h | 23 ++++ src/layers.c | 333 +++++++++++++++++++++++++++++++++++++++++++++++ src/meson.build | 1 + src/output.c | 57 ++++++++ src/server.c | 3 + 7 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 include/layers.h create mode 100644 src/layers.c diff --git a/README.md b/README.md index 7abed826..f4f492e8 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ Suggested apps to use with labwc: - [grim](https://github.com/emersion/grim) - Take screenshot - [wf-recorder](https://github.com/ammen99/wf-recorder) - Record screen +- [swaybg](https://github.com/swaywm/swaybg) - Set background image +- [waybar](https://github.com/Alexays/Waybar) - Panel ## 6. Roadmap @@ -77,16 +79,17 @@ Suggested apps to use with labwc: - [x] Read xbm icons - [x] Show maximize, iconify, close buttons - [x] Catch SIGHUP to reconfigure (re-load config and theme) +- [x] Support layer-shell protocol (partial) - [ ] Support 'maximize' - [ ] Show window title - [ ] Read ~/.config/labwc/autostart - [ ] Read ~/.config/labwc/environment (useful for XKB environment variables) -- [ ] Support layer-shell protocol (e.g. for setting background with swaybg). - [ ] Support foreign-toplevel protocol (e.g. to integrate with wlroots panels/bars) - [ ] Support damage control to reduce CPU usage - [ ] Implement client-menu - [ ] Implement root-menu - [ ] Support on-screen display (OSD), for example to support alt-tab window list +- [ ] Support [kanshi](https://github.com/emersion/kanshi.git) For further details see [wiki/Roadmap](https://github.com/johanmalm/labwc/wiki/Roadmap). diff --git a/include/labwc.h b/include/labwc.h index b29d330f..365ca1ae 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -48,7 +48,11 @@ struct server { struct wlr_backend *backend; struct wlr_xdg_shell *xdg_shell; + struct wlr_layer_shell_v1 *layer_shell; + struct wl_listener new_xdg_surface; + struct wl_listener new_layer_surface; + struct wl_listener xdg_toplevel_decoration; struct wlr_xwayland *xwayland; struct wl_listener new_xwayland_surface; @@ -88,6 +92,7 @@ struct output { struct wl_list link; struct server *server; struct wlr_output *wlr_output; + struct wl_list layers[4]; struct wl_listener frame; struct wl_listener destroy; }; diff --git a/include/layers.h b/include/layers.h new file mode 100644 index 00000000..d47be386 --- /dev/null +++ b/include/layers.h @@ -0,0 +1,23 @@ +#ifndef __LABWC_LAYERS_H +#define __LABWC_LAYERS_H +#include +#include + +struct server; + +struct lab_layer_surface { + struct wlr_layer_surface_v1 *layer_surface; + struct server *server; + struct wl_list link; + + struct wl_listener destroy; + struct wl_listener map; + struct wl_listener surface_commit; + struct wl_listener output_destroy; + + struct wlr_box geo; +}; + +void layers_init(struct server *server); + +#endif /* __LABWC_LAYERS_H */ diff --git a/src/layers.c b/src/layers.c new file mode 100644 index 00000000..269186a4 --- /dev/null +++ b/src/layers.c @@ -0,0 +1,333 @@ +/* + * layers.c - layer-shell implementation + * + * Based on https://git.sr.ht/~sircmpwm/wio and https://github.com/swaywm/sway + * Copyright (C) 2019 Drew DeVault and Sway developers + */ + +#include +#include +#include +#include +#include +#include "layers.h" +#include "labwc.h" + +static void +apply_exclusive(struct wlr_box *usable_area, uint32_t anchor, int32_t exclusive, + int32_t margin_top, int32_t margin_right, int32_t margin_bottom, + int32_t margin_left) +{ + if (exclusive <= 0) { + return; + } + struct { + uint32_t anchors; + int *positive_axis; + int *negative_axis; + int margin; + } edges[] = { + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, + .positive_axis = &usable_area->y, + .negative_axis = &usable_area->height, + .margin = margin_top, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->height, + .margin = margin_bottom, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = &usable_area->x, + .negative_axis = &usable_area->width, + .margin = margin_left, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->width, + .margin = margin_right, + }, + }; + for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) { + if ((anchor & edges[i].anchors) == edges[i].anchors) { + if (edges[i].positive_axis) { + *edges[i].positive_axis += exclusive + edges[i].margin; + } + if (edges[i].negative_axis) { + *edges[i].negative_axis -= exclusive + edges[i].margin; + } + } + } +} + +/** + * @list: struct lab_layer_surface + */ +static void +arrange_layer(struct wlr_output *output, struct wl_list *list, + struct wlr_box *usable_area, bool exclusive) +{ + struct lab_layer_surface *surface; + struct wlr_box full_area = { 0 }; + wlr_output_effective_resolution(output, &full_area.width, + &full_area.height); + wl_list_for_each_reverse(surface, list, link) { + struct wlr_layer_surface_v1 *layer = surface->layer_surface; + struct wlr_layer_surface_v1_state *state = &layer->current; + if (exclusive != (state->exclusive_zone > 0)) { + continue; + } + struct wlr_box bounds; + if (state->exclusive_zone == -1) { + bounds = full_area; + } else { + bounds = *usable_area; + } + struct wlr_box box = { + .width = state->desired_width, + .height = state->desired_height + }; + /* Horizontal axis */ + const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if ((state->anchor & both_horiz) && box.width == 0) { + box.x = bounds.x; + box.width = bounds.width; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x = bounds.x; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x = bounds.x + (bounds.width - box.width); + } else { + box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); + } + /* Vertical axis */ + const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + if ((state->anchor & both_vert) && box.height == 0) { + box.y = bounds.y; + box.height = bounds.height; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y = bounds.y; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y = bounds.y + (bounds.height - box.height); + } else { + box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); + } + /* Margin */ + if ((state->anchor & both_horiz) == both_horiz) { + box.x += state->margin.left; + box.width -= state->margin.left + state->margin.right; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x += state->margin.left; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x -= state->margin.right; + } + if ((state->anchor & both_vert) == both_vert) { + box.y += state->margin.top; + box.height -= state->margin.top + state->margin.bottom; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y += state->margin.top; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y -= state->margin.bottom; + } + if (box.width < 0 || box.height < 0) { + warn("protocol error"); + wlr_layer_surface_v1_close(layer); + continue; + } + + /* Apply */ + surface->geo = box; + apply_exclusive(usable_area, state->anchor, + state->exclusive_zone, state->margin.top, + state->margin.right, state->margin.bottom, + state->margin.left); + wlr_layer_surface_v1_configure(layer, box.width, box.height); + } +} + +void +arrange_layers(struct output *output) +{ + struct wlr_box usable_area = { 0 }; + if (!output) + return; + wlr_output_effective_resolution(output->wlr_output, + &usable_area.width, &usable_area.height); + + /* Exclusive surfaces */ + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], + &usable_area, true); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], + &usable_area, true); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], + &usable_area, true); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + &usable_area, true); + + /* Non-exclusive surfaces */ + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], + &usable_area, false); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], + &usable_area, false); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], + &usable_area, false); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + &usable_area, false); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + uint32_t 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 lab_layer_surface *layer, *topmost = NULL; + for (size_t i = 0; i < nlayers; ++i) { + wl_list_for_each_reverse (layer, + &output->layers[layers_above_shell[i]], link) { + if (layer->layer_surface->current.keyboard_interactive) { + topmost = layer; + break; + } + } + if (topmost != NULL) { + break; + } + } +} + +static void +handle_output_destroy(struct wl_listener *listener, void *data) +{ + struct lab_layer_surface *layer = + wl_container_of(listener, layer, output_destroy); + layer->layer_surface->output = NULL; + wl_list_remove(&layer->output_destroy.link); + wlr_layer_surface_v1_close(layer->layer_surface); +} + +static void +handle_surface_commit(struct wl_listener *listener, void *data) +{ + struct lab_layer_surface *layer = + wl_container_of(listener, layer, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; + struct wlr_output *wlr_output = layer_surface->output; + if (wlr_output != NULL) { + struct output *output = wlr_output->data; + arrange_layers(output); + } +} + +static void +handle_destroy(struct wl_listener *listener, void *data) +{ + struct lab_layer_surface *layer = wl_container_of( + listener, layer, destroy); + wl_list_remove(&layer->link); + wl_list_remove(&layer->destroy.link); + wl_list_remove(&layer->map.link); + wl_list_remove(&layer->surface_commit.link); + if (layer->layer_surface->output) { + wl_list_remove(&layer->output_destroy.link); + arrange_layers( + (struct output *)layer->layer_surface->output->data); + } + free(layer); +} + +static void +handle_map(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *layer_surface = data; + wlr_surface_send_enter(layer_surface->surface, layer_surface->output); +} + +void +server_new_layer_surface(struct wl_listener *listener, void *data) +{ + struct server *server = wl_container_of( + listener, server, new_layer_surface); + struct wlr_layer_surface_v1 *layer_surface = data; + if (!layer_surface->output) { + struct wlr_output *output = wlr_output_layout_output_at( + server->output_layout, server->cursor->x, + server->cursor->y); + layer_surface->output = output; + } + + struct output *output = layer_surface->output->data; + + /* TODO: fix the quick hack below */ + if (!output) { + wl_list_for_each(output, &server->outputs, link) { + if (output->wlr_output) { + break; + } + } + } + + struct lab_layer_surface *surface = + calloc(1, sizeof(struct lab_layer_surface)); + if (!surface) { + return; + } + surface->layer_surface = layer_surface; + layer_surface->data = surface; + surface->server = server; + + surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&layer_surface->surface->events.commit, + &surface->surface_commit); + surface->output_destroy.notify = handle_output_destroy; + wl_signal_add(&layer_surface->output->events.destroy, + &surface->output_destroy); + surface->destroy.notify = handle_destroy; + wl_signal_add(&layer_surface->events.destroy, &surface->destroy); + surface->map.notify = handle_map; + wl_signal_add(&layer_surface->events.map, &surface->map); + + wl_list_insert(&output->layers[layer_surface->client_pending.layer], + &surface->link); + /* + * Temporarily set the layer's current state to client_pending so that + * it can easily be arranged. + */ + struct wlr_layer_surface_v1_state old_state = layer_surface->current; + layer_surface->current = layer_surface->client_pending; + arrange_layers(output); + layer_surface->current = old_state; +} + +void +layers_init(struct server *server) +{ + server->layer_shell = wlr_layer_shell_v1_create(server->wl_display); + server->new_layer_surface.notify = server_new_layer_surface; + wl_signal_add(&server->layer_shell->events.new_surface, + &server->new_layer_surface); +} diff --git a/src/meson.build b/src/meson.build index aa5d0135..eb0e8b0e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,7 @@ labwc_sources = files( 'desktop.c', 'interactive.c', 'keyboard.c', + 'layers.c', 'main.c', 'osd.c', 'output.c', diff --git a/src/output.c b/src/output.c index 02e8bfa1..901b0faa 100644 --- a/src/output.c +++ b/src/output.c @@ -1,6 +1,7 @@ #include #include "labwc.h" #include "theme/theme.h" +#include "layers.h" struct draw_data { struct wlr_renderer *renderer; @@ -168,6 +169,53 @@ render_decorations(struct wlr_output *output, struct view *view) } } +struct render_data_layer { + struct lab_layer_surface *layer_surface; + struct timespec *now; +}; + +static void +render_layer_surface(struct wlr_surface *surface, int sx, int sy, void *data) +{ + struct render_data_layer *context = data; + struct lab_layer_surface *layer_surface = context->layer_surface; + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + return; + } + struct wlr_output *output = layer_surface->layer_surface->output; + double ox = 0, oy = 0; + wlr_output_layout_output_coords( + layer_surface->server->output_layout, output, &ox, &oy); + ox += layer_surface->geo.x + sx, oy += layer_surface->geo.y + sy; + float matrix[9]; + enum wl_output_transform transform = + wlr_output_transform_invert(surface->current.transform); + struct wlr_box box; + memcpy(&box, &layer_surface->geo, sizeof(struct wlr_box)); + wlr_matrix_project_box(matrix, &box, transform, 0, + output->transform_matrix); + wlr_render_texture_with_matrix(layer_surface->server->renderer, + texture, matrix, 1); + wlr_surface_send_frame_done(surface, context->now); +} + +static void +render_layer(struct timespec *now, struct wl_list *layer_surfaces) +{ + struct render_data_layer context = { + .now = now, + }; + struct lab_layer_surface *layer_surface; + wl_list_for_each(layer_surface, layer_surfaces, link) { + struct wlr_layer_surface_v1 *wlr_layer_surface_v1 = + layer_surface->layer_surface; + context.layer_surface = layer_surface; + wlr_surface_for_each_surface(wlr_layer_surface_v1->surface, + render_layer_surface, &context); + } +} + struct render_data { struct wlr_output *output; struct wlr_output_layout *output_layout; @@ -243,6 +291,9 @@ output_frame_notify(struct wl_listener *listener, void *data) float color[4] = { 0.3, 0.3, 0.3, 1.0 }; wlr_renderer_clear(renderer, color); + render_layer(&now, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); + render_layer(&now, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); + struct view *view; wl_list_for_each_reverse (view, &output->server->views, link) { if (!view->mapped) @@ -282,6 +333,8 @@ output_frame_notify(struct wl_listener *listener, void *data) render_surface(s, 0, 0, &rdata); } + render_layer(&now, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); + render_layer(&now, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); /* Just in case hardware cursors not supported by GPU */ wlr_output_render_software_cursors(output->wlr_output, NULL); @@ -324,6 +377,10 @@ new_output_notify(struct wl_listener *listener, void *data) output->destroy.notify = output_destroy_notify; wl_signal_add(&wlr_output->events.destroy, &output->destroy); + wl_list_init(&output->layers[0]); + wl_list_init(&output->layers[1]); + wl_list_init(&output->layers[2]); + wl_list_init(&output->layers[3]); /* * Arrange outputs from left-to-right in the order they appear. * TODO: support configuration in run-time diff --git a/src/server.c b/src/server.c index d3d696c2..d1852e2d 100644 --- a/src/server.c +++ b/src/server.c @@ -9,6 +9,7 @@ #include #include #include +#include "layers.h" static struct wlr_compositor *compositor; static struct wl_event_source *sighup_source; @@ -242,6 +243,8 @@ server_init(struct server *server) wlr_gamma_control_manager_v1_create(server->wl_display); wlr_primary_selection_v1_device_manager_create(server->wl_display); + layers_init(server); + /* Init xwayland */ server->xwayland = wlr_xwayland_create(server->wl_display, compositor, false); -- 2.52.0