]> git.mdlowis.com Git - proto/labwc.git/commitdiff
workspaces: Add workspaces.{c,h}
authorConsolatis <35009135+Consolatis@users.noreply.github.com>
Tue, 14 Jun 2022 23:07:45 +0000 (01:07 +0200)
committerConsolatis <35009135+Consolatis@users.noreply.github.com>
Wed, 15 Jun 2022 20:26:21 +0000 (22:26 +0200)
include/config/rcxml.h
include/labwc.h
include/workspaces.h [new file with mode: 0644]
src/meson.build
src/workspaces.c [new file with mode: 0644]

index 99a2be461308efa60aad6f5179cecb4558ae3ef0..41382de2f346b6ee2f56d2edb31fbd9d5d3d5a62 100644 (file)
@@ -55,6 +55,11 @@ struct rcxml {
 
        /* cycle view (alt+tab) */
        bool cycle_preview_contents;
+
+       struct {
+               int popuptime;
+               struct wl_list workspaces;  /* struct workspace.link */
+       } workspace_config;
 };
 
 extern struct rcxml rc;
index 9e083a7e247479945a19da6cb78e91a27681b810..83a30db0856f784da3cffa49f9fc7f69b46aae48 100644 (file)
@@ -87,6 +87,10 @@ struct seat {
        struct wlr_idle *wlr_idle;
        struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_manager;
 
+       /* Used to hide the workspace OSD after switching workspaces */
+       struct wl_event_source *workspace_osd_timer;
+       bool workspace_osd_shown_by_modifier;
+
        /* if set, views cannot receive focus */
        struct wlr_layer_surface_v1 *focused_layer;
 
@@ -143,9 +147,11 @@ struct seat {
 };
 
 struct lab_data_buffer;
+struct workspace;
 
 struct server {
        struct wl_display *wl_display;
+       struct wl_event_loop *wl_event_loop;  /* Can be used for timer events */
        struct wlr_renderer *renderer;
        struct wlr_allocator *allocator;
        struct wlr_backend *backend;
@@ -194,6 +200,10 @@ struct server {
        /* Tree for built in menu */
        struct wlr_scene_tree *menu_tree;
 
+       /* Workspaces */
+       struct wl_list workspaces;  /* struct workspace.link */
+       struct workspace *workspace_current;
+
        struct wl_list outputs;
        struct wl_listener new_output;
        struct wlr_output_layout *output_layout;
@@ -234,6 +244,7 @@ struct output {
        struct wlr_scene_tree *layer_tree[LAB_NR_LAYERS];
        struct wlr_scene_tree *layer_popup_tree;
        struct wlr_scene_tree *osd_tree;
+       struct wlr_scene_buffer *workspace_osd;
        struct wlr_box usable_area;
 
        struct lab_data_buffer *osd_buffer;
@@ -276,6 +287,7 @@ struct view {
        const struct view_impl *impl;
        struct wl_list link;
        struct output *output;
+       struct workspace *workspace;
 
        union {
                struct wlr_xdg_surface *xdg_surface;
diff --git a/include/workspaces.h b/include/workspaces.h
new file mode 100644 (file)
index 0000000..8b16bd0
--- /dev/null
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __LABWC_WORKSPACES_H
+#define __LABWC_WORKSPACES_H
+
+struct seat;
+struct view;
+struct server;
+struct wl_list;
+
+/* Double use: as config in config/rcxml.c and as instance in workspaces.c */
+struct workspace {
+       struct wl_list link; /* struct server.workspaces
+                               struct rcxml.workspace_config.workspaces */
+       struct server *server;
+
+       char *name;
+       struct wlr_scene_tree *tree;
+};
+
+
+void workspaces_init(struct server *server);
+void workspaces_switch_to(struct workspace *target);
+void workspaces_send_to(struct view *view, struct workspace *target);
+void workspaces_destroy(struct server *server);
+void workspaces_osd_hide(struct seat *seat);
+struct workspace * workspaces_find(struct workspace *anchor, const char *name);
+
+#endif /* __LABWC_WORKSPACES_H */
index aedda90f4a234a17241000795331f046fb810cf2..7771fcab5d58b529a0befc4fb307c12bb17ab040 100644 (file)
@@ -20,6 +20,7 @@ labwc_sources = files(
   'theme.c',
   'view.c',
   'view-impl.c',
+  'workspaces.c',
   'xdg.c',
   'xdg-deco.c',
   'xdg-popup.c',
diff --git a/src/workspaces.c b/src/workspaces.c
new file mode 100644 (file)
index 0000000..d481c63
--- /dev/null
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <cairo.h>
+#include <pango/pangocairo.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "labwc.h"
+#include "common/font.h"
+#include "common/zfree.h"
+#include "workspaces.h"
+
+/* Internal helpers */
+static size_t
+parse_workspace_index(const char *name)
+{
+       /*
+        * We only want to get positive numbers which span the whole string.
+        *
+        * More detailed requirement:
+        *  .---------------.--------------.
+        *  |     Input     | Return value |
+        *  |---------------+--------------|
+        *  | "2nd desktop" |      0       |
+        *  |    "-50"      |      0       |
+        *  |     "0"       |      0       |
+        *  |    "124"      |     124      |
+        *  |    "1.24"     |      0       |
+        *  `------------------------------ยด
+        *
+        * As atoi() happily parses any numbers until it hits a non-number we
+        * can't really use it for this case. Instead, we use strtol() combined
+        * with further checks for the endptr (remaining non-number characters)
+        * and returned negative numbers.
+        */
+       long index;
+       char *endptr;
+       errno = 0;
+       index = strtol(name, &endptr, 10);
+       if (errno || *endptr != '\0' || index < 0) {
+               return 0;
+       }
+       return index;
+}
+
+/*
+ * TODO: set_source and draw_border are straight up copies from src/osd.c
+ * find some proper place for them instead of duplicating stuff.
+ */
+static void
+set_source(cairo_t *cairo, float *c)
+{
+       cairo_set_source_rgba(cairo, c[0], c[1], c[2], c[3]);
+}
+
+static void
+draw_border(cairo_t *cairo, double width, double height, double line_width)
+{
+       cairo_save(cairo);
+
+       double x, y, w, h;
+       /* The anchor point of a line is in the center */
+       x = y = line_width / 2;
+       w = width - line_width;
+       h = height - line_width;
+       cairo_set_line_width(cairo, line_width);
+       cairo_rectangle(cairo, x, y, w, h);
+       cairo_stroke(cairo);
+
+       cairo_restore(cairo);
+}
+
+static void
+_osd_update(struct server *server)
+{
+
+       struct theme *theme = server->theme;
+
+       /* Settings */
+       uint16_t margin = 10;
+       uint16_t padding = 2;
+       uint16_t rect_height = 20;
+       uint16_t rect_width = 20;
+       struct font font = {
+               .name = rc.font_name_osd,
+               .size = rc.font_size_osd,
+       };
+
+       /* Dimensions */
+       size_t workspace_count = wl_list_length(&server->workspaces);
+       uint16_t marker_width = workspace_count * (rect_width + padding) - padding;
+       uint16_t width = margin * 2 + (marker_width < 200 ? 200 : marker_width);
+       uint16_t height = margin * 3 + rect_height + font_height(&font);
+
+       cairo_t *cairo;
+       cairo_surface_t *surface;
+       struct workspace *workspace;
+
+       struct output *output;
+       wl_list_for_each(output, &server->outputs, link) {
+               struct lab_data_buffer *buffer = buffer_create_cairo(width, height,
+                       output->wlr_output->scale, true);
+               if (!buffer) {
+                       wlr_log(WLR_ERROR, "Failed to allocate buffer for workspace OSD");
+                       continue;
+               }
+
+               cairo = buffer->cairo;
+
+               /* Background */
+               set_source(cairo, theme->osd_bg_color);
+               cairo_rectangle(cairo, 0, 0, width, height);
+               cairo_fill(cairo);
+
+               /* Border */
+               set_source(cairo, theme->osd_border_color);
+               draw_border(cairo, width, height, theme->osd_border_width);
+
+               uint16_t x = (width - marker_width) / 2;
+               wl_list_for_each(workspace, &server->workspaces, link) {
+                       bool active =  workspace == server->workspace_current;
+                       set_source(cairo, server->theme->osd_label_text_color);
+                       cairo_rectangle(cairo, x, margin,
+                               rect_width - padding, rect_height);
+                       cairo_stroke(cairo);
+                       if (active) {
+                               cairo_rectangle(cairo, x, margin,
+                                       rect_width - padding, rect_height);
+                               cairo_fill(cairo);
+                       }
+                       x += rect_width + padding;
+               }
+
+               /* Text */
+               set_source(cairo, server->theme->osd_label_text_color);
+               PangoLayout *layout = pango_cairo_create_layout(cairo);
+               pango_layout_set_width(layout, (width - 2 * margin) * PANGO_SCALE);
+               pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
+
+               PangoFontDescription *desc = pango_font_description_new();
+               pango_font_description_set_family(desc, font.name);
+               pango_font_description_set_size(desc, font.size * PANGO_SCALE);
+               pango_layout_set_font_description(layout, desc);
+
+               /* Center workspace indicator on the x axis */
+               x = font_width(&font, server->workspace_current->name);
+               x = (width - x) / 2;
+               cairo_move_to(cairo, x, margin * 2 + rect_height);
+               //pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
+               pango_layout_set_font_description(layout, desc);
+               pango_font_description_free(desc);
+               pango_layout_set_text(layout, server->workspace_current->name, -1);
+               pango_cairo_show_layout(cairo, layout);
+
+               g_object_unref(layout);
+               surface = cairo_get_target(cairo);
+               cairo_surface_flush(surface);
+
+               if (!output->workspace_osd) {
+                       output->workspace_osd = wlr_scene_buffer_create(
+                               &server->scene->tree, NULL);
+               }
+               /* Position the whole thing */
+               struct wlr_box output_box;
+               wlr_output_layout_get_box(output->server->output_layout,
+                       output->wlr_output, &output_box);
+               int lx = output->usable_area.x
+                       + (output->usable_area.width - width) / 2
+                       + output_box.x;
+               int ly = output->usable_area.y
+                       + (output->usable_area.height - height ) / 2
+                       + output_box.y;
+               wlr_scene_node_set_position(&output->workspace_osd->node, lx, ly);
+               wlr_scene_buffer_set_buffer(output->workspace_osd, &buffer->base);
+               wlr_scene_buffer_set_dest_size(output->workspace_osd,
+                       buffer->unscaled_width, buffer->unscaled_height);
+
+               /* And finally drop the buffer so it will get destroyed on OSD hide */
+               wlr_buffer_drop(&buffer->base);
+       }
+}
+
+/* Internal API */
+static void
+add_workspace(struct server *server, const char *name)
+{
+       struct workspace *workspace = calloc(1, sizeof(struct workspace));
+       workspace->server = server;
+       workspace->name = strdup(name);
+       workspace->tree = wlr_scene_tree_create(server->view_tree);
+       wl_list_insert(server->workspaces.prev, &workspace->link);
+       if (!server->workspace_current) {
+               server->workspace_current = workspace;
+       } else {
+               wlr_scene_node_set_enabled(&workspace->tree->node, false);
+       }
+}
+
+static struct workspace *
+get_prev(struct workspace *current, struct wl_list *workspaces)
+{
+       struct wl_list *target_link = current->link.prev;
+       if (target_link == workspaces) {
+               /* Current workspace is the first one, roll over */
+               target_link = target_link->prev;
+       }
+       return wl_container_of(target_link, current, link);
+}
+
+static struct workspace *
+get_next(struct workspace *current, struct wl_list *workspaces)
+{
+       struct wl_list *target_link = current->link.next;
+       if (target_link == workspaces) {
+               /* Current workspace is the last one, roll over */
+               target_link = target_link->next;
+       }
+       return wl_container_of(target_link, current, link);
+}
+
+static int
+_osd_handle_timeout(void *data)
+{
+       struct seat *seat = data;
+       workspaces_osd_hide(seat);
+       /* Don't re-check */
+       return 0;
+}
+
+static void
+_osd_show(struct server *server)
+{
+       if (!rc.workspace_config.popuptime) {
+               return;
+       }
+
+       _osd_update(server);
+       struct output *output;
+       wl_list_for_each(output, &server->outputs, link) {
+               wlr_scene_node_set_enabled(&output->workspace_osd->node, true);
+       }
+       struct wlr_keyboard *keyboard = &server->seat.keyboard_group->keyboard;
+       if (keyboard_any_modifiers_pressed(keyboard)) {
+               /* Hidden by release of all modifiers */
+               server->seat.workspace_osd_shown_by_modifier = true;
+       } else {
+               /* Hidden by timer */
+               if (!server->seat.workspace_osd_timer) {
+                       server->seat.workspace_osd_timer = wl_event_loop_add_timer(
+                               server->wl_event_loop, _osd_handle_timeout, &server->seat);
+               }
+               wl_event_source_timer_update(server->seat.workspace_osd_timer,
+                       rc.workspace_config.popuptime);
+       }
+}
+
+/* Public API */
+void
+workspaces_init(struct server *server)
+{
+       wl_list_init(&server->workspaces);
+
+       struct workspace *conf;
+       wl_list_for_each(conf, &rc.workspace_config.workspaces, link) {
+               add_workspace(server, conf->name);
+       }
+}
+
+void
+workspaces_switch_to(struct workspace *target)
+{
+       assert(target);
+       if (target == target->server->workspace_current) {
+               return;
+       }
+
+       /* Disable the old workspace */
+       wlr_scene_node_set_enabled(
+               &target->server->workspace_current->tree->node, false);
+
+       /* Enable the new workspace */
+       wlr_scene_node_set_enabled(&target->tree->node, true);
+
+       /* Make sure new views will spawn on the new workspace */
+       target->server->workspace_current = target;
+
+       /**
+        * Make sure we are focusing what the user sees.
+        *
+        * TODO: This is an issue for always-on-top views as they will
+        * loose keyboard focus once switching to another workspace.
+        */
+       desktop_focus_topmost_mapped_view(target->server);
+
+       /* And finally show the OSD */
+       _osd_show(target->server);
+}
+
+void
+workspaces_send_to(struct view *view, struct workspace *target)
+{
+       assert(view);
+       assert(target);
+       if (view->workspace == target) {
+               return;
+       }
+       wlr_scene_node_reparent(&view->scene_tree->node, target->tree);
+       view->workspace = target;
+}
+
+
+void
+workspaces_osd_hide(struct seat *seat)
+{
+       assert(seat);
+       struct output *output;
+       struct server *server = seat->server;
+       wl_list_for_each(output, &server->outputs, link) {
+               wlr_scene_node_set_enabled(&output->workspace_osd->node, false);
+               wlr_scene_buffer_set_buffer(output->workspace_osd, NULL);
+       }
+       seat->workspace_osd_shown_by_modifier = false;
+}
+
+struct workspace *
+workspaces_find(struct workspace *anchor, const char *name)
+{
+       assert(anchor);
+       if (!name) {
+               return NULL;
+       }
+       size_t index = 0;
+       struct workspace *target;
+       size_t wants_index = parse_workspace_index(name);
+       struct wl_list *workspaces = &anchor->server->workspaces;
+
+       if (wants_index) {
+               wl_list_for_each(target, workspaces, link) {
+                       if (wants_index == ++index) {
+                               return target;
+                       }
+               }
+       } else if (!strcasecmp(name, "left")) {
+               return get_prev(anchor, workspaces);
+       } else if (!strcasecmp(name, "right")) {
+               return get_next(anchor, workspaces);
+       } else {
+               wl_list_for_each(target, workspaces, link) {
+                       if (!strcasecmp(target->name, name)) {
+                               return target;
+                       }
+               }
+       }
+       wlr_log(WLR_ERROR, "Workspace '%s' not found", name);
+       return NULL;
+}
+
+void
+workspaces_destroy(struct server *server)
+{
+       struct workspace *workspace, *tmp;
+       wl_list_for_each_safe(workspace, tmp, &server->workspaces, link) {
+               wlr_scene_node_destroy(&workspace->tree->node);
+               zfree(workspace->name);
+               wl_list_remove(&workspace->link);
+               free(workspace);
+       }
+       assert(wl_list_empty(&server->workspaces));
+}