2022-06-15 01:07:45 +02:00
|
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2025-07-28 01:02:01 -04:00
|
|
|
|
#include "workspaces.h"
|
2022-06-15 01:07:45 +02:00
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
#include <cairo.h>
|
|
|
|
|
|
#include <pango/pangocairo.h>
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#include <strings.h>
|
2025-09-06 20:06:32 -04:00
|
|
|
|
#include <wlr/types/wlr_output_layout.h>
|
2025-07-28 01:22:10 -04:00
|
|
|
|
#include <wlr/types/wlr_scene.h>
|
2022-11-26 16:06:22 -05:00
|
|
|
|
#include "buffer.h"
|
2022-06-15 01:07:45 +02:00
|
|
|
|
#include "common/font.h"
|
2022-08-20 20:11:58 +02:00
|
|
|
|
#include "common/graphic-helpers.h"
|
2022-10-05 08:43:56 +02:00
|
|
|
|
#include "common/list.h"
|
2022-09-16 18:41:02 -04:00
|
|
|
|
#include "common/mem.h"
|
2025-08-17 16:01:50 -04:00
|
|
|
|
#include "config/rcxml.h"
|
2023-09-03 19:15:50 +02:00
|
|
|
|
#include "input/keyboard.h"
|
2022-10-05 08:43:56 +02:00
|
|
|
|
#include "labwc.h"
|
2025-07-26 15:34:45 -04:00
|
|
|
|
#include "output.h"
|
2024-07-24 01:04:44 +02:00
|
|
|
|
#include "protocols/cosmic-workspaces.h"
|
2024-12-10 06:26:19 +01:00
|
|
|
|
#include "protocols/ext-workspace.h"
|
2025-08-17 16:01:50 -04:00
|
|
|
|
#include "theme.h"
|
2022-12-29 04:50:21 +01:00
|
|
|
|
#include "view.h"
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
2024-07-24 01:04:44 +02:00
|
|
|
|
#define COSMIC_WORKSPACES_VERSION 1
|
2024-12-10 06:26:19 +01:00
|
|
|
|
#define EXT_WORKSPACES_VERSION 1
|
2024-07-24 01:04:44 +02:00
|
|
|
|
|
2022-06-15 01:07:45 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
_osd_update(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct theme *theme = server->theme;
|
|
|
|
|
|
|
|
|
|
|
|
/* Settings */
|
|
|
|
|
|
uint16_t margin = 10;
|
|
|
|
|
|
uint16_t padding = 2;
|
2023-12-11 12:41:33 +03:00
|
|
|
|
uint16_t rect_height = theme->osd_workspace_switcher_boxes_height;
|
|
|
|
|
|
uint16_t rect_width = theme->osd_workspace_switcher_boxes_width;
|
|
|
|
|
|
bool hide_boxes = theme->osd_workspace_switcher_boxes_width == 0 ||
|
|
|
|
|
|
theme->osd_workspace_switcher_boxes_height == 0;
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
/* Dimensions */
|
2024-07-25 21:56:05 +02:00
|
|
|
|
size_t workspace_count = wl_list_length(&server->workspaces.all);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
uint16_t marker_width = workspace_count * (rect_width + padding) - padding;
|
|
|
|
|
|
uint16_t width = margin * 2 + (marker_width < 200 ? 200 : marker_width);
|
2023-12-11 12:41:33 +03:00
|
|
|
|
uint16_t height = margin * (hide_boxes ? 2 : 3) + rect_height + font_height(&rc.font_osd);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
cairo_t *cairo;
|
|
|
|
|
|
cairo_surface_t *surface;
|
|
|
|
|
|
struct workspace *workspace;
|
|
|
|
|
|
|
|
|
|
|
|
struct output *output;
|
|
|
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
2023-05-10 18:37:28 +02:00
|
|
|
|
if (!output_is_usable(output)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2022-06-15 01:07:45 +02:00
|
|
|
|
struct lab_data_buffer *buffer = buffer_create_cairo(width, height,
|
2024-10-06 01:42:54 -04:00
|
|
|
|
output->wlr_output->scale);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
if (!buffer) {
|
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to allocate buffer for workspace OSD");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-28 06:58:17 +09:00
|
|
|
|
cairo = cairo_create(buffer->surface);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
/* Background */
|
2022-08-20 20:11:58 +02:00
|
|
|
|
set_cairo_color(cairo, theme->osd_bg_color);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
cairo_rectangle(cairo, 0, 0, width, height);
|
|
|
|
|
|
cairo_fill(cairo);
|
|
|
|
|
|
|
|
|
|
|
|
/* Border */
|
2022-08-20 20:11:58 +02:00
|
|
|
|
set_cairo_color(cairo, theme->osd_border_color);
|
2025-05-23 13:09:15 +09:00
|
|
|
|
struct wlr_fbox border_fbox = {
|
2023-06-29 17:45:33 +01:00
|
|
|
|
.width = width,
|
|
|
|
|
|
.height = height,
|
|
|
|
|
|
};
|
2025-05-23 13:09:15 +09:00
|
|
|
|
draw_cairo_border(cairo, border_fbox, theme->osd_border_width);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
2023-12-11 12:41:33 +03:00
|
|
|
|
/* Boxes */
|
|
|
|
|
|
uint16_t x;
|
|
|
|
|
|
if (!hide_boxes) {
|
|
|
|
|
|
x = (width - marker_width) / 2;
|
2024-07-25 21:56:05 +02:00
|
|
|
|
wl_list_for_each(workspace, &server->workspaces.all, link) {
|
|
|
|
|
|
bool active = workspace == server->workspaces.current;
|
2023-12-11 12:41:33 +03:00
|
|
|
|
set_cairo_color(cairo, server->theme->osd_label_text_color);
|
2025-04-06 14:49:11 +04:00
|
|
|
|
struct wlr_fbox fbox = {
|
|
|
|
|
|
.x = x,
|
|
|
|
|
|
.y = margin,
|
|
|
|
|
|
.width = rect_width,
|
|
|
|
|
|
.height = rect_height,
|
|
|
|
|
|
};
|
2025-04-06 15:05:04 +04:00
|
|
|
|
draw_cairo_border(cairo, fbox,
|
|
|
|
|
|
theme->osd_workspace_switcher_boxes_border_width);
|
2023-12-11 12:41:33 +03:00
|
|
|
|
if (active) {
|
|
|
|
|
|
cairo_rectangle(cairo, x, margin,
|
2025-04-06 14:49:11 +04:00
|
|
|
|
rect_width, rect_height);
|
2023-12-11 12:41:33 +03:00
|
|
|
|
cairo_fill(cairo);
|
|
|
|
|
|
}
|
|
|
|
|
|
x += rect_width + padding;
|
2022-06-15 01:07:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Text */
|
2022-08-20 20:11:58 +02:00
|
|
|
|
set_cairo_color(cairo, server->theme->osd_label_text_color);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
PangoLayout *layout = pango_cairo_create_layout(cairo);
|
2024-07-14 08:55:30 +01:00
|
|
|
|
pango_context_set_round_glyph_positions(pango_layout_get_context(layout), false);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
|
|
|
|
|
|
|
|
|
|
|
/* Center workspace indicator on the x axis */
|
2024-07-25 21:56:05 +02:00
|
|
|
|
int req_width = font_width(&rc.font_osd, server->workspaces.current->name);
|
2024-03-18 20:58:18 +10:00
|
|
|
|
req_width = MIN(req_width, width - 2 * margin);
|
|
|
|
|
|
x = (width - req_width) / 2;
|
2023-12-11 12:41:33 +03:00
|
|
|
|
if (!hide_boxes) {
|
|
|
|
|
|
cairo_move_to(cairo, x, margin * 2 + rect_height);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
cairo_move_to(cairo, x, (height - font_height(&rc.font_osd)) / 2.0);
|
|
|
|
|
|
}
|
2024-03-18 20:58:18 +10:00
|
|
|
|
PangoFontDescription *desc = font_to_pango_desc(&rc.font_osd);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
//pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
|
|
|
|
|
|
pango_layout_set_font_description(layout, desc);
|
2024-03-18 20:58:18 +10:00
|
|
|
|
pango_layout_set_width(layout, req_width * PANGO_SCALE);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
pango_font_description_free(desc);
|
2024-07-25 21:56:05 +02:00
|
|
|
|
pango_layout_set_text(layout, server->workspaces.current->name, -1);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
pango_cairo_show_layout(cairo, layout);
|
|
|
|
|
|
|
|
|
|
|
|
g_object_unref(layout);
|
|
|
|
|
|
surface = cairo_get_target(cairo);
|
|
|
|
|
|
cairo_surface_flush(surface);
|
2024-11-28 06:58:17 +09:00
|
|
|
|
cairo_destroy(cairo);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
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
|
2022-09-17 12:31:07 +01:00
|
|
|
|
+ (output->usable_area.height - height) / 2
|
2022-06-15 01:07:45 +02:00
|
|
|
|
+ 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,
|
2024-10-06 01:42:54 -04:00
|
|
|
|
buffer->logical_width, buffer->logical_height);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
/* And finally drop the buffer so it will get destroyed on OSD hide */
|
|
|
|
|
|
wlr_buffer_drop(&buffer->base);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-24 01:04:44 +02:00
|
|
|
|
/* cosmic workspace handlers */
|
|
|
|
|
|
static void
|
2024-11-18 20:45:11 +01:00
|
|
|
|
handle_cosmic_workspace_activate(struct wl_listener *listener, void *data)
|
2024-07-24 01:04:44 +02:00
|
|
|
|
{
|
2024-11-18 20:45:11 +01:00
|
|
|
|
struct workspace *workspace = wl_container_of(listener, workspace, on_cosmic.activate);
|
2024-07-24 01:04:44 +02:00
|
|
|
|
workspaces_switch_to(workspace, /* update_focus */ true);
|
2024-11-18 20:45:11 +01:00
|
|
|
|
wlr_log(WLR_INFO, "cosmic activating workspace %s", workspace->name);
|
2024-07-24 01:04:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-10 06:26:19 +01:00
|
|
|
|
/* ext workspace handlers */
|
|
|
|
|
|
static void
|
|
|
|
|
|
handle_ext_workspace_activate(struct wl_listener *listener, void *data)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct workspace *workspace = wl_container_of(listener, workspace, on_ext.activate);
|
|
|
|
|
|
workspaces_switch_to(workspace, /* update_focus */ true);
|
|
|
|
|
|
wlr_log(WLR_INFO, "ext activating workspace %s", workspace->name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-15 01:07:45 +02:00
|
|
|
|
/* Internal API */
|
|
|
|
|
|
static void
|
|
|
|
|
|
add_workspace(struct server *server, const char *name)
|
|
|
|
|
|
{
|
2022-09-18 15:22:26 -04:00
|
|
|
|
struct workspace *workspace = znew(*workspace);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
workspace->server = server;
|
2022-09-16 18:41:02 -04:00
|
|
|
|
workspace->name = xstrdup(name);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
workspace->tree = wlr_scene_tree_create(server->view_tree);
|
2024-07-25 21:56:05 +02:00
|
|
|
|
wl_list_append(&server->workspaces.all, &workspace->link);
|
|
|
|
|
|
if (!server->workspaces.current) {
|
|
|
|
|
|
server->workspaces.current = workspace;
|
2022-06-15 01:07:45 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
wlr_scene_node_set_enabled(&workspace->tree->node, false);
|
|
|
|
|
|
}
|
2024-07-24 01:04:44 +02:00
|
|
|
|
|
|
|
|
|
|
bool active = server->workspaces.current == workspace;
|
2024-11-18 20:45:11 +01:00
|
|
|
|
|
|
|
|
|
|
/* cosmic */
|
2024-07-24 01:04:44 +02:00
|
|
|
|
workspace->cosmic_workspace = lab_cosmic_workspace_create(server->workspaces.cosmic_group);
|
|
|
|
|
|
lab_cosmic_workspace_set_name(workspace->cosmic_workspace, name);
|
|
|
|
|
|
lab_cosmic_workspace_set_active(workspace->cosmic_workspace, active);
|
|
|
|
|
|
|
2024-11-18 20:45:11 +01:00
|
|
|
|
workspace->on_cosmic.activate.notify = handle_cosmic_workspace_activate;
|
|
|
|
|
|
wl_signal_add(&workspace->cosmic_workspace->events.activate,
|
|
|
|
|
|
&workspace->on_cosmic.activate);
|
2024-12-10 06:26:19 +01:00
|
|
|
|
|
|
|
|
|
|
/* ext */
|
|
|
|
|
|
workspace->ext_workspace = lab_ext_workspace_create(
|
|
|
|
|
|
server->workspaces.ext_manager, /*id*/ NULL);
|
|
|
|
|
|
lab_ext_workspace_assign_to_group(workspace->ext_workspace, server->workspaces.ext_group);
|
|
|
|
|
|
lab_ext_workspace_set_name(workspace->ext_workspace, name);
|
|
|
|
|
|
lab_ext_workspace_set_active(workspace->ext_workspace, active);
|
|
|
|
|
|
|
|
|
|
|
|
workspace->on_ext.activate.notify = handle_ext_workspace_activate;
|
|
|
|
|
|
wl_signal_add(&workspace->ext_workspace->events.activate,
|
|
|
|
|
|
&workspace->on_ext.activate);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct workspace *
|
2023-05-14 20:44:59 +03:00
|
|
|
|
get_prev(struct workspace *current, struct wl_list *workspaces, bool wrap)
|
2022-06-15 01:07:45 +02:00
|
|
|
|
{
|
|
|
|
|
|
struct wl_list *target_link = current->link.prev;
|
|
|
|
|
|
if (target_link == workspaces) {
|
2023-05-14 20:44:59 +03:00
|
|
|
|
/* Current workspace is the first one */
|
|
|
|
|
|
if (!wrap) {
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* Roll over */
|
2022-06-15 01:07:45 +02:00
|
|
|
|
target_link = target_link->prev;
|
|
|
|
|
|
}
|
|
|
|
|
|
return wl_container_of(target_link, current, link);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct workspace *
|
2023-05-14 20:44:59 +03:00
|
|
|
|
get_next(struct workspace *current, struct wl_list *workspaces, bool wrap)
|
2022-06-15 01:07:45 +02:00
|
|
|
|
{
|
|
|
|
|
|
struct wl_list *target_link = current->link.next;
|
|
|
|
|
|
if (target_link == workspaces) {
|
2023-05-14 20:44:59 +03:00
|
|
|
|
/* Current workspace is the last one */
|
|
|
|
|
|
if (!wrap) {
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* Roll over */
|
2022-06-15 01:07:45 +02:00
|
|
|
|
target_link = target_link->next;
|
|
|
|
|
|
}
|
|
|
|
|
|
return wl_container_of(target_link, current, link);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-06 08:10:18 +08:00
|
|
|
|
static bool
|
|
|
|
|
|
workspace_has_views(struct workspace *workspace, struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct view *view;
|
|
|
|
|
|
|
|
|
|
|
|
for_each_view(view, &server->views, LAB_VIEW_CRITERIA_NO_OMNIPRESENT) {
|
|
|
|
|
|
if (view->workspace == workspace) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct workspace *
|
|
|
|
|
|
get_adjacent_occupied(struct workspace *current, struct wl_list *workspaces,
|
|
|
|
|
|
bool wrap, bool reverse)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct server *server = current->server;
|
|
|
|
|
|
struct wl_list *start = ¤t->link;
|
|
|
|
|
|
struct wl_list *link = reverse ? start->prev : start->next;
|
|
|
|
|
|
bool has_wrapped = false;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
/* Handle list boundaries */
|
|
|
|
|
|
if (link == workspaces) {
|
|
|
|
|
|
if (!wrap) {
|
|
|
|
|
|
break; /* No wrapping allowed - stop searching */
|
|
|
|
|
|
}
|
|
|
|
|
|
if (has_wrapped) {
|
|
|
|
|
|
break; /* Already wrapped once - stop to prevent infinite loop */
|
|
|
|
|
|
}
|
|
|
|
|
|
/* Wrap around */
|
|
|
|
|
|
link = reverse ? workspaces->prev : workspaces->next;
|
|
|
|
|
|
has_wrapped = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Get the workspace */
|
|
|
|
|
|
struct workspace *target = wl_container_of(link, target, link);
|
|
|
|
|
|
|
|
|
|
|
|
/* Check if we've come full circle */
|
|
|
|
|
|
if (link == start) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Check if it's occupied (and not current) */
|
|
|
|
|
|
if (target != current && workspace_has_views(target, server)) {
|
|
|
|
|
|
return target;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Move to next/prev */
|
|
|
|
|
|
link = reverse ? link->prev : link->next;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return NULL; /* No occupied workspace found */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct workspace *
|
|
|
|
|
|
get_prev_occupied(struct workspace *current, struct wl_list *workspaces, bool wrap)
|
|
|
|
|
|
{
|
|
|
|
|
|
return get_adjacent_occupied(current, workspaces, wrap, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct workspace *
|
|
|
|
|
|
get_next_occupied(struct workspace *current, struct wl_list *workspaces, bool wrap)
|
|
|
|
|
|
{
|
|
|
|
|
|
return get_adjacent_occupied(current, workspaces, wrap, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-15 01:07:45 +02:00
|
|
|
|
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) {
|
2023-05-10 18:37:28 +02:00
|
|
|
|
if (output_is_usable(output) && output->workspace_osd) {
|
|
|
|
|
|
wlr_scene_node_set_enabled(&output->workspace_osd->node, true);
|
|
|
|
|
|
}
|
2022-06-15 01:07:45 +02:00
|
|
|
|
}
|
2025-01-10 11:02:58 +01:00
|
|
|
|
if (keyboard_get_all_modifiers(&server->seat)) {
|
2022-06-15 01:07:45 +02:00
|
|
|
|
/* 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)
|
|
|
|
|
|
{
|
2024-07-24 01:04:44 +02:00
|
|
|
|
server->workspaces.cosmic_manager = lab_cosmic_workspace_manager_create(
|
|
|
|
|
|
server->wl_display, /* capabilities */ CW_CAP_WS_ACTIVATE,
|
|
|
|
|
|
COSMIC_WORKSPACES_VERSION);
|
|
|
|
|
|
|
2024-12-10 06:26:19 +01:00
|
|
|
|
server->workspaces.ext_manager = lab_ext_workspace_manager_create(
|
|
|
|
|
|
server->wl_display, /* capabilities */ WS_CAP_WS_ACTIVATE,
|
|
|
|
|
|
EXT_WORKSPACES_VERSION);
|
|
|
|
|
|
|
2024-07-24 01:04:44 +02:00
|
|
|
|
server->workspaces.cosmic_group = lab_cosmic_workspace_group_create(
|
|
|
|
|
|
server->workspaces.cosmic_manager);
|
|
|
|
|
|
|
2024-12-10 06:26:19 +01:00
|
|
|
|
server->workspaces.ext_group = lab_ext_workspace_group_create(
|
|
|
|
|
|
server->workspaces.ext_manager);
|
|
|
|
|
|
|
2024-07-25 21:56:05 +02:00
|
|
|
|
wl_list_init(&server->workspaces.all);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
struct workspace *conf;
|
|
|
|
|
|
wl_list_for_each(conf, &rc.workspace_config.workspaces, link) {
|
|
|
|
|
|
add_workspace(server, conf->name);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-27 18:37:28 -04:00
|
|
|
|
/*
|
|
|
|
|
|
* update_focus should normally be set to true. It is set to false only
|
|
|
|
|
|
* when this function is called from desktop_focus_view(), in order to
|
|
|
|
|
|
* avoid unnecessary extra focus changes and possible recursion.
|
|
|
|
|
|
*/
|
2022-06-15 01:07:45 +02:00
|
|
|
|
void
|
2023-09-27 18:37:28 -04:00
|
|
|
|
workspaces_switch_to(struct workspace *target, bool update_focus)
|
2022-06-15 01:07:45 +02:00
|
|
|
|
{
|
|
|
|
|
|
assert(target);
|
2022-12-29 04:49:13 +01:00
|
|
|
|
struct server *server = target->server;
|
2024-07-25 21:56:05 +02:00
|
|
|
|
if (target == server->workspaces.current) {
|
2022-06-15 01:07:45 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Disable the old workspace */
|
|
|
|
|
|
wlr_scene_node_set_enabled(
|
2024-07-25 21:56:05 +02:00
|
|
|
|
&server->workspaces.current->tree->node, false);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
2024-07-24 01:04:44 +02:00
|
|
|
|
lab_cosmic_workspace_set_active(
|
|
|
|
|
|
server->workspaces.current->cosmic_workspace, false);
|
2024-12-10 06:26:19 +01:00
|
|
|
|
lab_ext_workspace_set_active(
|
|
|
|
|
|
server->workspaces.current->ext_workspace, false);
|
2024-07-24 01:04:44 +02:00
|
|
|
|
|
2023-11-25 17:54:36 -06:00
|
|
|
|
/* Move Omnipresent views to new workspace */
|
|
|
|
|
|
struct view *view;
|
|
|
|
|
|
enum lab_view_criteria criteria =
|
|
|
|
|
|
LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
|
2024-11-12 20:35:00 +01:00
|
|
|
|
for_each_view_reverse(view, &server->views, criteria) {
|
2023-11-25 17:54:36 -06:00
|
|
|
|
if (view->visible_on_all_workspaces) {
|
|
|
|
|
|
view_move_to_workspace(view, target);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-15 01:07:45 +02:00
|
|
|
|
/* Enable the new workspace */
|
|
|
|
|
|
wlr_scene_node_set_enabled(&target->tree->node, true);
|
|
|
|
|
|
|
2022-06-18 03:09:18 +03:00
|
|
|
|
/* Save the last visited workspace */
|
2024-07-25 21:56:05 +02:00
|
|
|
|
server->workspaces.last = server->workspaces.current;
|
2022-06-18 03:09:18 +03:00
|
|
|
|
|
2022-06-15 01:07:45 +02:00
|
|
|
|
/* Make sure new views will spawn on the new workspace */
|
2024-07-25 21:56:05 +02:00
|
|
|
|
server->workspaces.current = target;
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
2025-06-27 13:30:30 +02:00
|
|
|
|
struct view *grabbed_view = server->grabbed_view;
|
|
|
|
|
|
if (grabbed_view && !view_is_always_on_top(grabbed_view)) {
|
|
|
|
|
|
view_move_to_workspace(grabbed_view, target);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-29 04:50:21 +01:00
|
|
|
|
/*
|
2024-11-12 01:23:37 +01:00
|
|
|
|
* Make sure we are focusing what the user sees. Only refocus if
|
|
|
|
|
|
* the focus is not already on an omnipresent or always-on-top view.
|
|
|
|
|
|
*
|
|
|
|
|
|
* TODO: Decouple always-on-top views from the omnipresent state.
|
|
|
|
|
|
* One option for that would be to create a new scene tree
|
|
|
|
|
|
* as child of every workspace tree and then reparent a-o-t
|
|
|
|
|
|
* windows to that one. Combined with adjusting the condition
|
|
|
|
|
|
* below that should take care of the issue.
|
2022-06-15 01:07:45 +02:00
|
|
|
|
*/
|
2023-09-27 18:37:28 -04:00
|
|
|
|
if (update_focus) {
|
2025-05-23 13:09:15 +09:00
|
|
|
|
struct view *active_view = server->active_view;
|
|
|
|
|
|
if (!active_view || (!active_view->visible_on_all_workspaces
|
|
|
|
|
|
&& !view_is_always_on_top(active_view))) {
|
2023-09-23 11:51:47 -04:00
|
|
|
|
desktop_focus_topmost_view(server);
|
2023-09-27 18:37:28 -04:00
|
|
|
|
}
|
2022-12-29 04:50:21 +01:00
|
|
|
|
}
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
/* And finally show the OSD */
|
2022-12-29 04:49:13 +01:00
|
|
|
|
_osd_show(server);
|
2023-03-05 16:34:41 +01:00
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Make sure we are not carrying around a
|
|
|
|
|
|
* cursor image from the previous desktop
|
|
|
|
|
|
*/
|
|
|
|
|
|
cursor_update_focus(server);
|
2023-11-10 23:58:25 +01:00
|
|
|
|
|
|
|
|
|
|
/* Ensure that only currently visible fullscreen windows hide the top layer */
|
2025-01-08 23:35:23 -04:00
|
|
|
|
desktop_update_top_layer_visibility(server);
|
2024-07-24 01:04:44 +02:00
|
|
|
|
|
|
|
|
|
|
lab_cosmic_workspace_set_active(target->cosmic_workspace, true);
|
2024-12-10 06:26:19 +01:00
|
|
|
|
lab_ext_workspace_set_active(target->ext_workspace, true);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
2023-05-10 18:37:28 +02:00
|
|
|
|
if (!output->workspace_osd) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2022-06-15 01:07:45 +02:00
|
|
|
|
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;
|
2023-03-05 16:34:41 +01:00
|
|
|
|
|
|
|
|
|
|
/* Update the cursor focus in case it was on top of the OSD before */
|
|
|
|
|
|
cursor_update_focus(server);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct workspace *
|
2023-05-14 20:44:59 +03:00
|
|
|
|
workspaces_find(struct workspace *anchor, const char *name, bool wrap)
|
2022-06-15 01:07:45 +02:00
|
|
|
|
{
|
|
|
|
|
|
assert(anchor);
|
|
|
|
|
|
if (!name) {
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
size_t index = 0;
|
|
|
|
|
|
struct workspace *target;
|
|
|
|
|
|
size_t wants_index = parse_workspace_index(name);
|
2024-07-25 21:56:05 +02:00
|
|
|
|
struct wl_list *workspaces = &anchor->server->workspaces.all;
|
2022-06-15 01:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
if (wants_index) {
|
|
|
|
|
|
wl_list_for_each(target, workspaces, link) {
|
|
|
|
|
|
if (wants_index == ++index) {
|
|
|
|
|
|
return target;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-08-29 13:12:09 +03:00
|
|
|
|
} else if (!strcasecmp(name, "current")) {
|
|
|
|
|
|
return anchor;
|
2022-06-18 03:09:18 +03:00
|
|
|
|
} else if (!strcasecmp(name, "last")) {
|
2024-07-25 21:56:05 +02:00
|
|
|
|
return anchor->server->workspaces.last;
|
2022-06-15 01:07:45 +02:00
|
|
|
|
} else if (!strcasecmp(name, "left")) {
|
2023-05-14 20:44:59 +03:00
|
|
|
|
return get_prev(anchor, workspaces, wrap);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
} else if (!strcasecmp(name, "right")) {
|
2023-05-14 20:44:59 +03:00
|
|
|
|
return get_next(anchor, workspaces, wrap);
|
2025-06-06 08:10:18 +08:00
|
|
|
|
} else if (!strcasecmp(name, "left-occupied")) {
|
|
|
|
|
|
return get_prev_occupied(anchor, workspaces, wrap);
|
|
|
|
|
|
} else if (!strcasecmp(name, "right-occupied")) {
|
|
|
|
|
|
return get_next_occupied(anchor, workspaces, wrap);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
} 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-17 18:09:01 +09:00
|
|
|
|
static void
|
|
|
|
|
|
destroy_workspace(struct workspace *workspace)
|
|
|
|
|
|
{
|
|
|
|
|
|
wlr_scene_node_destroy(&workspace->tree->node);
|
|
|
|
|
|
zfree(workspace->name);
|
|
|
|
|
|
wl_list_remove(&workspace->link);
|
2025-03-13 17:39:23 +09:00
|
|
|
|
wl_list_remove(&workspace->on_cosmic.activate.link);
|
|
|
|
|
|
wl_list_remove(&workspace->on_ext.activate.link);
|
2024-07-24 01:04:44 +02:00
|
|
|
|
|
|
|
|
|
|
lab_cosmic_workspace_destroy(workspace->cosmic_workspace);
|
2024-12-10 06:26:19 +01:00
|
|
|
|
lab_ext_workspace_destroy(workspace->ext_workspace);
|
2024-04-17 18:09:01 +09:00
|
|
|
|
free(workspace);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
workspaces_reconfigure(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Compare actual workspace list with the new desired configuration to:
|
|
|
|
|
|
* - Update names
|
|
|
|
|
|
* - Add workspaces if more workspaces are desired
|
|
|
|
|
|
* - Destroy workspaces if fewer workspace are desired
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2024-07-25 21:56:05 +02:00
|
|
|
|
struct wl_list *actual_workspace_link = server->workspaces.all.next;
|
2024-04-17 18:09:01 +09:00
|
|
|
|
|
|
|
|
|
|
struct workspace *configured_workspace;
|
|
|
|
|
|
wl_list_for_each(configured_workspace,
|
|
|
|
|
|
&rc.workspace_config.workspaces, link) {
|
|
|
|
|
|
struct workspace *actual_workspace = wl_container_of(
|
|
|
|
|
|
actual_workspace_link, actual_workspace, link);
|
|
|
|
|
|
|
2024-07-25 21:56:05 +02:00
|
|
|
|
if (actual_workspace_link == &server->workspaces.all) {
|
2024-04-17 18:09:01 +09:00
|
|
|
|
/* # of configured workspaces increased */
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "Adding workspace \"%s\"",
|
|
|
|
|
|
configured_workspace->name);
|
|
|
|
|
|
add_workspace(server, configured_workspace->name);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (strcmp(actual_workspace->name, configured_workspace->name)) {
|
|
|
|
|
|
/* Workspace is renamed */
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "Renaming workspace \"%s\" to \"%s\"",
|
|
|
|
|
|
actual_workspace->name, configured_workspace->name);
|
|
|
|
|
|
free(actual_workspace->name);
|
|
|
|
|
|
actual_workspace->name = xstrdup(configured_workspace->name);
|
2024-07-24 01:04:44 +02:00
|
|
|
|
lab_cosmic_workspace_set_name(
|
|
|
|
|
|
actual_workspace->cosmic_workspace, actual_workspace->name);
|
2024-12-10 06:26:19 +01:00
|
|
|
|
lab_ext_workspace_set_name(
|
|
|
|
|
|
actual_workspace->ext_workspace, actual_workspace->name);
|
2024-04-17 18:09:01 +09:00
|
|
|
|
}
|
|
|
|
|
|
actual_workspace_link = actual_workspace_link->next;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-25 21:56:05 +02:00
|
|
|
|
if (actual_workspace_link == &server->workspaces.all) {
|
2024-04-17 18:09:01 +09:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* # of configured workspaces decreased */
|
2025-08-31 04:41:08 +09:00
|
|
|
|
overlay_finish(&server->seat);
|
2024-04-17 18:09:01 +09:00
|
|
|
|
struct workspace *first_workspace =
|
2024-07-25 21:56:05 +02:00
|
|
|
|
wl_container_of(server->workspaces.all.next, first_workspace, link);
|
2024-04-17 18:09:01 +09:00
|
|
|
|
|
2024-07-25 21:56:05 +02:00
|
|
|
|
while (actual_workspace_link != &server->workspaces.all) {
|
2024-04-17 18:09:01 +09:00
|
|
|
|
struct workspace *actual_workspace = wl_container_of(
|
|
|
|
|
|
actual_workspace_link, actual_workspace, link);
|
|
|
|
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "Destroying workspace \"%s\"",
|
|
|
|
|
|
actual_workspace->name);
|
|
|
|
|
|
|
|
|
|
|
|
struct view *view;
|
|
|
|
|
|
wl_list_for_each(view, &server->views, link) {
|
|
|
|
|
|
if (view->workspace == actual_workspace) {
|
|
|
|
|
|
view_move_to_workspace(view, first_workspace);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-25 21:56:05 +02:00
|
|
|
|
if (server->workspaces.current == actual_workspace) {
|
2024-04-17 18:09:01 +09:00
|
|
|
|
workspaces_switch_to(first_workspace,
|
|
|
|
|
|
/* update_focus */ true);
|
|
|
|
|
|
}
|
2024-07-25 21:56:05 +02:00
|
|
|
|
if (server->workspaces.last == actual_workspace) {
|
|
|
|
|
|
server->workspaces.last = first_workspace;
|
2024-04-17 18:09:01 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actual_workspace_link = actual_workspace_link->next;
|
|
|
|
|
|
destroy_workspace(actual_workspace);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-15 01:07:45 +02:00
|
|
|
|
void
|
|
|
|
|
|
workspaces_destroy(struct server *server)
|
|
|
|
|
|
{
|
|
|
|
|
|
struct workspace *workspace, *tmp;
|
2024-07-25 21:56:05 +02:00
|
|
|
|
wl_list_for_each_safe(workspace, tmp, &server->workspaces.all, link) {
|
2024-04-17 18:09:01 +09:00
|
|
|
|
destroy_workspace(workspace);
|
2022-06-15 01:07:45 +02:00
|
|
|
|
}
|
2024-07-25 21:56:05 +02:00
|
|
|
|
assert(wl_list_empty(&server->workspaces.all));
|
2022-06-15 01:07:45 +02:00
|
|
|
|
}
|