mirror of
				https://github.com/labwc/labwc.git
				synced 2025-10-29 05:40:24 -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) */ | ||||
| 	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 */ | ||||
|  | @ -20,6 +20,7 @@ labwc_sources = files( | |||
|   'theme.c', | ||||
|   'view.c', | ||||
|   'view-impl.c', | ||||
|   'workspaces.c', | ||||
|   'xdg.c', | ||||
|   'xdg-deco.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
	
	 Consolatis
						Consolatis