]> git.mdlowis.com Git - proto/labwc.git/commitdiff
Add partial support for layer-shell
authorJohan Malm <jgm323@gmail.com>
Wed, 30 Sep 2020 16:18:20 +0000 (17:18 +0100)
committerJohan Malm <jgm323@gmail.com>
Wed, 30 Sep 2020 16:18:20 +0000 (17:18 +0100)
We can now run swaybg and waybar

README.md
include/labwc.h
include/layers.h [new file with mode: 0644]
src/layers.c [new file with mode: 0644]
src/meson.build
src/output.c
src/server.c

index 7abed826d76fcea120a589b2af3efa5d4e57047e..f4f492e82113614ca40ad2b5fa6812ed4bd06878 100644 (file)
--- 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).
 
index b29d330ff01b957f6049988d6af92a9f21c976e8..365ca1ae0b572f20e0613092f2f3d47b9b75f75c 100644 (file)
@@ -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 (file)
index 0000000..d47be38
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef __LABWC_LAYERS_H
+#define __LABWC_LAYERS_H
+#include <wayland-server.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+
+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 (file)
index 0000000..269186a
--- /dev/null
@@ -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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#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);
+}
index aa5d01351212f4f7f9b2542c1aa9ad4351e3914b..eb0e8b0eea3bf722dc8b257680b14de5dd03e9c6 100644 (file)
@@ -5,6 +5,7 @@ labwc_sources = files(
   'desktop.c',
   'interactive.c',
   'keyboard.c',
+  'layers.c',
   'main.c',
   'osd.c',
   'output.c',
index 02e8bfa1897b34a19ab9b97259988ca9de7e196e..901b0faa91d4798e2ec0f205968e2a27b54a3142 100644 (file)
@@ -1,6 +1,7 @@
 #include <wlr/types/wlr_xdg_output_v1.h>
 #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
index d3d696c2bc0360c9462a1a715693fda54cd297f8..d1852e2d08effd5e79538c129001be44607b0771 100644 (file)
@@ -9,6 +9,7 @@
 #include <wlr/types/wlr_gamma_control_v1.h>
 #include <wlr/types/wlr_primary_selection_v1.h>
 #include <wlr/types/wlr_screencopy_v1.h>
+#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);