mirror of
https://github.com/labwc/labwc.git
synced 2026-04-03 07:15:34 -04:00
workspaces: Add workspaces.{c,h}
This commit is contained in:
parent
1afca2c2cd
commit
d557623c34
5 changed files with 417 additions and 0 deletions
|
|
@ -55,6 +55,11 @@ struct rcxml {
|
||||||
|
|
||||||
/* cycle view (alt+tab) */
|
/* cycle view (alt+tab) */
|
||||||
bool cycle_preview_contents;
|
bool cycle_preview_contents;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int popuptime;
|
||||||
|
struct wl_list workspaces; /* struct workspace.link */
|
||||||
|
} workspace_config;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct rcxml rc;
|
extern struct rcxml rc;
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,10 @@ struct seat {
|
||||||
struct wlr_idle *wlr_idle;
|
struct wlr_idle *wlr_idle;
|
||||||
struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_manager;
|
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 */
|
/* if set, views cannot receive focus */
|
||||||
struct wlr_layer_surface_v1 *focused_layer;
|
struct wlr_layer_surface_v1 *focused_layer;
|
||||||
|
|
||||||
|
|
@ -143,9 +147,11 @@ struct seat {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lab_data_buffer;
|
struct lab_data_buffer;
|
||||||
|
struct workspace;
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
struct wl_display *wl_display;
|
struct wl_display *wl_display;
|
||||||
|
struct wl_event_loop *wl_event_loop; /* Can be used for timer events */
|
||||||
struct wlr_renderer *renderer;
|
struct wlr_renderer *renderer;
|
||||||
struct wlr_allocator *allocator;
|
struct wlr_allocator *allocator;
|
||||||
struct wlr_backend *backend;
|
struct wlr_backend *backend;
|
||||||
|
|
@ -194,6 +200,10 @@ struct server {
|
||||||
/* Tree for built in menu */
|
/* Tree for built in menu */
|
||||||
struct wlr_scene_tree *menu_tree;
|
struct wlr_scene_tree *menu_tree;
|
||||||
|
|
||||||
|
/* Workspaces */
|
||||||
|
struct wl_list workspaces; /* struct workspace.link */
|
||||||
|
struct workspace *workspace_current;
|
||||||
|
|
||||||
struct wl_list outputs;
|
struct wl_list outputs;
|
||||||
struct wl_listener new_output;
|
struct wl_listener new_output;
|
||||||
struct wlr_output_layout *output_layout;
|
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_tree[LAB_NR_LAYERS];
|
||||||
struct wlr_scene_tree *layer_popup_tree;
|
struct wlr_scene_tree *layer_popup_tree;
|
||||||
struct wlr_scene_tree *osd_tree;
|
struct wlr_scene_tree *osd_tree;
|
||||||
|
struct wlr_scene_buffer *workspace_osd;
|
||||||
struct wlr_box usable_area;
|
struct wlr_box usable_area;
|
||||||
|
|
||||||
struct lab_data_buffer *osd_buffer;
|
struct lab_data_buffer *osd_buffer;
|
||||||
|
|
@ -276,6 +287,7 @@ struct view {
|
||||||
const struct view_impl *impl;
|
const struct view_impl *impl;
|
||||||
struct wl_list link;
|
struct wl_list link;
|
||||||
struct output *output;
|
struct output *output;
|
||||||
|
struct workspace *workspace;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
struct wlr_xdg_surface *xdg_surface;
|
struct wlr_xdg_surface *xdg_surface;
|
||||||
|
|
|
||||||
28
include/workspaces.h
Normal file
28
include/workspaces.h
Normal file
|
|
@ -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 */
|
||||||
|
|
@ -20,6 +20,7 @@ labwc_sources = files(
|
||||||
'theme.c',
|
'theme.c',
|
||||||
'view.c',
|
'view.c',
|
||||||
'view-impl.c',
|
'view-impl.c',
|
||||||
|
'workspaces.c',
|
||||||
'xdg.c',
|
'xdg.c',
|
||||||
'xdg-deco.c',
|
'xdg-deco.c',
|
||||||
'xdg-popup.c',
|
'xdg-popup.c',
|
||||||
|
|
|
||||||
371
src/workspaces.c
Normal file
371
src/workspaces.c
Normal file
|
|
@ -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));
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue