mirror of
				https://github.com/labwc/labwc.git
				synced 2025-11-03 09:01:51 -05:00 
			
		
		
		
	
						commit
						050af96d57
					
				
					 20 changed files with 729 additions and 71 deletions
				
			
		| 
						 | 
				
			
			@ -66,6 +66,14 @@ Actions are used in menus and keyboard/mouse bindings.
 | 
			
		|||
*<action name="ToggleAlwaysOnTop">*
 | 
			
		||||
	Toggle always-on-top of focused window.
 | 
			
		||||
 | 
			
		||||
*<action name="GoToDesktop"><to>*
 | 
			
		||||
	Switch to workspace. Supported values are "left", "right" or the full
 | 
			
		||||
	name of a workspace or its index (starting at 1) as configured in rc.xml.
 | 
			
		||||
 | 
			
		||||
*<action name="SendToDesktop"><to>*
 | 
			
		||||
	Send active window to workspace.
 | 
			
		||||
	Supported values are the same as for GoToDesktop.
 | 
			
		||||
 | 
			
		||||
# SEE ALSO
 | 
			
		||||
 | 
			
		||||
labwc(1), labwc-config(5), labwc-theme(5)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,6 +88,7 @@ The rest of this man page describes configuration options.
 | 
			
		|||
	Raise window to top when focused. Default is no.
 | 
			
		||||
 | 
			
		||||
## WINDOW SNAPPING
 | 
			
		||||
 | 
			
		||||
*<snapping><range>*
 | 
			
		||||
	The distance in pixels from the edge of an ouput for window Move
 | 
			
		||||
	operations to trigger SnapToEdge. A range of 0 disables window snapping.
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +97,18 @@ The rest of this man page describes configuration options.
 | 
			
		|||
*<snapping><topMaximize>* [yes|no]
 | 
			
		||||
	Maximize window if Move operation ends on the top edge. Default is yes.
 | 
			
		||||
 | 
			
		||||
## WORKSPACES
 | 
			
		||||
 | 
			
		||||
*<desktops><names><name>*
 | 
			
		||||
	Define workspaces. A workspace covers all outputs. The OSD only shows
 | 
			
		||||
	windows on the current workspace. Workspaces can be switched to with
 | 
			
		||||
	GoToDesktop and windows can be moved with SendToDesktop. See
 | 
			
		||||
	labwc-actions(5) for more information about their arguments.
 | 
			
		||||
 | 
			
		||||
*<desktops><popupTime>*
 | 
			
		||||
	Define the timeout after which to hide the workspace OSD.
 | 
			
		||||
	A setting of 0 disables the OSD. Default is 1000 ms.
 | 
			
		||||
 | 
			
		||||
## THEME
 | 
			
		||||
 | 
			
		||||
*<theme><name>*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,16 @@
 | 
			
		|||
	<item label="AlwaysOnTop">
 | 
			
		||||
		<action name="ToggleAlwaysOnTop" />
 | 
			
		||||
	</item>
 | 
			
		||||
	<menu id="workspaces" label="Workspace">
 | 
			
		||||
		<item label="Move left">
 | 
			
		||||
			<action name="SendToDesktop" to="left" />
 | 
			
		||||
			<action name="GoToDesktop" to="left" />
 | 
			
		||||
		</item>
 | 
			
		||||
		<item label="Move right">
 | 
			
		||||
			<action name="SendToDesktop" to="right" />
 | 
			
		||||
			<action name="GoToDesktop" to="right" />
 | 
			
		||||
		</item>
 | 
			
		||||
	</menu>
 | 
			
		||||
	<item label="Close">
 | 
			
		||||
		<action name="Close" />
 | 
			
		||||
	</item>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,32 @@
 | 
			
		|||
    <topMaximize>yes</topMaximize>
 | 
			
		||||
  </snapping>
 | 
			
		||||
 | 
			
		||||
  <!--
 | 
			
		||||
    Use GoToDesktop left | right to switch workspaces.
 | 
			
		||||
    Use SendToDesktop left | right to move windows.
 | 
			
		||||
    See man labwc-actions for futher information.
 | 
			
		||||
 | 
			
		||||
    Workspaces can be configured like this:
 | 
			
		||||
    <desktops>
 | 
			
		||||
      <popupTime>1000</popupTime>
 | 
			
		||||
      <names>
 | 
			
		||||
        <name>Workspace 1</name>
 | 
			
		||||
        <name>Workspace 2</name>
 | 
			
		||||
        <name>Workspace 3</name>
 | 
			
		||||
      </names>
 | 
			
		||||
    </desktops>
 | 
			
		||||
  -->
 | 
			
		||||
  <desktops>
 | 
			
		||||
    <!--
 | 
			
		||||
      popupTime defaults to 1000 so could be left out.
 | 
			
		||||
      Set to 0 to completely disable the workspace OSD.
 | 
			
		||||
    -->
 | 
			
		||||
    <popupTime>1000</popupTime>
 | 
			
		||||
    <names>
 | 
			
		||||
      <name>Default</name>
 | 
			
		||||
    </names>
 | 
			
		||||
  </desktops>
 | 
			
		||||
 | 
			
		||||
  <!--
 | 
			
		||||
    Keybind actions are specified in labwc-actions(5)
 | 
			
		||||
    The following keybind modifiers are supported:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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 */
 | 
			
		||||
							
								
								
									
										23
									
								
								src/action.c
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								src/action.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -9,6 +9,7 @@
 | 
			
		|||
#include "menu/menu.h"
 | 
			
		||||
#include "ssd.h"
 | 
			
		||||
#include "action.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
enum action_type {
 | 
			
		||||
	ACTION_TYPE_NONE = 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +32,8 @@ enum action_type {
 | 
			
		|||
	ACTION_TYPE_MOVE,
 | 
			
		||||
	ACTION_TYPE_RAISE,
 | 
			
		||||
	ACTION_TYPE_RESIZE,
 | 
			
		||||
	ACTION_TYPE_GO_TO_DESKTOP,
 | 
			
		||||
	ACTION_TYPE_SEND_TO_DESKTOP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const char *action_names[] = {
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +57,8 @@ const char *action_names[] = {
 | 
			
		|||
	"Move",
 | 
			
		||||
	"Raise",
 | 
			
		||||
	"Resize",
 | 
			
		||||
	"GoToDesktop",
 | 
			
		||||
	"SendToDesktop",
 | 
			
		||||
	NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -255,6 +260,24 @@ actions_run(struct view *activator, struct server *server,
 | 
			
		|||
					resize_edges);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case ACTION_TYPE_GO_TO_DESKTOP:
 | 
			
		||||
			{
 | 
			
		||||
				struct workspace *target;
 | 
			
		||||
				target = workspaces_find(server->workspace_current, action->arg);
 | 
			
		||||
				if (target) {
 | 
			
		||||
					workspaces_switch_to(target);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case ACTION_TYPE_SEND_TO_DESKTOP:
 | 
			
		||||
			if (view) {
 | 
			
		||||
				struct workspace *target;
 | 
			
		||||
				target = workspaces_find(view->workspace, action->arg);
 | 
			
		||||
				if (target) {
 | 
			
		||||
					workspaces_send_to(view, target);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case ACTION_TYPE_NONE:
 | 
			
		||||
			wlr_log(WLR_ERROR,
 | 
			
		||||
				"Not executing unknown action with arg %s",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@
 | 
			
		|||
#include "config/libinput.h"
 | 
			
		||||
#include "config/mousebind.h"
 | 
			
		||||
#include "config/rcxml.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
static bool in_keybind;
 | 
			
		||||
static bool in_mousebind;
 | 
			
		||||
| 
						 | 
				
			
			@ -74,10 +75,16 @@ fill_keybind(char *nodename, char *content)
 | 
			
		|||
		wlr_log(WLR_ERROR, "Action argument already set: %s",
 | 
			
		||||
			current_keybind_action->arg);
 | 
			
		||||
	} else if (!strcmp(nodename, "command.action")) {
 | 
			
		||||
		/* Execute */
 | 
			
		||||
		current_keybind_action->arg = strdup(content);
 | 
			
		||||
	} else if (!strcmp(nodename, "direction.action")) {
 | 
			
		||||
		/* MoveToEdge, SnapToEdge */
 | 
			
		||||
		current_keybind_action->arg = strdup(content);
 | 
			
		||||
	} else if (!strcmp(nodename, "menu.action")) {
 | 
			
		||||
		/* ShowMenu */
 | 
			
		||||
		current_keybind_action->arg = strdup(content);
 | 
			
		||||
	} else if (!strcmp(nodename, "to.action")) {
 | 
			
		||||
		/* GoToDesktop, SendToDesktop */
 | 
			
		||||
		current_keybind_action->arg = strdup(content);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -387,6 +394,12 @@ entry(xmlNode *node, char *nodename, char *content)
 | 
			
		|||
		rc.snap_top_maximize = get_bool(content);
 | 
			
		||||
	} else if (!strcasecmp(nodename, "cycleViewPreview.core")) {
 | 
			
		||||
		rc.cycle_preview_contents = get_bool(content);
 | 
			
		||||
	} else if (!strcasecmp(nodename, "name.names.desktops")) {
 | 
			
		||||
		struct workspace *workspace = calloc(1, sizeof(struct workspace));
 | 
			
		||||
		workspace->name = strdup(content);
 | 
			
		||||
		wl_list_insert(rc.workspace_config.workspaces.prev, &workspace->link);
 | 
			
		||||
	} else if (!strcasecmp(nodename, "popupTime.desktops")) {
 | 
			
		||||
		rc.workspace_config.popuptime = atoi(content);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -486,6 +499,8 @@ rcxml_init()
 | 
			
		|||
	rc.snap_edge_range = 1;
 | 
			
		||||
	rc.snap_top_maximize = true;
 | 
			
		||||
	rc.cycle_preview_contents = false;
 | 
			
		||||
	rc.workspace_config.popuptime = INT_MIN;
 | 
			
		||||
	wl_list_init(&rc.workspace_config.workspaces);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -620,6 +635,14 @@ post_processing(void)
 | 
			
		|||
		struct libinput_category *l = libinput_category_create();
 | 
			
		||||
		l->type = TOUCH_DEVICE;
 | 
			
		||||
	}
 | 
			
		||||
	if (!wl_list_length(&rc.workspace_config.workspaces)) {
 | 
			
		||||
		struct workspace *workspace = calloc(1, sizeof(struct workspace));
 | 
			
		||||
		workspace->name = strdup("Default");
 | 
			
		||||
		wl_list_insert(rc.workspace_config.workspaces.prev, &workspace->link);
 | 
			
		||||
	}
 | 
			
		||||
	if (rc.workspace_config.popuptime == INT_MIN) {
 | 
			
		||||
		rc.workspace_config.popuptime = 1000;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
| 
						 | 
				
			
			@ -718,6 +741,13 @@ rcxml_finish(void)
 | 
			
		|||
		zfree(l);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct workspace *w, *w_tmp;
 | 
			
		||||
	wl_list_for_each_safe(w, w_tmp, &rc.workspace_config.workspaces, link) {
 | 
			
		||||
		wl_list_remove(&w->link);
 | 
			
		||||
		zfree(w->name);
 | 
			
		||||
		zfree(w);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Reset state vars for starting fresh when Reload is triggered */
 | 
			
		||||
	current_keybind = NULL;
 | 
			
		||||
	current_mousebind = NULL;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
#include "node.h"
 | 
			
		||||
#include "ssd.h"
 | 
			
		||||
#include "common/scene-helpers.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
move_to_front(struct view *view)
 | 
			
		||||
| 
						 | 
				
			
			@ -162,73 +163,80 @@ isfocusable(struct view *view)
 | 
			
		|||
	return (view->mapped || view->minimized);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
has_focusable_view(struct wl_list *wl_list)
 | 
			
		||||
static struct wl_list *
 | 
			
		||||
get_prev_item(struct wl_list *item)
 | 
			
		||||
{
 | 
			
		||||
	struct view *view;
 | 
			
		||||
	wl_list_for_each (view, wl_list, link) {
 | 
			
		||||
		if (isfocusable(view)) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
	return item->prev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct wl_list *
 | 
			
		||||
get_next_item(struct wl_list *item)
 | 
			
		||||
{
 | 
			
		||||
	return item->next;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct view *
 | 
			
		||||
first_view(struct server *server)
 | 
			
		||||
{
 | 
			
		||||
	struct view *view;
 | 
			
		||||
	view = wl_container_of(server->views.next, view, link);
 | 
			
		||||
	return view;
 | 
			
		||||
	struct wlr_scene_node *node;
 | 
			
		||||
	struct wl_list *list_head =
 | 
			
		||||
		&server->workspace_current->tree->children;
 | 
			
		||||
	wl_list_for_each_reverse(node, list_head, link) {
 | 
			
		||||
		return node_view_from_node(node);
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct view *
 | 
			
		||||
desktop_cycle_view(struct server *server, struct view *current,
 | 
			
		||||
desktop_cycle_view(struct server *server, struct view *start_view,
 | 
			
		||||
		enum lab_cycle_dir dir)
 | 
			
		||||
{
 | 
			
		||||
	if (!has_focusable_view(&server->views)) {
 | 
			
		||||
	struct view *view = start_view ? start_view : first_view(server);
 | 
			
		||||
	if (!view) {
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
	start_view = view;
 | 
			
		||||
	struct wlr_scene_node *node = &view->scene_tree->node;
 | 
			
		||||
 | 
			
		||||
	struct view *view = current ? current : first_view(server);
 | 
			
		||||
	if (dir == LAB_CYCLE_DIR_FORWARD) {
 | 
			
		||||
		/* Replacement for wl_list_for_each_from() */
 | 
			
		||||
		do {
 | 
			
		||||
			view = wl_container_of(view->link.next, view, link);
 | 
			
		||||
		} while (&view->link == &server->views || !isfocusable(view));
 | 
			
		||||
	} else if (dir == LAB_CYCLE_DIR_BACKWARD) {
 | 
			
		||||
		do {
 | 
			
		||||
			view = wl_container_of(view->link.prev, view, link);
 | 
			
		||||
		} while (&view->link == &server->views || !isfocusable(view));
 | 
			
		||||
	}
 | 
			
		||||
	return view;
 | 
			
		||||
}
 | 
			
		||||
	assert(node->parent);
 | 
			
		||||
	struct wl_list *list_head = &node->parent->children;
 | 
			
		||||
	struct wl_list *list_item = &node->link;
 | 
			
		||||
	struct wl_list *(*iter)(struct wl_list *);
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
has_mapped_view(struct wl_list *wl_list)
 | 
			
		||||
{
 | 
			
		||||
	struct view *view;
 | 
			
		||||
	wl_list_for_each (view, wl_list, link) {
 | 
			
		||||
		if (view->mapped) {
 | 
			
		||||
			return true;
 | 
			
		||||
	/* Scene nodes are ordered like last node == displayed topmost */
 | 
			
		||||
	iter = dir == LAB_CYCLE_DIR_FORWARD ? get_prev_item : get_next_item;
 | 
			
		||||
 | 
			
		||||
	do {
 | 
			
		||||
		list_item = iter(list_item);
 | 
			
		||||
		if (list_item == list_head) {
 | 
			
		||||
			/* Start / End of list reached. Roll over */
 | 
			
		||||
			list_item = iter(list_item);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
		node = wl_container_of(list_item, node, link);
 | 
			
		||||
		view = node_view_from_node(node);
 | 
			
		||||
		if (isfocusable(view)) {
 | 
			
		||||
			return view;
 | 
			
		||||
		}
 | 
			
		||||
	} while (view != start_view);
 | 
			
		||||
 | 
			
		||||
	/* No focusable views found, including the one we started with */
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct view *
 | 
			
		||||
topmost_mapped_view(struct server *server)
 | 
			
		||||
{
 | 
			
		||||
	if (!has_mapped_view(&server->views)) {
 | 
			
		||||
		return NULL;
 | 
			
		||||
	struct view *view;
 | 
			
		||||
	struct wl_list *node_list;
 | 
			
		||||
	struct wlr_scene_node *node;
 | 
			
		||||
	node_list = &server->workspace_current->tree->children;
 | 
			
		||||
	wl_list_for_each_reverse(node, node_list, link) {
 | 
			
		||||
		view = node_view_from_node(node);
 | 
			
		||||
		if (view->mapped) {
 | 
			
		||||
			return view;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* start from tail of server->views */
 | 
			
		||||
	struct view *view = wl_container_of(server->views.prev, view, link);
 | 
			
		||||
	do {
 | 
			
		||||
		view = wl_container_of(view->link.next, view, link);
 | 
			
		||||
	} while (&view->link == &server->views || !view->mapped);
 | 
			
		||||
	return view;
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct view *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
// SPDX-License-Identifier: GPL-2.0-only
 | 
			
		||||
#include "labwc.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handle_toplevel_handle_request_minimize(struct wl_listener *listener, void *data)
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +43,9 @@ handle_toplevel_handle_request_activate(struct wl_listener *listener, void *data
 | 
			
		|||
	// struct wlr_foreign_toplevel_handle_v1_activated_event *event = data;
 | 
			
		||||
	/* In a multi-seat world we would select seat based on event->seat here. */
 | 
			
		||||
	if (view) {
 | 
			
		||||
		if (view->workspace != view->server->workspace_current) {
 | 
			
		||||
			workspaces_switch_to(view->workspace);
 | 
			
		||||
		}
 | 
			
		||||
		desktop_focus_and_activate_view(&view->server->seat, view);
 | 
			
		||||
		desktop_move_to_front(view);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
#include "buffer.h"
 | 
			
		||||
#include "key-state.h"
 | 
			
		||||
#include "labwc.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
change_vt(struct server *server, unsigned int vt)
 | 
			
		||||
| 
						 | 
				
			
			@ -38,17 +39,23 @@ keyboard_modifiers_notify(struct wl_listener *listener, void *data)
 | 
			
		|||
	struct seat *seat = wl_container_of(listener, seat, keyboard_modifiers);
 | 
			
		||||
	struct server *server = seat->server;
 | 
			
		||||
 | 
			
		||||
	if (server->cycle_view) {
 | 
			
		||||
	if (server->cycle_view || seat->workspace_osd_shown_by_modifier) {
 | 
			
		||||
		struct wlr_keyboard_key_event *event = data;
 | 
			
		||||
		struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard;
 | 
			
		||||
 | 
			
		||||
		if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED
 | 
			
		||||
				&& !keyboard_any_modifiers_pressed(keyboard))  {
 | 
			
		||||
			/* end cycle */
 | 
			
		||||
			desktop_focus_and_activate_view(&server->seat,
 | 
			
		||||
				server->cycle_view);
 | 
			
		||||
			desktop_move_to_front(server->cycle_view);
 | 
			
		||||
			server->cycle_view = NULL;
 | 
			
		||||
			osd_finish(server);
 | 
			
		||||
			if (server->cycle_view) {
 | 
			
		||||
				/* end cycle */
 | 
			
		||||
				desktop_focus_and_activate_view(&server->seat,
 | 
			
		||||
					server->cycle_view);
 | 
			
		||||
				desktop_move_to_front(server->cycle_view);
 | 
			
		||||
				server->cycle_view = NULL;
 | 
			
		||||
				osd_finish(server);
 | 
			
		||||
			}
 | 
			
		||||
			if (seat->workspace_osd_shown_by_modifier) {
 | 
			
		||||
				workspaces_osd_hide(seat);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -203,9 +203,20 @@ fill_item(char *nodename, char *content)
 | 
			
		|||
		 * compatibility with old openbox-menu generators
 | 
			
		||||
		 */
 | 
			
		||||
		current_item_action->arg = strdup(content);
 | 
			
		||||
	} else if (!strcmp(nodename, "to.action")) {
 | 
			
		||||
		current_item_action->arg = strdup(content);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
item_destroy(struct menuitem *item)
 | 
			
		||||
{
 | 
			
		||||
	wl_list_remove(&item->link);
 | 
			
		||||
	action_list_free(&item->actions);
 | 
			
		||||
	wlr_scene_node_destroy(&item->tree->node);
 | 
			
		||||
	free(item);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
entry(xmlNode *node, char *nodename, char *content)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -447,6 +458,38 @@ menu_configure(struct menu *menu, int lx, int ly, enum menu_align align)
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
menu_hide_submenu(const char *id)
 | 
			
		||||
{
 | 
			
		||||
	struct menu *menu, *hide_menu;
 | 
			
		||||
	hide_menu = menu_get_by_id(id);
 | 
			
		||||
	if (!hide_menu) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	for (int i = 0; i < nr_menus; ++i) {
 | 
			
		||||
		menu = menus + i;
 | 
			
		||||
		size_t item_index = 0;
 | 
			
		||||
		size_t items_destroyed = 0;
 | 
			
		||||
		struct menuitem *item, *item_tmp;
 | 
			
		||||
		wl_list_for_each_reverse_safe(item, item_tmp, &menu->menuitems, link) {
 | 
			
		||||
			if (item->submenu == hide_menu) {
 | 
			
		||||
				item_destroy(item);
 | 
			
		||||
				items_destroyed++;
 | 
			
		||||
				item_index++;
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			if (items_destroyed) {
 | 
			
		||||
				int y = (item_index - items_destroyed) * menu->item_height;
 | 
			
		||||
				wlr_scene_node_set_position(&item->tree->node, 0, y);
 | 
			
		||||
			}
 | 
			
		||||
			item_index++;
 | 
			
		||||
		}
 | 
			
		||||
		if (items_destroyed) {
 | 
			
		||||
			menu->size.height -= items_destroyed * menu->item_height;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
menu_init_rootmenu(struct server *server)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -487,9 +530,29 @@ menu_init_windowmenu(struct server *server)
 | 
			
		|||
		fill_item("name.action", "ToggleDecorations");
 | 
			
		||||
		current_item = item_create(menu, _("AlwaysOnTop"));
 | 
			
		||||
		fill_item("name.action", "ToggleAlwaysOnTop");
 | 
			
		||||
 | 
			
		||||
		/* Workspace sub-menu */
 | 
			
		||||
		struct menu *workspace_menu = menu_create(server, "workspaces", "");
 | 
			
		||||
		current_item = item_create(workspace_menu, _("Move left"));
 | 
			
		||||
		fill_item("name.action", "SendToDesktop");
 | 
			
		||||
		fill_item("to.action", "left");
 | 
			
		||||
		fill_item("name.action", "GoToDesktop");
 | 
			
		||||
		fill_item("to.action", "left");
 | 
			
		||||
		current_item = item_create(workspace_menu, _("Move right"));
 | 
			
		||||
		fill_item("name.action", "SendToDesktop");
 | 
			
		||||
		fill_item("to.action", "right");
 | 
			
		||||
		fill_item("name.action", "GoToDesktop");
 | 
			
		||||
		fill_item("to.action", "right");
 | 
			
		||||
		current_item = item_create(menu, _("Workspace"));
 | 
			
		||||
		current_item->submenu = workspace_menu;
 | 
			
		||||
 | 
			
		||||
		current_item = item_create(menu, _("Close"));
 | 
			
		||||
		fill_item("name.action", "Close");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (wl_list_length(&rc.workspace_config.workspaces) == 1) {
 | 
			
		||||
		menu_hide_submenu("workspaces");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
| 
						 | 
				
			
			@ -500,9 +563,7 @@ menu_finish(void)
 | 
			
		|||
		menu = menus + i;
 | 
			
		||||
		struct menuitem *item, *next;
 | 
			
		||||
		wl_list_for_each_safe(item, next, &menu->menuitems, link) {
 | 
			
		||||
			wl_list_remove(&item->link);
 | 
			
		||||
			action_list_free(&item->actions);
 | 
			
		||||
			free(item);
 | 
			
		||||
			item_destroy(item);
 | 
			
		||||
		}
 | 
			
		||||
		/**
 | 
			
		||||
		 * Destroying the root node will destroy everything,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ labwc_sources = files(
 | 
			
		|||
  'theme.c',
 | 
			
		||||
  'view.c',
 | 
			
		||||
  'view-impl.c',
 | 
			
		||||
  'workspaces.c',
 | 
			
		||||
  'xdg.c',
 | 
			
		||||
  'xdg-deco.c',
 | 
			
		||||
  'xdg-popup.c',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										61
									
								
								src/osd.c
									
										
									
									
									
								
							
							
						
						
									
										61
									
								
								src/osd.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -10,6 +10,8 @@
 | 
			
		|||
#include "config/rcxml.h"
 | 
			
		||||
#include "labwc.h"
 | 
			
		||||
#include "theme.h"
 | 
			
		||||
#include "node.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
#define OSD_ITEM_HEIGHT (20)
 | 
			
		||||
#define OSD_ITEM_WIDTH (600)
 | 
			
		||||
| 
						 | 
				
			
			@ -78,11 +80,13 @@ get_formatted_app_id(struct view *view)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
get_osd_height(struct wl_list *views)
 | 
			
		||||
get_osd_height(struct wl_list *node_list)
 | 
			
		||||
{
 | 
			
		||||
	int height = 0;
 | 
			
		||||
	struct view *view;
 | 
			
		||||
	wl_list_for_each(view, views, link) {
 | 
			
		||||
	struct wlr_scene_node *node;
 | 
			
		||||
	wl_list_for_each(node, node_list, link) {
 | 
			
		||||
		view = node_view_from_node(node);
 | 
			
		||||
		if (!isfocusable(view)) {
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -115,21 +119,32 @@ osd_finish(struct server *server)
 | 
			
		|||
void
 | 
			
		||||
osd_update(struct server *server)
 | 
			
		||||
{
 | 
			
		||||
	if (wl_list_empty(&server->views)) {
 | 
			
		||||
	struct wl_list *node_list =
 | 
			
		||||
		&server->workspace_current->tree->children;
 | 
			
		||||
 | 
			
		||||
	if (wl_list_empty(node_list)) {
 | 
			
		||||
		osd_finish(server);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct theme *theme = server->theme;
 | 
			
		||||
	bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1;
 | 
			
		||||
 | 
			
		||||
	struct buf buf;
 | 
			
		||||
	buf_init(&buf);
 | 
			
		||||
 | 
			
		||||
	struct view *view;
 | 
			
		||||
	struct output *output;
 | 
			
		||||
	struct wlr_scene_node *node;
 | 
			
		||||
	wl_list_for_each(output, &server->outputs, link) {
 | 
			
		||||
		destroy_osd_nodes(output);
 | 
			
		||||
		float scale = output->wlr_output->scale;
 | 
			
		||||
		int w = (OSD_ITEM_WIDTH + (2 * OSD_BORDER_WIDTH));
 | 
			
		||||
		int h = get_osd_height(&server->views);
 | 
			
		||||
		int w = OSD_ITEM_WIDTH + (2 * OSD_BORDER_WIDTH);
 | 
			
		||||
		int h = get_osd_height(node_list);
 | 
			
		||||
		if (show_workspace) {
 | 
			
		||||
			/* workspace indicator */
 | 
			
		||||
			h += OSD_ITEM_HEIGHT;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (output->osd_buffer) {
 | 
			
		||||
			wlr_buffer_drop(&output->osd_buffer->base);
 | 
			
		||||
| 
						 | 
				
			
			@ -148,10 +163,16 @@ osd_update(struct server *server)
 | 
			
		|||
		set_source(cairo, theme->osd_border_color);
 | 
			
		||||
		draw_border(cairo, w, h, theme->osd_border_width);
 | 
			
		||||
 | 
			
		||||
		/* highlight current window */
 | 
			
		||||
		int y = OSD_BORDER_WIDTH;
 | 
			
		||||
		struct view *view;
 | 
			
		||||
		wl_list_for_each(view, &server->views, link) {
 | 
			
		||||
 | 
			
		||||
		if (show_workspace) {
 | 
			
		||||
			/* workspace indicator */
 | 
			
		||||
			y += OSD_ITEM_HEIGHT;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* highlight current window */
 | 
			
		||||
		wl_list_for_each_reverse(node, node_list, link) {
 | 
			
		||||
			view = node_view_from_node(node);
 | 
			
		||||
			if (!isfocusable(view)) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -179,7 +200,6 @@ osd_update(struct server *server)
 | 
			
		|||
		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);
 | 
			
		||||
		pango_font_description_free(desc);
 | 
			
		||||
 | 
			
		||||
		PangoTabArray *tabs = pango_tab_array_new_with_positions(2, TRUE,
 | 
			
		||||
			PANGO_TAB_LEFT, OSD_TAB1, PANGO_TAB_LEFT, OSD_TAB2);
 | 
			
		||||
| 
						 | 
				
			
			@ -189,9 +209,28 @@ osd_update(struct server *server)
 | 
			
		|||
		pango_cairo_update_layout(cairo, layout);
 | 
			
		||||
 | 
			
		||||
		y = OSD_BORDER_WIDTH;
 | 
			
		||||
		y += (OSD_ITEM_HEIGHT - font_height(&font)) / 2;
 | 
			
		||||
 | 
			
		||||
		wl_list_for_each(view, &server->views, link) {
 | 
			
		||||
		/* Center text entries on the y axis */
 | 
			
		||||
		int y_offset = (OSD_ITEM_HEIGHT - font_height(&font)) / 2;
 | 
			
		||||
		y += y_offset;
 | 
			
		||||
 | 
			
		||||
		if (show_workspace) {
 | 
			
		||||
			/* Center workspace indicator on the x axis */
 | 
			
		||||
			int x = font_width(&font, server->workspace_current->name);
 | 
			
		||||
			x = (OSD_ITEM_WIDTH - x) / 2;
 | 
			
		||||
			cairo_move_to(cairo, x, y);
 | 
			
		||||
			pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
 | 
			
		||||
			pango_layout_set_font_description(layout, desc);
 | 
			
		||||
			pango_layout_set_text(layout, server->workspace_current->name, -1);
 | 
			
		||||
			pango_cairo_show_layout(cairo, layout);
 | 
			
		||||
			pango_font_description_set_weight(desc, PANGO_WEIGHT_NORMAL);
 | 
			
		||||
			pango_layout_set_font_description(layout, desc);
 | 
			
		||||
			y += OSD_ITEM_HEIGHT;
 | 
			
		||||
		}
 | 
			
		||||
		pango_font_description_free(desc);
 | 
			
		||||
 | 
			
		||||
		wl_list_for_each_reverse(node, node_list, link) {
 | 
			
		||||
			view = node_view_from_node(node);
 | 
			
		||||
			if (!isfocusable(view)) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@
 | 
			
		|||
#include "menu/menu.h"
 | 
			
		||||
#include "ssd.h"
 | 
			
		||||
#include "theme.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
#define LAB_XDG_SHELL_VERSION (2)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -181,6 +182,7 @@ server_init(struct server *server)
 | 
			
		|||
		event_loop, SIGINT, handle_sigterm, server->wl_display);
 | 
			
		||||
	sigterm_source = wl_event_loop_add_signal(
 | 
			
		||||
		event_loop, SIGTERM, handle_sigterm, server->wl_display);
 | 
			
		||||
	server->wl_event_loop = event_loop;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * The backend is a feature which abstracts the underlying input and
 | 
			
		||||
| 
						 | 
				
			
			@ -245,6 +247,8 @@ server_init(struct server *server)
 | 
			
		|||
#endif
 | 
			
		||||
	server->menu_tree = wlr_scene_tree_create(&server->scene->tree);
 | 
			
		||||
 | 
			
		||||
	workspaces_init(server);
 | 
			
		||||
 | 
			
		||||
	output_init(server);
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
| 
						 | 
				
			
			@ -452,7 +456,7 @@ server_start(struct server *server)
 | 
			
		|||
void
 | 
			
		||||
server_finish(struct server *server)
 | 
			
		||||
{
 | 
			
		||||
/* TODO: clean up various scene_tree nodes */
 | 
			
		||||
 | 
			
		||||
#if HAVE_XWAYLAND
 | 
			
		||||
	wlr_xwayland_destroy(server->xwayland);
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -465,4 +469,7 @@ server_finish(struct server *server)
 | 
			
		|||
	wlr_output_layout_destroy(server->output_layout);
 | 
			
		||||
 | 
			
		||||
	wl_display_destroy(server->wl_display);
 | 
			
		||||
 | 
			
		||||
	/* TODO: clean up various scene_tree nodes */
 | 
			
		||||
	workspaces_destroy(server);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
#include "labwc.h"
 | 
			
		||||
#include "ssd.h"
 | 
			
		||||
#include "menu/menu.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -336,8 +337,8 @@ void
 | 
			
		|||
view_toggle_always_on_top(struct view *view)
 | 
			
		||||
{
 | 
			
		||||
	if (is_always_on_top(view)) {
 | 
			
		||||
		wlr_scene_node_reparent(&view->scene_tree->node,
 | 
			
		||||
			view->server->view_tree);
 | 
			
		||||
		view->workspace = view->server->workspace_current;
 | 
			
		||||
		wlr_scene_node_reparent(&view->scene_tree->node, view->workspace->tree);
 | 
			
		||||
	} else {
 | 
			
		||||
		wlr_scene_node_reparent(&view->scene_tree->node,
 | 
			
		||||
			view->server->view_tree_always_on_top);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
#include "labwc.h"
 | 
			
		||||
#include "node.h"
 | 
			
		||||
#include "ssd.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handle_new_xdg_popup(struct wl_listener *listener, void *data)
 | 
			
		||||
| 
						 | 
				
			
			@ -374,7 +375,8 @@ xdg_surface_new(struct wl_listener *listener, void *data)
 | 
			
		|||
	view->impl = &xdg_toplevel_view_impl;
 | 
			
		||||
	view->xdg_surface = xdg_surface;
 | 
			
		||||
 | 
			
		||||
	view->scene_tree = wlr_scene_tree_create(view->server->view_tree);
 | 
			
		||||
	view->workspace = server->workspace_current;
 | 
			
		||||
	view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
 | 
			
		||||
	wlr_scene_node_set_enabled(&view->scene_tree->node, false);
 | 
			
		||||
 | 
			
		||||
	struct wlr_scene_tree *tree = wlr_scene_xdg_surface_create(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
#include "labwc.h"
 | 
			
		||||
#include "node.h"
 | 
			
		||||
#include "ssd.h"
 | 
			
		||||
#include "workspaces.h"
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handle_commit(struct wl_listener *listener, void *data)
 | 
			
		||||
| 
						 | 
				
			
			@ -434,7 +435,8 @@ xwayland_surface_new(struct wl_listener *listener, void *data)
 | 
			
		|||
	view->impl = &xwl_view_impl;
 | 
			
		||||
	view->xwayland_surface = xsurface;
 | 
			
		||||
 | 
			
		||||
	view->scene_tree = wlr_scene_tree_create(view->server->view_tree);
 | 
			
		||||
	view->workspace = server->workspace_current;
 | 
			
		||||
	view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
 | 
			
		||||
	node_descriptor_create(&view->scene_tree->node,
 | 
			
		||||
		LAB_NODE_DESC_VIEW, view);
 | 
			
		||||
	xsurface->data = view;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue