diff options
Diffstat (limited to 'sway/tree')
| -rw-r--r-- | sway/tree/container.c | 1016 | ||||
| -rw-r--r-- | sway/tree/criteria.c | 451 | ||||
| -rw-r--r-- | sway/tree/focus.c | 278 | ||||
| -rw-r--r-- | sway/tree/layout.c | 1773 | ||||
| -rw-r--r-- | sway/tree/output.c | 277 | ||||
| -rw-r--r-- | sway/tree/workspace.c | 373 | 
6 files changed, 4168 insertions, 0 deletions
| diff --git a/sway/tree/container.c b/sway/tree/container.c new file mode 100644 index 00000000..829fde69 --- /dev/null +++ b/sway/tree/container.c @@ -0,0 +1,1016 @@ +#define _XOPEN_SOURCE 500 +#include <ctype.h> +#include <stdlib.h> +#include <stdbool.h> +#include <strings.h> +#include <string.h> +#include "sway/config.h" +#include "sway/container.h" +#include "sway/workspace.h" +#include "sway/focus.h" +#include "sway/border.h" +#include "sway/layout.h" +#include "sway/input_state.h" +#include "sway/ipc-server.h" +#include "sway/output.h" +#include "log.h" +#include "stringop.h" + +#define ASSERT_NONNULL(PTR) \ +	sway_assert (PTR, #PTR "must be non-null") + +static swayc_t *new_swayc(enum swayc_types type) { +	// next id starts at 1 because 0 is assigned to root_container in layout.c +	static size_t next_id = 1; +	swayc_t *c = calloc(1, sizeof(swayc_t)); +	if (!c) { +		return NULL; +	} +	c->id = next_id++; +	c->handle = -1; +	c->gaps = -1; +	c->layout = L_NONE; +	c->workspace_layout = L_NONE; +	c->type = type; +	c->nb_master = 1; +	c->nb_slave_groups = 1; +	if (type != C_VIEW) { +		c->children = create_list(); +	} +	return c; +} + +static void free_swayc(swayc_t *cont) { +	if (!ASSERT_NONNULL(cont)) { +		return; +	} +	if (cont->children) { +		// remove children until there are no more, free_swayc calls +		// remove_child, which removes child from this container +		while (cont->children->length) { +			free_swayc(cont->children->items[0]); +		} +		list_free(cont->children); +	} +	if (cont->unmanaged) { +		list_free(cont->unmanaged); +	} +	if (cont->floating) { +		while (cont->floating->length) { +			free_swayc(cont->floating->items[0]); +		} +		list_free(cont->floating); +	} +	if (cont->marks) { +		list_foreach(cont->marks, free); +		list_free(cont->marks); +	} +	if (cont->parent) { +		remove_child(cont); +	} +	if (cont->name) { +		free(cont->name); +	} +	if (cont->class) { +		free(cont->class); +	} +	if (cont->instance) { +		free(cont->instance); +	} +	if (cont->app_id) { +		free(cont->app_id); +	} +	if (cont->bg_pid != 0) { +		terminate_swaybg(cont->bg_pid); +	} +	if (cont->border) { +		if (cont->border->buffer) { +			free(cont->border->buffer); +		} +		free(cont->border); +	} +	free(cont); +} + +static void update_root_geometry() { +	int width = 0; +	int height = 0; +	swayc_t *child; +	int child_width; +	int child_height; + +	for (int i = 0; i < root_container.children->length; ++i) { +		child = root_container.children->items[i]; +		child_width = child->width + child->x; +		child_height = child->height + child->y; +		if (child_width > width) { +			width = child_width; +		} + +		if (child_height > height) { +			height = child_height; +		} +	} + +	root_container.width = width; +	root_container.height = height; +} + +// New containers + +swayc_t *new_output(wlc_handle handle) { +	struct wlc_size size; +	output_get_scaled_size(handle, &size); +	const char *name = wlc_output_get_name(handle); +	// Find current outputs to see if this already exists +	{ +		int i, len = root_container.children->length; +		for (i = 0; i < len; ++i) { +			swayc_t *op = root_container.children->items[i]; +			const char *op_name = op->name; +			if (op_name && name && strcmp(op_name, name) == 0) { +				sway_log(L_DEBUG, "restoring output %" PRIuPTR ":%s", handle, op_name); +				return op; +			} +		} +	} + +	sway_log(L_DEBUG, "New output %" PRIuPTR ":%s", handle, name); + +	struct output_config *oc = NULL, *all = NULL; +	int i; +	for (i = 0; i < config->output_configs->length; ++i) { +		struct output_config *cur = config->output_configs->items[i]; +		if (strcasecmp(name, cur->name) == 0) { +			sway_log(L_DEBUG, "Matched output config for %s", name); +			oc = cur; +		} +		if (strcasecmp("*", cur->name) == 0) { +			sway_log(L_DEBUG, "Matched wildcard output config for %s", name); +			all = cur; +		} + +		if (oc && all) { +			break; +		} +	} + +	if (!oc) { +		oc = all; +	} + +	if (oc && !oc->enabled) { +		return NULL; +	} + +	swayc_t *output = new_swayc(C_OUTPUT); +	output->handle = handle; +	output->name = name ? strdup(name) : NULL; +	output->width = size.w; +	output->height = size.h; +	output->unmanaged = create_list(); +	output->bg_pid = 0; + +	apply_output_config(oc, output); +	add_child(&root_container, output); +	load_swaybars(); + +	// Create workspace +	char *ws_name = NULL; +	swayc_t *ws = NULL; + +	if (name) { +		for (i = 0; i < config->workspace_outputs->length; ++i) { +			struct workspace_output *wso = config->workspace_outputs->items[i]; +			if (strcasecmp(wso->output, name) == 0) { +				sway_log(L_DEBUG, "Matched workspace to output: %s for %s", wso->workspace, wso->output); +				// Check if any other workspaces are using this name +				if ((ws = workspace_by_name(wso->workspace))) { +					// if yes, move those to this output, because they should be here +					move_workspace_to(ws, output); +				} else if (!ws_name) { +					// set a workspace name in case we need to create a default one +					ws_name = strdup(wso->workspace); +				} +			} +		} +	} + +	if (output->children->length == 0) { +		if (!ws_name) { +			ws_name = workspace_next_name(output->name); +		} +		// create and initialize default workspace +		sway_log(L_DEBUG, "Creating default workspace %s", ws_name); +		ws = new_workspace(output, ws_name); +		ws->is_focused = true; +	} else { +		sort_workspaces(output); +		set_focused_container(output->children->items[0]); +	} + +	free(ws_name); +	update_root_geometry(); +	return output; +} + +swayc_t *new_workspace(swayc_t *output, const char *name) { +	if (!ASSERT_NONNULL(output)) { +		return NULL; +	} +	sway_log(L_DEBUG, "Added workspace %s for output %u", name, (unsigned int)output->handle); +	swayc_t *workspace = new_swayc(C_WORKSPACE); + +	workspace->prev_layout = L_NONE; +	workspace->layout = default_layout(output); +	workspace->workspace_layout = default_layout(output); + +	workspace->x = output->x; +	workspace->y = output->y; +	workspace->width = output->width; +	workspace->height = output->height; +	workspace->name = !name ? NULL : strdup(name); +	workspace->visible = false; +	workspace->floating = create_list(); + +	add_child(output, workspace); +	sort_workspaces(output); + +	return workspace; +} + +swayc_t *new_container(swayc_t *child, enum swayc_layouts layout) { +	if (!ASSERT_NONNULL(child) +			&& !sway_assert(!child->is_floating, "cannot create container around floating window")) { +		return NULL; +	} +	swayc_t *cont = new_swayc(C_CONTAINER); + +	sway_log(L_DEBUG, "creating container %p around %p", cont, child); + +	cont->prev_layout = L_NONE; +	cont->layout = layout; +	cont->width = child->width; +	cont->height = child->height; +	cont->x = child->x; +	cont->y = child->y; +	cont->visible = child->visible; +	cont->cached_geometry = child->cached_geometry; +	cont->gaps = child->gaps; + +	/* Container inherits all of workspaces children, layout and whatnot */ +	if (child->type == C_WORKSPACE) { +		swayc_t *workspace = child; +		// reorder focus +		cont->focused = workspace->focused; +		workspace->focused = cont; +		// set all children focu to container +		int i; +		for (i = 0; i < workspace->children->length; ++i) { +			((swayc_t *)workspace->children->items[i])->parent = cont; +		} +		// Swap children +		list_t  *tmp_list  = workspace->children; +		workspace->children = cont->children; +		cont->children = tmp_list; +		// add container to workspace chidren +		add_child(workspace, cont); +		// give them proper layouts +		cont->layout = workspace->workspace_layout; +		cont->prev_layout = workspace->prev_layout; +		/* TODO: might break shit in move_container!!! workspace->layout = layout; */ +		set_focused_container_for(workspace, get_focused_view(workspace)); +	} else { // Or is built around container +		swayc_t *parent = replace_child(child, cont); +		if (parent) { +			add_child(cont, child); +		} +	} +	return cont; +} + +swayc_t *new_view(swayc_t *sibling, wlc_handle handle) { +	if (!ASSERT_NONNULL(sibling)) { +		return NULL; +	} +	const char *title = wlc_view_get_title(handle); +	swayc_t *view = new_swayc(C_VIEW); +	sway_log(L_DEBUG, "Adding new view %" PRIuPTR ":%s to container %p %d", +		handle, title, sibling, sibling ? sibling->type : 0); +	// Setup values +	view->handle = handle; +	view->name = title ? strdup(title) : NULL; +	const char *class = wlc_view_get_class(handle); +	view->class = class ? strdup(class) : NULL; +	const char *instance = wlc_view_get_instance(handle); +	view->instance = instance ? strdup(instance) : NULL; +	const char *app_id = wlc_view_get_app_id(handle); +	view->app_id = app_id ? strdup(app_id) : NULL; +	view->visible = true; +	view->is_focused = true; +	view->sticky = false; +	view->width = 0; +	view->height = 0; +	view->desired_width = -1; +	view->desired_height = -1; +	// setup border +	view->border_type = config->border; +	view->border_thickness = config->border_thickness; + +	view->is_floating = false; + +	if (sibling->type == C_WORKSPACE) { +		// Case of focused workspace, just create as child of it +		add_child(sibling, view); +	} else { +		// Regular case, create as sibling of current container +		add_sibling(sibling, view); +	} +	return view; +} + +swayc_t *new_floating_view(wlc_handle handle) { +	if (swayc_active_workspace() == NULL) { +		return NULL; +	} +	const char *title = wlc_view_get_title(handle); +	swayc_t *view = new_swayc(C_VIEW); +	sway_log(L_DEBUG, "Adding new view %" PRIuPTR ":%x:%s as a floating view", +		handle, wlc_view_get_type(handle), title); +	// Setup values +	view->handle = handle; +	view->name = title ? strdup(title) : NULL; +	const char *class = wlc_view_get_class(handle); +	view->class = class ? strdup(class) : NULL; +	const char *instance = wlc_view_get_instance(handle); +	view->instance = instance ? strdup(instance) : NULL; +	const char *app_id = wlc_view_get_app_id(handle); +	view->app_id = app_id ? strdup(app_id) : NULL; +	view->visible = true; +	view->sticky = false; + +	// Set the geometry of the floating view +	const struct wlc_geometry *geometry = wlc_view_get_geometry(handle); + +	// give it requested geometry, but place in center if possible +	// in top left otherwise +	if (geometry->size.w != 0) { +		view->x = (swayc_active_workspace()->width - geometry->size.w) / 2; +	} else { +		view->x = 0; +	} +	if (geometry->size.h != 0) { +		view->y = (swayc_active_workspace()->height - geometry->size.h) / 2; +	} else { +		view->y = 0; +	} + +	view->width = geometry->size.w; +	view->height = geometry->size.h; + +	view->desired_width = view->width; +	view->desired_height = view->height; + +	// setup border +	view->border_type = config->floating_border; +	view->border_thickness = config->floating_border_thickness; + +	view->is_floating = true; + +	// Case of focused workspace, just create as child of it +	list_add(swayc_active_workspace()->floating, view); +	view->parent = swayc_active_workspace(); +	if (swayc_active_workspace()->focused == NULL) { +		set_focused_container_for(swayc_active_workspace(), view); +	} +	return view; +} + +void floating_view_sane_size(swayc_t *view) { +	// floating_minimum is used as sane value. +	// floating_maximum has priority in case of conflict +	// TODO: implement total_outputs_dimensions() +	if (config->floating_minimum_height != -1 && +		view->desired_height < config->floating_minimum_height) { +		view->desired_height = config->floating_minimum_height; +	} +	if (config->floating_minimum_width != -1 && +		view->desired_width < config->floating_minimum_width) { +		view->desired_width = config->floating_minimum_width; +	} + +	// if 0 do not resize, only enforce max value +	if (config->floating_maximum_height == 0) { +		// Missing total_outputs_dimensions() using swayc_active_workspace() +		config->floating_maximum_height = swayc_active_workspace()->height; + +	} else if (config->floating_maximum_height != -1 && +			view->desired_height > config->floating_maximum_height) { +		view->desired_height = config->floating_maximum_height; +	} + +	// if 0 do not resize, only enforce max value +	if (config->floating_maximum_width == 0) { +		// Missing total_outputs_dimensions() using swayc_active_workspace() +		config->floating_maximum_width = swayc_active_workspace()->width; + +	} else 	if (config->floating_maximum_width != -1 && +		view->desired_width > config->floating_maximum_width) { +		view->desired_width = config->floating_maximum_width; +	} + +	sway_log(L_DEBUG, "Sane values for view to %d x %d @ %.f, %.f", +		view->desired_width, view->desired_height, view->x, view->y); + +	return; +} + + +// Destroy container + +swayc_t *destroy_output(swayc_t *output) { +	if (!ASSERT_NONNULL(output)) { +		return NULL; +	} +	if (output->children->length > 0) { +		// TODO save workspaces when there are no outputs. +		// TODO also check if there will ever be no outputs except for exiting +		// program +		if (root_container.children->length > 1) { +			int p = root_container.children->items[0] == output; +			// Move workspace from this output to another output +			while (output->children->length) { +				swayc_t *child = output->children->items[0]; +				remove_child(child); +				add_child(root_container.children->items[p], child); +			} +			sort_workspaces(root_container.children->items[p]); +			update_visibility(root_container.children->items[p]); +			arrange_windows(root_container.children->items[p], -1, -1); +		} +	} +	sway_log(L_DEBUG, "OUTPUT: Destroying output '%" PRIuPTR "'", output->handle); +	free_swayc(output); +	update_root_geometry(); +	return &root_container; +} + +swayc_t *destroy_workspace(swayc_t *workspace) { +	if (!ASSERT_NONNULL(workspace)) { +		return NULL; +	} + +	// Do not destroy this if it's the last workspace on this output +	swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT); +	if (output && output->children->length == 1) { +		return NULL; +	} + +	swayc_t *parent = workspace->parent; +	// destroy the WS if there are no children +	if (workspace->children->length == 0 && workspace->floating->length == 0) { +		sway_log(L_DEBUG, "destroying workspace '%s'", workspace->name); +		ipc_event_workspace(workspace, NULL, "empty"); +	} else { +		// Move children to a different workspace on this output +		swayc_t *new_workspace = NULL; +		int i; +		for(i = 0; i < output->children->length; i++) { +			if(output->children->items[i] != workspace) { +				break; +			} +		} +		new_workspace = output->children->items[i]; + +		sway_log(L_DEBUG, "moving children to different workspace '%s' -> '%s'", +			workspace->name, new_workspace->name); + +		for(i = 0; i < workspace->children->length; i++) { +			move_container_to(workspace->children->items[i], new_workspace); +		} + +		for(i = 0; i < workspace->floating->length; i++) { +			move_container_to(workspace->floating->items[i], new_workspace); +		} +	} + +	free_swayc(workspace); +	return parent; +} + +swayc_t *destroy_container(swayc_t *container) { +	if (!ASSERT_NONNULL(container)) { +		return NULL; +	} +	while (container->children->length == 0 && container->type == C_CONTAINER) { +		sway_log(L_DEBUG, "Container: Destroying container '%p'", container); +		swayc_t *parent = container->parent; +		free_swayc(container); +		container = parent; +	} +	return container; +} + +swayc_t *destroy_view(swayc_t *view) { +	if (!ASSERT_NONNULL(view)) { +		return NULL; +	} +	sway_log(L_DEBUG, "Destroying view '%p'", view); +	swayc_t *parent = view->parent; +	free_swayc(view); + +	// Destroy empty containers +	if (parent && parent->type == C_CONTAINER) { +		return destroy_container(parent); +	} +	return parent; +} + +// Container lookup + + +swayc_t *swayc_by_test(swayc_t *container, bool (*test)(swayc_t *view, void *data), void *data) { +	if (!container->children) { +		return NULL; +	} +	// Special case for checking floating stuff +	int i; +	if (container->type == C_WORKSPACE) { +		for (i = 0; i < container->floating->length; ++i) { +			swayc_t *child = container->floating->items[i]; +			if (test(child, data)) { +				return child; +			} +		} +	} +	for (i = 0; i < container->children->length; ++i) { +		swayc_t *child = container->children->items[i]; +		if (test(child, data)) { +			return child; +		} else { +			swayc_t *res = swayc_by_test(child, test, data); +			if (res) { +				return res; +			} +		} +	} +	return NULL; +} + +static bool test_name(swayc_t *view, void *data) { +	if (!view || !view->name) { +		return false; +	} +	return strcmp(view->name, data) == 0; +} + +swayc_t *swayc_by_name(const char *name) { +	return swayc_by_test(&root_container, test_name, (void *)name); +} + +swayc_t *swayc_parent_by_type(swayc_t *container, enum swayc_types type) { +	if (!ASSERT_NONNULL(container)) { +		return NULL; +	} +	if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) { +		return NULL; +	} +	do { +		container = container->parent; +	} while (container && container->type != type); +	return container; +} + +swayc_t *swayc_parent_by_layout(swayc_t *container, enum swayc_layouts layout) { +	if (!ASSERT_NONNULL(container)) { +		return NULL; +	} +	if (!sway_assert(layout < L_LAYOUTS && layout >= L_NONE, "invalid layout")) { +		return NULL; +	} +	do { +		container = container->parent; +	} while (container && container->layout != layout); +	return container; +} + +swayc_t *swayc_focus_by_type(swayc_t *container, enum swayc_types type) { +	if (!ASSERT_NONNULL(container)) { +		return NULL; +	} +	if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) { +		return NULL; +	} +	do { +		container = container->focused; +	} while (container && container->type != type); +	return container; +} + +swayc_t *swayc_focus_by_layout(swayc_t *container, enum swayc_layouts layout) { +	if (!ASSERT_NONNULL(container)) { +		return NULL; +	} +	if (!sway_assert(layout < L_LAYOUTS && layout >= L_NONE, "invalid layout")) { +		return NULL; +	} +	do { +		container = container->focused; +	} while (container && container->layout != layout); +	return container; +} + + +static swayc_t *_swayc_by_handle_helper(wlc_handle handle, swayc_t *parent) { +	if (!parent || !parent->children) { +		return NULL; +	} +	int i, len; +	swayc_t **child; +	if (parent->type == C_WORKSPACE) { +		len = parent->floating->length; +		child = (swayc_t **)parent->floating->items; +		for (i = 0; i < len; ++i, ++child) { +			if ((*child)->handle == handle) { +				return *child; +			} +		} +	} + +	len = parent->children->length; +	child = (swayc_t**)parent->children->items; +	for (i = 0; i < len; ++i, ++child) { +		if ((*child)->handle == handle) { +			return *child; +		} else { +			swayc_t *res; +			if ((res = _swayc_by_handle_helper(handle, *child))) { +				return res; +			} +		} +	} +	return NULL; +} + +swayc_t *swayc_by_handle(wlc_handle handle) { +	return _swayc_by_handle_helper(handle, &root_container); +} + +swayc_t *swayc_active_output(void) { +	return root_container.focused; +} + +swayc_t *swayc_active_workspace(void) { +	return root_container.focused ? root_container.focused->focused : NULL; +} + +swayc_t *swayc_active_workspace_for(swayc_t *cont) { +	if (!cont) { +		return NULL; +	} +	switch (cont->type) { +	case C_ROOT: +		cont = cont->focused; +		/* Fallthrough */ + +	case C_OUTPUT: +		cont = cont ? cont->focused : NULL; +		/* Fallthrough */ + +	case C_WORKSPACE: +		return cont; + +	default: +		return swayc_parent_by_type(cont, C_WORKSPACE); +	} +} + +static bool pointer_test(swayc_t *view, void *_origin) { +	const struct wlc_point *origin = _origin; +	// Determine the output that the view is under +	swayc_t *parent = swayc_parent_by_type(view, C_OUTPUT); +	if (origin->x >= view->x && origin->y >= view->y +		&& origin->x < view->x + view->width && origin->y < view->y + view->height +		&& view->visible && parent == root_container.focused) { +		return true; +	} +	return false; +} + +swayc_t *container_under_pointer(void) { +	// root.output->workspace +	if (!root_container.focused) { +		return NULL; +	} +	swayc_t *lookup = root_container.focused; +	// Case of empty workspace +	if (lookup->children && !lookup->unmanaged) { +		return NULL; +	} +	double x, y; +	wlc_pointer_get_position_v2(&x, &y); +	struct wlc_point origin = { .x = x, .y = y }; + +	while (lookup && lookup->type != C_VIEW) { +		int i; +		int len; +		for (int _i = 0; lookup->unmanaged && _i < lookup->unmanaged->length; ++_i) { +			wlc_handle *handle = lookup->unmanaged->items[_i]; +			const struct wlc_geometry *geo = wlc_view_get_geometry(*handle); +			if (origin.x >= geo->origin.x && origin.y >= geo->origin.y +					&& origin.x < geo->origin.x + (int)geo->size.w +					&& origin.y < geo->origin.y + (int)geo->size.h) { +				// Hack: we force focus upon unmanaged views here +				wlc_view_focus(*handle); +				return NULL; +			} +		} +		// if tabbed/stacked go directly to focused container, otherwise search +		// children +		if (lookup->layout == L_TABBED || lookup->layout == L_STACKED) { +			lookup = lookup->focused; +			continue; +		} +		// if workspace, search floating +		if (lookup->type == C_WORKSPACE) { +			i = len = lookup->floating->length; +			bool got_floating = false; +			while (--i > -1) { +				if (pointer_test(lookup->floating->items[i], &origin)) { +					lookup = lookup->floating->items[i]; +					got_floating = true; +					break; +				} +			} +			if (got_floating) { +				continue; +			} +		} +		// search children +		len = lookup->children->length; +		for (i = 0; i < len; ++i) { +			if (pointer_test(lookup->children->items[i], &origin)) { +				lookup = lookup->children->items[i]; +				break; +			} +		} +		// when border and titles are done, this could happen +		if (i == len) { +			break; +		} +	} +	return lookup; +} + +swayc_t *container_find(swayc_t *container, bool (*f)(swayc_t *, const void *), const void *data) { +	if (container->children == NULL || container->children->length == 0) { +		return NULL; +	} + +	swayc_t *con; +	if (container->type == C_WORKSPACE) { +		for (int i = 0; i < container->floating->length; ++i) { +			con = container->floating->items[i]; +			if (f(con, data)) { +				return con; +			} +			con = container_find(con, f, data); +			if (con != NULL) { +				return con; +			} +		} +	} + +	for (int i = 0; i < container->children->length; ++i) { +		con = container->children->items[i]; +		if (f(con, data)) { +			return con; +		} + +		con = container_find(con, f, data); +		if (con != NULL) { +			return con; +		} +	} + +	return NULL; +} + +// Container information + +bool swayc_is_fullscreen(swayc_t *view) { +	return view && view->type == C_VIEW && (wlc_view_get_state(view->handle) & WLC_BIT_FULLSCREEN); +} + +bool swayc_is_active(swayc_t *view) { +	return view && view->type == C_VIEW && (wlc_view_get_state(view->handle) & WLC_BIT_ACTIVATED); +} + +bool swayc_is_parent_of(swayc_t *parent, swayc_t *child) { +	while (child != &root_container) { +		child = child->parent; +		if (child == parent) { +			return true; +		} +	} +	return false; +} + +bool swayc_is_child_of(swayc_t *child, swayc_t *parent) { +	return swayc_is_parent_of(parent, child); +} + +bool swayc_is_empty_workspace(swayc_t *container) { +	return container->type == C_WORKSPACE && container->children->length == 0; +} + +int swayc_gap(swayc_t *container) { +	if (container->type == C_VIEW || container->type == C_CONTAINER) { +		return container->gaps >= 0 ? container->gaps : config->gaps_inner; +	} else if (container->type == C_WORKSPACE) { +		int base = container->gaps >= 0 ? container->gaps : config->gaps_outer; +		if (config->edge_gaps && !(config->smart_gaps && container->children->length == 1)) { +			// the inner gap is created via a margin around each window which +			// is half the gap size, so the workspace also needs half a gap +			// size to make the outermost gap the same size (excluding the +			// actual "outer gap" size which is handled independently) +			return base + config->gaps_inner / 2; +		} else if (config->smart_gaps && container->children->length == 1) { +			return 0; +		} else { +			return base; +		} +	} else { +		return 0; +	} +} + +// Mapping + +void container_map(swayc_t *container, void (*f)(swayc_t *view, void *data), void *data) { +	if (container) { +		int i; +		if (container->children)  { +			for (i = 0; i < container->children->length; ++i) { +				swayc_t *child = container->children->items[i]; +				container_map(child, f, data); +			} +		} +		if (container->floating) { +			for (i = 0; i < container->floating->length; ++i) { +				swayc_t *child = container->floating->items[i]; +				container_map(child, f, data); +			} +		} +		f(container, data); +	} +} + +void update_visibility_output(swayc_t *container, wlc_handle output) { +	// Inherit visibility +	swayc_t *parent = container->parent; +	container->visible = parent->visible; +	// special cases where visibility depends on focus +	if (parent->type == C_OUTPUT || parent->layout == L_TABBED || +			parent->layout == L_STACKED) { +		container->visible = parent->focused == container && parent->visible; +	} +	// Set visibility and output for view +	if (container->type == C_VIEW) { +		wlc_view_set_output(container->handle, output); +		wlc_view_set_mask(container->handle, container->visible ? VISIBLE : 0); +	} +	// Update visibility for children +	else { +		if (container->children) { +			int i, len = container->children->length; +			for (i = 0; i < len; ++i) { +				update_visibility_output(container->children->items[i], output); +			} +		} +		if (container->floating) { +			int i, len = container->floating->length; +			for (i = 0; i < len; ++i) { +				update_visibility_output(container->floating->items[i], output); +			} +		} +	} +} + +void update_visibility(swayc_t *container) { +	if (!container) return; +	switch (container->type) { +	case C_ROOT: +		container->visible = true; +		if (container->children) { +			int i, len = container->children->length; +			for (i = 0; i < len; ++i) { +				update_visibility(container->children->items[i]); +			} +		} +		return; + +	case C_OUTPUT: +		container->visible = true; +		if (container->children) { +			int i, len = container->children->length; +			for (i = 0; i < len; ++i) { +				update_visibility_output(container->children->items[i], container->handle); +			} +		} +		return; + +	default: +		{ +			swayc_t *op = swayc_parent_by_type(container, C_OUTPUT); +			update_visibility_output(container, op->handle); +		} +	} +} + +void set_gaps(swayc_t *view, void *_data) { +	int *data = _data; +	if (!ASSERT_NONNULL(view)) { +		return; +	} +	if (view->type == C_WORKSPACE || view->type == C_VIEW) { +		view->gaps = *data; +	} +} + +void add_gaps(swayc_t *view, void *_data) { +	int *data = _data; +	if (!ASSERT_NONNULL(view)) { +		return; +	} +	if (view->type == C_WORKSPACE || view->type == C_VIEW) { +		if ((view->gaps += *data) < 0) { +			view->gaps = 0; +		} +	} +} + +static void close_view(swayc_t *container, void *data) { +	if (container->type == C_VIEW) { +		wlc_view_close(container->handle); +	} +} + +void close_views(swayc_t *container) { +	container_map(container, close_view, NULL); +} + +swayc_t *swayc_tabbed_stacked_ancestor(swayc_t *view) { +	swayc_t *parent = NULL; +	if (!ASSERT_NONNULL(view)) { +		return NULL; +	} +	while (view->type != C_WORKSPACE && view->parent && view->parent->type != C_WORKSPACE) { +		view = view->parent; +		if (view->layout == L_TABBED || view->layout == L_STACKED) { +			parent = view; +		} +	} + +	return parent; +} + +swayc_t *swayc_tabbed_stacked_parent(swayc_t *con) { +	if (!ASSERT_NONNULL(con)) { +		return NULL; +	} +	if (con->parent && (con->parent->layout == L_TABBED || con->parent->layout == L_STACKED)) { +		return con->parent; +	} +	return NULL; +} + +swayc_t *swayc_change_layout(swayc_t *container, enum swayc_layouts layout) { +	// if layout change modifies the auto layout's major axis, swap width and height +	// to preserve current ratios. +	if (is_auto_layout(layout) && is_auto_layout(container->layout)) { +		enum swayc_layouts prev_major = +			container->layout == L_AUTO_LEFT || container->layout == L_AUTO_RIGHT +			? L_HORIZ : L_VERT; +		enum swayc_layouts new_major = +			layout == L_AUTO_LEFT || layout == L_AUTO_RIGHT +			? L_HORIZ : L_VERT; +		if (new_major != prev_major) { +			for (int i = 0; i < container->children->length; ++i) { +				swayc_t *child = container->children->items[i]; +				double h = child->height; +				child->height = child->width; +				child->width = h; +			} +		} +	} +	if (container->type == C_WORKSPACE) { +		container->workspace_layout = layout; +		if (layout == L_HORIZ || layout == L_VERT || is_auto_layout(layout)) { +			container->layout = layout; +		} +	} else { +		container->layout = layout; +	} +	return container; +} diff --git a/sway/tree/criteria.c b/sway/tree/criteria.c new file mode 100644 index 00000000..e8978ebe --- /dev/null +++ b/sway/tree/criteria.c @@ -0,0 +1,451 @@ +#define _XOPEN_SOURCE 700 +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <pcre.h> +#include "sway/criteria.h" +#include "sway/container.h" +#include "sway/config.h" +#include "stringop.h" +#include "list.h" +#include "log.h" + +enum criteria_type { // *must* keep in sync with criteria_strings[] +	CRIT_CLASS, +	CRIT_CON_ID, +	CRIT_CON_MARK, +	CRIT_FLOATING, +	CRIT_ID, +	CRIT_INSTANCE, +	CRIT_TILING, +	CRIT_TITLE, +	CRIT_URGENT, +	CRIT_WINDOW_ROLE, +	CRIT_WINDOW_TYPE, +	CRIT_WORKSPACE, +	CRIT_LAST +}; + +static const char * const criteria_strings[CRIT_LAST] = { +	[CRIT_CLASS] = "class", +	[CRIT_CON_ID] = "con_id", +	[CRIT_CON_MARK] = "con_mark", +	[CRIT_FLOATING] = "floating", +	[CRIT_ID] = "id", +	[CRIT_INSTANCE] = "instance", +	[CRIT_TILING] = "tiling", +	[CRIT_TITLE] = "title", +	[CRIT_URGENT] = "urgent", // either "latest" or "oldest" ... +	[CRIT_WINDOW_ROLE] = "window_role", +	[CRIT_WINDOW_TYPE] = "window_type", +	[CRIT_WORKSPACE] = "workspace" +}; + +/** + * A single criteria token (ie. value/regex pair), + * e.g. 'class="some class regex"'. + */ +struct crit_token { +	enum criteria_type type; +	pcre *regex; +	char *raw; +}; + +static void free_crit_token(struct crit_token *crit) { +	pcre_free(crit->regex); +	free(crit->raw); +	free(crit); +} + +static void free_crit_tokens(list_t *crit_tokens) { +	for (int i = 0; i < crit_tokens->length; i++) { +		free_crit_token(crit_tokens->items[i]); +	} +	list_free(crit_tokens); +} + +// Extracts criteria string from its brackets. Returns new (duplicate) +// substring. +static char *criteria_from(const char *arg) { +	char *criteria = NULL; +	if (*arg == '[') { +		criteria = strdup(arg + 1); +	} else { +		criteria = strdup(arg); +	} + +	int last = strlen(criteria) - 1; +	if (criteria[last] == ']') { +		criteria[last] = '\0'; +	} +	return criteria; +} + +// Return instances of c found in str. +static int countchr(char *str, char c) { +	int found = 0; +	for (int i = 0; str[i]; i++) { +		if (str[i] == c) { +			++found; +		} +	} +	return found; +} + +// criteria_str is e.g. '[class="some class regex" instance="instance name"]'. +// +// Will create array of pointers in buf, where first is duplicate of given +// string (must be freed) and the rest are pointers to names and values in the +// base string (every other, naturally). argc will be populated with the length +// of buf. +// +// Returns error string or NULL if successful. +static char *crit_tokens(int *argc, char ***buf, const char * const criteria_str) { +	sway_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str); +	char *base = criteria_from(criteria_str); +	char *head = base; +	char *namep = head; // start of criteria name +	char *valp = NULL; // start of value + +	// We're going to place EOS markers where we need to and fill up an array +	// of pointers to the start of each token (either name or value). +	int pairs = countchr(base, '='); +	int max_tokens = pairs * 2 + 1; // this gives us at least enough slots + +	char **argv = *buf = calloc(max_tokens, sizeof(char*)); +	argv[0] = base; // this needs to be freed by caller +	bool quoted = true; + +	*argc = 1; // uneven = name, even = value +	while (*head && *argc < max_tokens) { +		if (namep != head && *(head - 1) == '\\') { +			// escaped character: don't try to parse this +		} else if (*head == '=' && namep != head) { +			if (*argc % 2 != 1) { +				// we're not expecting a name +				return strdup("Unable to parse criteria: " +					"Found out of place equal sign"); +			} else { +				// name ends here +				char *end = head; // don't want to rewind the head +				while (*(end - 1) == ' ') { +					--end; +				} +				*end = '\0'; +				if (*(namep) == ' ') { +					namep = strrchr(namep, ' ') + 1; +				} +				argv[*argc] = namep; +				*argc += 1; +			} +		} else if (*head == '"') { +			if (*argc % 2 != 0) { +				// we're not expecting a value +				return strdup("Unable to parse criteria: " +					"Found quoted value where it was not expected"); +			} else if (!valp) { // value starts here +				valp = head + 1; +				quoted = true; +			} else { +				// value ends here +				argv[*argc] = valp; +				*argc += 1; +				*head = '\0'; +				valp = NULL; +				namep = head + 1; +			} +		} else if (*argc % 2 == 0 && *head != ' ') { +			// parse unquoted values +			if (!valp) { +				quoted = false; +				valp = head;  // value starts here +			} +		} else if (valp && !quoted && *head == ' ') { +			// value ends here +			argv[*argc] = valp; +			*argc += 1; +			*head = '\0'; +			valp = NULL; +			namep = head + 1; +		} +		head++; +	} + +	// catch last unquoted value if needed +	if (valp && !quoted && !*head) { +		argv[*argc] = valp; +		*argc += 1; +	} + +	return NULL; +} + +// Returns error string on failure or NULL otherwise. +static char *parse_criteria_name(enum criteria_type *type, char *name) { +	*type = CRIT_LAST; +	for (int i = 0; i < CRIT_LAST; i++) { +		if (strcmp(criteria_strings[i], name) == 0) { +			*type = (enum criteria_type) i; +			break; +		} +	} +	if (*type == CRIT_LAST) { +		const char *fmt = "Criteria type '%s' is invalid or unsupported."; +		int len = strlen(name) + strlen(fmt) - 1; +		char *error = malloc(len); +		snprintf(error, len, fmt, name); +		return error; +	} else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE || +			*type == CRIT_WINDOW_TYPE) { +		// (we're just being helpful here) +		const char *fmt = "\"%s\" criteria currently unsupported, " +			"no window will match this"; +		int len = strlen(fmt) + strlen(name) - 1; +		char *error = malloc(len); +		snprintf(error, len, fmt, name); +		return error; +	} +	return NULL; +} + +// Returns error string on failure or NULL otherwise. +static char *generate_regex(pcre **regex, char *value) { +	const char *reg_err; +	int offset; + +	*regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); + +	if (!*regex) { +		const char *fmt = "Regex compilation (for '%s') failed: %s"; +		int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; +		char *error = malloc(len); +		snprintf(error, len, fmt, value, reg_err); +		return error; +	} +	return NULL; +} + +// Test whether the criterion corresponds to the currently focused window +static bool crit_is_focused(const char *value) { +	return !strcmp(value, "focused") || !strcmp(value, "__focused__"); +} + +// Populate list with crit_tokens extracted from criteria string, returns error +// string or NULL if successful. +char *extract_crit_tokens(list_t *tokens, const char * const criteria) { +	int argc; +	char **argv = NULL, *error = NULL; +	if ((error = crit_tokens(&argc, &argv, criteria))) { +		goto ect_cleanup; +	} +	for (int i = 1; i + 1 < argc; i += 2) { +		char* name = argv[i], *value = argv[i + 1]; +		struct crit_token *token = calloc(1, sizeof(struct crit_token)); +		token->raw = strdup(value); + +		if ((error = parse_criteria_name(&token->type, name))) { +			free_crit_token(token); +			goto ect_cleanup; +		} else if (token->type == CRIT_URGENT || crit_is_focused(value)) { +			sway_log(L_DEBUG, "%s -> \"%s\"", name, value); +			list_add(tokens, token); +		} else if((error = generate_regex(&token->regex, value))) { +			free_crit_token(token); +			goto ect_cleanup; +		} else { +			sway_log(L_DEBUG, "%s -> /%s/", name, value); +			list_add(tokens, token); +		} +	} +ect_cleanup: +	free(argv[0]); // base string +	free(argv); +	return error; +} + +static int regex_cmp(const char *item, const pcre *regex) { +	return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); +} + +// test a single view if it matches list of criteria tokens (all of them). +static bool criteria_test(swayc_t *cont, list_t *tokens) { +	if (cont->type != C_VIEW) { +		return false; +	} +	int matches = 0; +	for (int i = 0; i < tokens->length; i++) { +		struct crit_token *crit = tokens->items[i]; +		switch (crit->type) { +		case CRIT_CLASS: +			if (!cont->class) { +				// ignore +			} else if (crit_is_focused(crit->raw)) { +				swayc_t *focused = get_focused_view(&root_container); +				if (focused->class && strcmp(cont->class, focused->class) == 0) { +					matches++; +				} +			} else if (crit->regex && regex_cmp(cont->class, crit->regex) == 0) { +				matches++; +			} +			break; +		case CRIT_CON_ID: { +			char *endptr; +			size_t crit_id = strtoul(crit->raw, &endptr, 10); + +			if (*endptr == 0 && cont->id == crit_id) { +				++matches; +			} +			break; +		} +		case CRIT_CON_MARK: +			if (crit->regex && cont->marks && (list_seq_find(cont->marks, (int (*)(const void *, const void *))regex_cmp, crit->regex) != -1)) { +				// Make sure it isn't matching the NUL string +				if ((strcmp(crit->raw, "") == 0) == (list_seq_find(cont->marks, (int (*)(const void *, const void *))strcmp, "") != -1)) { +					++matches; +				} +			} +			break; +		case CRIT_FLOATING: +			if (cont->is_floating) { +				matches++; +			} +			break; +		case CRIT_ID: +			if (!cont->app_id) { +				// ignore +			} else if (crit->regex && regex_cmp(cont->app_id, crit->regex) == 0) { +				matches++; +			} +			break; +		case CRIT_INSTANCE: +			if (!cont->instance) { +				// ignore +			} else if (crit_is_focused(crit->raw)) { +				swayc_t *focused = get_focused_view(&root_container); +				if (focused->instance && strcmp(cont->instance, focused->instance) == 0) { +					matches++; +				} +			} else if (crit->regex && regex_cmp(cont->instance, crit->regex) == 0) { +				matches++; +			} +			break; +		case CRIT_TILING: +			if (!cont->is_floating) { +				matches++; +			} +			break; +		case CRIT_TITLE: +			if (!cont->name) { +				// ignore +			} else if (crit_is_focused(crit->raw)) { +				swayc_t *focused = get_focused_view(&root_container); +				if (focused->name && strcmp(cont->name, focused->name) == 0) { +					matches++; +				} +			} else if (crit->regex && regex_cmp(cont->name, crit->regex) == 0) { +				matches++; +			} +			break; +		case CRIT_URGENT: // "latest" or "oldest" +			break; +		case CRIT_WINDOW_ROLE: +			break; +		case CRIT_WINDOW_TYPE: +			// TODO wlc indeed exposes this information +			break; +		case CRIT_WORKSPACE: ; +			swayc_t *cont_ws = swayc_parent_by_type(cont, C_WORKSPACE); +			if (!cont_ws || !cont_ws->name) { +				// ignore +			} else if (crit_is_focused(crit->raw)) { +				swayc_t *focused_ws = swayc_active_workspace(); +				if (focused_ws->name && strcmp(cont_ws->name, focused_ws->name) == 0) { +					matches++; +				} +			} else if (crit->regex && regex_cmp(cont_ws->name, crit->regex) == 0) { +				matches++; +			} +			break; +		default: +			sway_abort("Invalid criteria type (%i)", crit->type); +			break; +		} +	} +	return matches == tokens->length; +} + +int criteria_cmp(const void *a, const void *b) { +	if (a == b) { +		return 0; +	} else if (!a) { +		return -1; +	} else if (!b) { +		return 1; +	} +	const struct criteria *crit_a = a, *crit_b = b; +	int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist); +	if (cmp != 0) { +		return cmp; +	} +	return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw); +} + +void free_criteria(struct criteria *crit) { +	if (crit->tokens) { +		free_crit_tokens(crit->tokens); +	} +	if (crit->cmdlist) { +		free(crit->cmdlist); +	} +	if (crit->crit_raw) { +		free(crit->crit_raw); +	} +	free(crit); +} + +bool criteria_any(swayc_t *cont, list_t *criteria) { +	for (int i = 0; i < criteria->length; i++) { +		struct criteria *bc = criteria->items[i]; +		if (criteria_test(cont, bc->tokens)) { +			return true; +		} +	} +	return false; +} + +list_t *criteria_for(swayc_t *cont) { +	list_t *criteria = config->criteria, *matches = create_list(); +	for (int i = 0; i < criteria->length; i++) { +		struct criteria *bc = criteria->items[i]; +		if (criteria_test(cont, bc->tokens)) { +			list_add(matches, bc); +		} +	} +	return matches; +} + +struct list_tokens { +	list_t *list; +	list_t *tokens; +}; + +static void container_match_add(swayc_t *container, struct list_tokens *list_tokens) { +	if (criteria_test(container, list_tokens->tokens)) { +		list_add(list_tokens->list, container); +	} +} + +list_t *container_for(list_t *tokens) { +	struct list_tokens list_tokens = (struct list_tokens){create_list(), tokens}; + +	container_map(&root_container, (void (*)(swayc_t *, void *))container_match_add, &list_tokens); +	 +	for (int i = 0; i < scratchpad->length; ++i) { +		swayc_t *c = scratchpad->items[i]; +		if (criteria_test(c, tokens)) { +			list_add(list_tokens.list, c); +		} +	} + +	return list_tokens.list; +} diff --git a/sway/tree/focus.c b/sway/tree/focus.c new file mode 100644 index 00000000..66f7ee17 --- /dev/null +++ b/sway/tree/focus.c @@ -0,0 +1,278 @@ +#include "stdbool.h" +#include <wlc/wlc.h> +#include "sway/focus.h" +#include "sway/workspace.h" +#include "sway/layout.h" +#include "sway/config.h" +#include "sway/input_state.h" +#include "sway/ipc-server.h" +#include "sway/border.h" +#include "log.h" + +bool locked_container_focus = false; +bool suspend_workspace_cleanup = false; + +// switches parent focus to c. will switch it accordingly +static void update_focus(swayc_t *c) { +	// Handle if focus switches +	swayc_t *parent = c->parent; +	if (!parent) return; +	if (parent->focused != c) { +		// Get previous focus +		swayc_t *prev = parent->focused; +		// Set new focus +		parent->focused = c; + +		switch (c->type) { +		// Shouldn't happen +		case C_ROOT: return; + +		// Case where output changes +		case C_OUTPUT: +			wlc_output_focus(c->handle); +			break; + +		// Case where workspace changes +		case C_WORKSPACE: +			if (prev) { +				ipc_event_workspace(prev, c, "focus"); + +				// if the old workspace has no children, destroy it +				if(prev->children->length == 0 && prev->floating->length == 0 && !suspend_workspace_cleanup) { +					destroy_workspace(prev); +				} else { +					// update visibility of old workspace +					update_visibility(prev); +				} +			} +			// Update visibility of newly focused workspace +			update_visibility(c); +			break; + +		default: +		case C_VIEW: +		case C_CONTAINER: +			break; +		} +	} +} + +bool move_focus(enum movement_direction direction) { +	swayc_t *old_view = get_focused_container(&root_container); +	swayc_t *new_view = get_swayc_in_direction(old_view, direction); +	if (!new_view) { +		return false; +	} else if (new_view->type == C_ROOT) { +		sway_log(L_DEBUG, "Not setting focus above the workspace level"); +		return false; +	} else if (new_view->type == C_OUTPUT) { +		return set_focused_container(swayc_active_workspace_for(new_view)); +	} else if (direction == MOVE_PARENT || direction == MOVE_CHILD) { +		return set_focused_container(new_view); +	} else if (config->mouse_warping) { +		swayc_t *old_op = old_view->type == C_OUTPUT ? +			old_view : swayc_parent_by_type(old_view, C_OUTPUT); +		swayc_t *focused = get_focused_view(new_view); +		if (set_focused_container(focused)) { +			if (old_op != swayc_active_output() && focused && focused->type == C_VIEW) { +				center_pointer_on(focused); +			} +			return true; +		} +	} else { +		return set_focused_container(get_focused_view(new_view)); +	} +	return false; +} + +swayc_t *get_focused_container(swayc_t *parent) { +	if (!parent) { +		return swayc_active_workspace(); +	} +	while (!parent->is_focused && parent->focused) { +		parent = parent->focused; +	} +	return parent; +} + +bool set_focused_container(swayc_t *c) { +	if (locked_container_focus || !c || !c->parent) { +		return false; +	} + +	// current ("old") workspace for sending workspace change event later +	swayc_t *old_ws = swayc_active_workspace(); +	// keep track of child count so we can determine if it gets destroyed +	int old_ws_child_count = 0; +	if (old_ws) { +		old_ws_child_count = old_ws->children->length + old_ws->floating->length; +	} + +	// current ("old") focused container +	swayc_t *old_focus = get_focused_container(&root_container); +	// if old_focus is a workspace, then it's the same workspace as +	// old_ws, and we'll need to null its pointer too, since it will +	// be destroyed in the update_focus() call +	bool old_focus_was_ws = (old_focus->type == C_WORKSPACE); + +	// workspace of new focused container +	swayc_t *workspace = swayc_active_workspace_for(c); + +	if (swayc_is_fullscreen(get_focused_container(workspace))) { +		// if switching to a workspace with a fullscreen view, +		// focus on the fullscreen view +		c = get_focused_container(workspace); +	} + +	swayc_log(L_DEBUG, c, "Setting focus to %p:%" PRIuPTR, c, c->handle); + +	if (c->type == C_VIEW) { +		// dispatch a window event +		ipc_event_window(c, "focus"); +	} + +	// update the global pointer +	current_focus = c; + +	// update container focus from here to root, making necessary changes along +	// the way +	swayc_t *p = c; +	if (p->type != C_OUTPUT && p->type != C_ROOT) { +		p->is_focused = true; +	} +	while (p != &root_container) { +		update_focus(p); +		p = p->parent; +		p->is_focused = false; +	} + +	if (old_focus_was_ws && old_ws_child_count == 0) { +		// this workspace was destroyed in update_focus(), so null the pointers +		old_focus = NULL; +		old_ws = NULL; +	} + +	if (!(wlc_view_get_type(p->handle) & WLC_BIT_POPUP)) { +		if (old_focus) { +			if (old_focus->type == C_VIEW) { +				wlc_view_set_state(old_focus->handle, WLC_BIT_ACTIVATED, false); +			} +			update_container_border(old_focus); +		} +		if (c->type == C_VIEW) { +			wlc_view_set_state(c->handle, WLC_BIT_ACTIVATED, true); +		} +		/* TODO WLR +		if (!desktop_shell.is_locked) { +			// If the system is locked, we do everything _but_ actually setting +			// focus. This includes making our internals think that this view is +			// focused. +			wlc_view_focus(c->handle); +		} +		*/ +		if (c->parent->layout != L_TABBED && c->parent->layout != L_STACKED) { +			update_container_border(c); +		} + +		swayc_t *parent = swayc_tabbed_stacked_ancestor(c); +		if (parent != NULL) { +			arrange_backgrounds(); +			arrange_windows(parent, -1, -1); +		} +	} + +	if (old_ws != workspace) { +		// old_ws might be NULL here but that's ok +		ipc_event_workspace(old_ws, workspace, "focus"); +	} + +	return true; +} + +bool set_focused_container_for(swayc_t *a, swayc_t *c) { +	if (locked_container_focus || !c) { +		return false; +	} +	swayc_t *find = c; +	while (find != a && (find = find->parent)) { +		if (find == &root_container) { +			return false; +		} +	} + +	// Get workspace for c, get that workspaces current focused container. +	swayc_t *workspace = swayc_active_workspace_for(c); +	swayc_t *focused = get_focused_view(workspace); +	// if the workspace we are changing focus to has a fullscreen view return +	if (swayc_is_fullscreen(focused) && c != focused) { +		return false; +	} + +	// Check if we are changing a parent container that will see change +	bool effective = true; +	while (find != &root_container) { +		if (find->parent->focused != find) { +			effective = false; +		} +		find = find->parent; +	} +	if (effective) { +		// Go to set_focused_container +		return set_focused_container(c); +	} + +	sway_log(L_DEBUG, "Setting focus for %p:%" PRIuPTR " to %p:%" PRIuPTR, +		a, a->handle, c, c->handle); + +	c->is_focused = true; +	swayc_t *p = c; +	while (p != a) { +		update_focus(p); +		p = p->parent; +		p->is_focused = false; +	} +	return true; +} + +swayc_t *get_focused_view(swayc_t *parent) { +	swayc_t *c = parent; +	while (c && c->type != C_VIEW) { +		if (c->type == C_WORKSPACE && c->focused == NULL) { +			return c; +		} +		c = c->focused; +	} +	if (c == NULL) { +		c = swayc_active_workspace_for(parent); +	} +	return c; +} + +swayc_t *get_focused_float(swayc_t *ws) { +	if(!sway_assert(ws->type == C_WORKSPACE, "must be of workspace type")) { +		ws = swayc_active_workspace(); +	} +	if (ws->floating->length) { +		return ws->floating->items[ws->floating->length - 1]; +	} +	return NULL; +} + +swayc_t *get_focused_view_include_floating(swayc_t *parent) { +	swayc_t *c = parent; +	swayc_t *f = NULL; + +	while (c && c->type != C_VIEW) { +		if (c->type == C_WORKSPACE && c->focused == NULL) { +			return ((f = get_focused_float(c))) ? f : c; +		} + +		c = c->focused; +	} + +	if (c == NULL) { +		c = swayc_active_workspace_for(parent); +	} + +	return c; +} diff --git a/sway/tree/layout.c b/sway/tree/layout.c new file mode 100644 index 00000000..22f81688 --- /dev/null +++ b/sway/tree/layout.c @@ -0,0 +1,1773 @@ +#define _XOPEN_SOURCE 500 +#include <stdlib.h> +#include <stdbool.h> +#include <math.h> +#include <wlc/wlc.h> +#include "sway/config.h" +#include "sway/container.h" +#include "sway/workspace.h" +#include "sway/focus.h" +#include "sway/output.h" +#include "sway/ipc-server.h" +#include "sway/border.h" +#include "sway/layout.h" +#include "list.h" +#include "log.h" + +swayc_t root_container; +swayc_t *current_focus; +list_t *scratchpad; + +int min_sane_h = 60; +int min_sane_w = 100; + +void init_layout(void) { +	root_container.id = 0; // normally assigned in new_swayc() +	root_container.type = C_ROOT; +	root_container.layout = L_NONE; +	root_container.name = strdup("root"); +	root_container.children = create_list(); +	root_container.handle = -1; +	root_container.visible = true; +	current_focus = &root_container; +	scratchpad = create_list(); +} + +int index_child(const swayc_t *child) { +	swayc_t *parent = child->parent; +	int i, len; +	if (!child->is_floating) { +		len = parent->children->length; +		for (i = 0; i < len; ++i) { +			if (parent->children->items[i] == child) { +				break; +			} +		} +	} else { +		len = parent->floating->length; +		for (i = 0; i < len; ++i) { +			if (parent->floating->items[i] == child) { +				break; +			} +		} +	} +	if (!sway_assert(i < len, "Stray container")) { +		return -1; +	} +	return i; +} + +void add_child(swayc_t *parent, swayc_t *child) { +	sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type, +		child->width, child->height, parent, parent->type, parent->width, parent->height); +	list_add(parent->children, child); +	child->parent = parent; +	// set focus for this container +	if (!parent->focused) { +		parent->focused = child; +	} +	if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) { +		child = new_container(child, parent->workspace_layout); +	} +} + +static double *get_height(swayc_t *cont) { +	return &cont->height; +} + +static double *get_width(swayc_t *cont) { +	return &cont->width; +} + +void insert_child(swayc_t *parent, swayc_t *child, int index) { +	if (index > parent->children->length) { +		index = parent->children->length; +	} +	if (index < 0) { +		index = 0; +	} +	list_insert(parent->children, index, child); +	child->parent = parent; +	if (!parent->focused) { +		parent->focused = child; +	} +	if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) { +		child = new_container(child, parent->workspace_layout); +	} +	if (is_auto_layout(parent->layout)) { +		/* go through each group, adjust the size of the first child of each group */ +		double *(*get_maj_dim)(swayc_t *cont); +		double *(*get_min_dim)(swayc_t *cont); +		if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) { +			get_maj_dim = get_width; +			get_min_dim = get_height; +		} else { +			get_maj_dim = get_height; +			get_min_dim = get_width; +		} +		for (int i = index; i < parent->children->length;) { +			int start = auto_group_start_index(parent, i); +			int end = auto_group_end_index(parent, i); +			swayc_t *first = parent->children->items[start]; +			if (start + 1 < parent->children->length) { +				/* preserve the group's dimension along major axis */ +				*get_maj_dim(first) = *get_maj_dim(parent->children->items[start + 1]); +			} else { +				/* new group, let the apply_layout handle it */ +				first->height = first->width = 0; +				break; +			} +			double remaining = *get_min_dim(parent); +			for (int j = end - 1; j > start; --j) { +				swayc_t *sibling = parent->children->items[j]; +				if (sibling == child) { +					/* the inserted child won't yet have its minor +					   dimension set */ +					remaining -= *get_min_dim(parent) / (end - start); +				} else { +					remaining -= *get_min_dim(sibling); +				} +			} +			*get_min_dim(first) = remaining; +			i = end; +		} +	} +} + +void add_floating(swayc_t *ws, swayc_t *child) { +	sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type, +		child->width, child->height, ws, ws->type, ws->width, ws->height); +	if (!sway_assert(ws->type == C_WORKSPACE, "Must be of workspace type")) { +		return; +	} +	list_add(ws->floating, child); +	child->parent = ws; +	child->is_floating = true; +	if (!ws->focused) { +		ws->focused = child; +	} +	ipc_event_window(child, "floating"); +} + +swayc_t *add_sibling(swayc_t *fixed, swayc_t *active) { +	swayc_t *parent = fixed->parent; +	if (fixed->is_floating) { +		if (active->is_floating) { +			int i = index_child(fixed); +			list_insert(parent->floating, i + 1, active); +		} else { +			list_add(parent->children, active); +		} +	} else { +		if (active->is_floating) { +			list_add(parent->floating, active); +		} else { +			int i = index_child(fixed); +			if (is_auto_layout(parent->layout)) { +				list_add(parent->children, active); +			} else { +				list_insert(parent->children, i + 1, active); +			} +		} +	} +	active->parent = parent; +	// focus new child +	parent->focused = active; +	return active->parent; +} + +swayc_t *replace_child(swayc_t *child, swayc_t *new_child) { +	swayc_t *parent = child->parent; +	if (parent == NULL) { +		return NULL; +	} +	int i = index_child(child); +	if (child->is_floating) { +		parent->floating->items[i] = new_child; +	} else { +		parent->children->items[i] = new_child; +	} +	// Set parent and focus for new_child +	new_child->parent = child->parent; +	if (child->parent->focused == child) { +		child->parent->focused = new_child; +	} +	child->parent = NULL; + +	// Set geometry for new child +	new_child->x = child->x; +	new_child->y = child->y; +	new_child->width = child->width; +	new_child->height = child->height; + +	// reset geometry for child +	child->width = 0; +	child->height = 0; + +	// deactivate child +	if (child->type == C_VIEW) { +		wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false); +	} +	return parent; +} + +swayc_t *remove_child(swayc_t *child) { +	int i; +	swayc_t *parent = child->parent; +	if (child->is_floating) { +		// Special case for floating views +		for (i = 0; i < parent->floating->length; ++i) { +			if (parent->floating->items[i] == child) { +				list_del(parent->floating, i); +				break; +			} +		} +		i = 0; +	} else { +		for (i = 0; i < parent->children->length; ++i) { +			if (parent->children->items[i] == child) { +				list_del(parent->children, i); +				break; +			} +		} +		if (is_auto_layout(parent->layout) && parent->children->length) { +			/* go through each group, adjust the size of the last child of each group */ +			double *(*get_maj_dim)(swayc_t *cont); +			double *(*get_min_dim)(swayc_t *cont); +			if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) { +				get_maj_dim = get_width; +				get_min_dim = get_height; +			} else { +				get_maj_dim = get_height; +				get_min_dim = get_width; +			} +			for (int j = parent->children->length - 1; j >= i;) { +				int start = auto_group_start_index(parent, j); +				int end = auto_group_end_index(parent, j); +				swayc_t *first = parent->children->items[start]; +				if (i == start) { +					/* removed element was first child in the current group, +					   use its size along the major axis */ +					*get_maj_dim(first) = *get_maj_dim(child); +				} else if (start > i) { +					/* preserve the group's dimension along major axis */ +					*get_maj_dim(first) = *get_maj_dim(parent->children->items[start - 1]); +				} +				if (end != parent->children->length) { +					double remaining = *get_min_dim(parent); +					for (int k = start; k < end - 1; ++k) { +						swayc_t *sibling = parent->children->items[k]; +						remaining -= *get_min_dim(sibling); +					} +					/* last element of the group gets remaining size, elements +					   that don't change groups keep their ratio */ +					*get_min_dim((swayc_t *) parent->children->items[end - 1]) = remaining; +				} /* else last group, let apply_layout handle it */ +				j = start - 1; +			} +		} +	} +	// Set focused to new container +	if (parent->focused == child) { +		if (parent->children->length > 0) { +			parent->focused = parent->children->items[i ? i-1:0]; +		} else if (parent->floating && parent->floating->length) { +			parent->focused = parent->floating->items[parent->floating->length - 1]; +		} else { +			parent->focused = NULL; +		} +	} +	child->parent = NULL; +	// deactivate view +	if (child->type == C_VIEW) { +		wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false); +	} +	return parent; +} + +void swap_container(swayc_t *a, swayc_t *b) { +	if (!sway_assert(a&&b, "parameters must be non null") || +		!sway_assert(a->parent && b->parent, "containers must have parents")) { +		return; +	} +	size_t a_index = index_child(a); +	size_t b_index = index_child(b); +	swayc_t *a_parent = a->parent; +	swayc_t *b_parent = b->parent; +	// Swap the pointers +	if (a->is_floating) { +		a_parent->floating->items[a_index] = b; +	} else { +		a_parent->children->items[a_index] = b; +	} +	if (b->is_floating) { +		b_parent->floating->items[b_index] = a; +	} else { +		b_parent->children->items[b_index] = a; +	} +	a->parent = b_parent; +	b->parent = a_parent; +	if (a_parent->focused == a) { +		a_parent->focused = b; +	} +	// don't want to double switch +	if (b_parent->focused == b && a_parent != b_parent) { +		b_parent->focused = a; +	} +} + +void swap_geometry(swayc_t *a, swayc_t *b) { +	double x = a->x; +	double y = a->y; +	double w = a->width; +	double h = a->height; +	a->x = b->x; +	a->y = b->y; +	a->width = b->width; +	a->height = b->height; +	b->x = x; +	b->y = y; +	b->width = w; +	b->height = h; +} + +static void swap_children(swayc_t *container, int a, int b) { +	if (a >= 0 && b >= 0 && a < container->children->length +			&& b < container->children->length +		&& a != b) { +		swayc_t *pa = (swayc_t *)container->children->items[a]; +		swayc_t *pb = (swayc_t *)container->children->items[b]; +		container->children->items[a] = container->children->items[b]; +		container->children->items[b] = pa; +		if (is_auto_layout(container->layout)) { +			size_t ga = auto_group_index(container, a); +			size_t gb = auto_group_index(container, b); +			if (ga != gb) { +				swap_geometry(pa, pb); +			} +		} +	} +} + +void move_container(swayc_t *container, enum movement_direction dir, int move_amt) { +	enum swayc_layouts layout = L_NONE; +	swayc_t *parent = container->parent; +	if (container->is_floating) { +		swayc_t *output = swayc_parent_by_type(container, C_OUTPUT); +		switch(dir) { +		case MOVE_LEFT: +			container->x = MAX(0, container->x - move_amt); +			break; +		case MOVE_RIGHT: +			container->x = MIN(output->width - container->width, container->x + move_amt); +			break; +		case MOVE_UP: +			container->y = MAX(0, container->y - move_amt); +			break; +		case MOVE_DOWN: +			container->y = MIN(output->height - container->height, container->y + move_amt); +			break; +		default: +			break; +		} +		update_geometry(container); +		return; +	} +	if (container->type != C_VIEW && container->type != C_CONTAINER) { +		return; +	} +	if (dir == MOVE_UP || dir == MOVE_DOWN) { +		layout = L_VERT; +	} else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { +		layout = L_HORIZ; +	} else if (dir == MOVE_FIRST) { +		// swap first child in auto layout with currently focused child +		if (is_auto_layout(parent->layout)) { +			int focused_idx = index_child(container); +			swayc_t *first = parent->children->items[0]; +			if (focused_idx > 0) { +				list_swap(parent->children, 0, focused_idx); +				swap_geometry(first, container); +			} +			arrange_windows(parent->parent, -1, -1); +			ipc_event_window(container, "move"); +			set_focused_container_for(parent->parent, container); +		} +		return; +	} else if (! (dir == MOVE_NEXT || dir == MOVE_PREV)) { +		return; +	} +	swayc_t *child = container; +	bool ascended = false; + +	// View is wrapped in intermediate container which is needed for displaying +	// the titlebar. Moving only the view outside of its parent container would just +	// wrap it again under worspace. There would effectively be no movement, +	// just a change of wrapping container. +	if (child->type == C_VIEW && +		parent->type == C_CONTAINER && +		parent->children->length == 1 && +		parent->parent->type == C_WORKSPACE) { +		child = parent; +		parent = parent->parent; +	} + +	while (true) { +		sway_log(L_DEBUG, "container:%p, parent:%p, child %p,", +				container,parent,child); +		if (parent->layout == layout +			|| (layout == L_NONE && (parent->type == C_CONTAINER || parent->type == C_WORKSPACE)) /* accept any layout for next/prev direction */ +			|| (parent->layout == L_TABBED && layout == L_HORIZ) +			|| (parent->layout == L_STACKED && layout == L_VERT) +			|| is_auto_layout(parent->layout)) { +			int diff; +			// If it has ascended (parent has moved up), no container is removed +			// so insert it at index, or index+1. +			// if it has not, the moved container is removed, so it needs to be +			// inserted at index-1, or index+1 +			if (ascended) { +				diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? 0 : 1; +			} else { +				diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? -1 : 1; +			} +			int idx = index_child(child); +			int desired = idx + diff; +			if (dir == MOVE_NEXT || dir == MOVE_PREV) { +				// Next/Prev always wrap. +				if (desired < 0) { +					desired += parent->children->length; +				} else if (desired >= parent->children->length) { +					desired = 0; +				} +			} +			// when it has ascended, legal insertion position is 0:len +			// when it has not, legal insertion position is 0:len-1 +			if (desired >= 0 && desired - ascended < parent->children->length) { +				if (!ascended) { +					child = parent->children->items[desired]; +					// Move container into sibling container +					if (child->type == C_CONTAINER) { +						parent = child; +						// Insert it in first/last if matching layout, otherwise +						// insert it next to focused container +						if (parent->layout == layout +							|| (parent->layout == L_TABBED && layout == L_HORIZ) +							|| (parent->layout == L_STACKED && layout == L_VERT) +							|| is_auto_layout(parent->layout)) { +							desired = (diff < 0) * parent->children->length; +						} else { +							desired = index_child(child->focused) + 1; +						} +						//reset geometry +						container->width = container->height = 0; +					} +				} +				if (container->parent == parent) { +					swap_children(parent, idx, desired); +				} else { +					swayc_t *old_parent = remove_child(container); +					insert_child(parent, container, desired); +					destroy_container(old_parent); +					sway_log(L_DEBUG,"Moving to %p %d", parent, desired); +				} +				break; +			} +		} +		// Change parent layout if we need to +		if (parent->children->length == 1 && parent->layout != layout && layout != L_NONE) { +			/* swayc_change_layout(parent, layout); */ +			parent->layout = layout; +			continue; +		} +		if (parent->type == C_WORKSPACE) { +			// If moving to an adjacent output we need a starting position (since this +			// output might border to multiple outputs). +			struct wlc_point abs_pos; +			get_absolute_center_position(container, &abs_pos); + +			swayc_t *output = swayc_adjacent_output(parent->parent, dir, &abs_pos, true); + +			if (output) { +				sway_log(L_DEBUG, "Moving between outputs"); +				swayc_t *old_parent = remove_child(container); +				destroy_container(old_parent); + +				swayc_t *dest = output->focused; +				switch (dir) { +				case MOVE_LEFT: +				case MOVE_UP: +					// reset container geometry +					container->width = container->height = 0; +					add_child(dest, container); +					break; +				case MOVE_RIGHT: +				case MOVE_DOWN: +					// reset container geometry +					container->width = container->height = 0; +					insert_child(dest, container, 0); +					break; +				default: +					break; +				} +				// arrange new workspace +				arrange_windows(dest, -1, -1); +				set_focused_container(container); +				break; +			} + +			// We simply cannot move any further. +			if (parent->layout == layout) { +				break; +			} +			// Create container around workspace to insert child into +			parent = new_container(parent, layout); +			// Previous line set the resulting container's layout to +			// workspace_layout. It should have been just layout. +			parent->layout = parent->parent->layout; +		} +		ascended = true; +		child = parent; +		parent = child->parent; +	} +	arrange_windows(parent->parent, -1, -1); +	ipc_event_window(container, "move"); +	set_focused_container_for(parent->parent, container); +} + +void move_container_to(swayc_t* container, swayc_t* destination) { +	if (container == destination || swayc_is_parent_of(container, destination)) { +		return; +	} +	swayc_t *parent = remove_child(container); +	// Send to new destination +	if (container->is_floating) { +		swayc_t *ws = swayc_active_workspace_for(destination); +		add_floating(ws, container); + +		// If the workspace only has one child after adding one, it +		// means that the workspace was just initialized. +		if (ws->children->length + ws->floating->length == 1) { +			ipc_event_workspace(NULL, ws, "init"); +		} +	} else if (destination->type == C_WORKSPACE) { +		// reset container geometry +		container->width = container->height = 0; +		add_child(destination, container); + +		// If the workspace only has one child after adding one, it +		// means that the workspace was just initialized. +		if (destination->children->length + destination->floating->length == 1) { +			ipc_event_workspace(NULL, destination, "init"); +		} +	} else { +		// reset container geometry +		container->width = container->height = 0; +		add_sibling(destination, container); +	} +	// Destroy old container if we need to +	parent = destroy_container(parent); +	// Refocus +	swayc_t *op1 = swayc_parent_by_type(destination, C_OUTPUT); +	swayc_t *op2 = swayc_parent_by_type(parent, C_OUTPUT); +	set_focused_container(get_focused_view(op1)); +	arrange_windows(op1, -1, -1); +	update_visibility(op1); +	if (op1 != op2) { +		set_focused_container(get_focused_view(op2)); +		arrange_windows(op2, -1, -1); +		update_visibility(op2); +	} +} + +void move_workspace_to(swayc_t* workspace, swayc_t* destination) { +	if (workspace == destination || swayc_is_parent_of(workspace, destination)) { +		return; +	} +	swayc_t *src_op = remove_child(workspace); +	// reset container geometry +	workspace->width = workspace->height = 0; +	add_child(destination, workspace); +	sort_workspaces(destination); +	// Refocus destination (change to new workspace) +	set_focused_container(get_focused_view(workspace)); +	arrange_windows(destination, -1, -1); +	update_visibility(destination); + +	// make sure source output has a workspace +	if (src_op->children->length == 0) { +		char *ws_name = workspace_next_name(src_op->name); +		swayc_t *ws = new_workspace(src_op, ws_name); +		ws->is_focused = true; +		free(ws_name); +	} +	set_focused_container(get_focused_view(src_op)); +	update_visibility(src_op); +} + +static void adjust_border_geometry(swayc_t *c, struct wlc_geometry *g, +	const struct wlc_size *res, int left, int right, int top, int bottom) { + +	g->size.w += left + right; +	if (g->origin.x - left < 0) { +		g->size.w += g->origin.x - left; +	} else if (g->origin.x + g->size.w - right > res->w) { +		g->size.w = res->w - g->origin.x + right; +	} + +	g->size.h += top + bottom; +	if (g->origin.y - top < 0) { +		g->size.h += g->origin.y - top; +	} else if (g->origin.y + g->size.h - top > res->h) { +		g->size.h = res->h - g->origin.y + top; +	} + +	g->origin.x = MIN((uint32_t)MAX(g->origin.x - left, 0), res->w); +	g->origin.y = MIN((uint32_t)MAX(g->origin.y - top, 0), res->h); + +} + +static void update_border_geometry_floating(swayc_t *c, struct wlc_geometry *geometry) { +	struct wlc_geometry g = *geometry; +	c->actual_geometry = g; + +	swayc_t *output = swayc_parent_by_type(c, C_OUTPUT); +	struct wlc_size res; +	output_get_scaled_size(output->handle, &res); + +	switch (c->border_type) { +	case B_NONE: +		break; +	case B_PIXEL: +		adjust_border_geometry(c, &g, &res, c->border_thickness, +			c->border_thickness, c->border_thickness, c->border_thickness); +		break; +	case B_NORMAL: +	{ +		int title_bar_height = config->font_height + 4; // borders + padding + +		adjust_border_geometry(c, &g, &res, c->border_thickness, +			c->border_thickness, title_bar_height, c->border_thickness); + +		struct wlc_geometry title_bar = { +			.origin = { +				.x = c->actual_geometry.origin.x - c->border_thickness, +				.y = c->actual_geometry.origin.y - title_bar_height +			}, +			.size = { +				.w = c->actual_geometry.size.w + (2 * c->border_thickness), +				.h = title_bar_height +			} +		}; +		c->title_bar_geometry = title_bar; +		break; +	} +	} + +	c->border_geometry = g; +	*geometry = c->actual_geometry; + +	update_container_border(c); +} + +void update_layout_geometry(swayc_t *parent, enum swayc_layouts prev_layout) { +	switch (parent->layout) { +	case L_TABBED: +	case L_STACKED: +		if (prev_layout != L_TABBED && prev_layout != L_STACKED) { +			// cache current geometry for all non-float children +			int i; +			for (i = 0; i < parent->children->length; ++i) { +				swayc_t *child = parent->children->items[i]; +				child->cached_geometry.origin.x = child->x; +				child->cached_geometry.origin.y = child->y; +				child->cached_geometry.size.w = child->width; +				child->cached_geometry.size.h = child->height; +			} +		} +		break; +	default: +		if (prev_layout == L_TABBED || prev_layout == L_STACKED) { +			// recover cached geometry for all non-float children +			int i; +			for (i = 0; i < parent->children->length; ++i) { +				swayc_t *child = parent->children->items[i]; +				// only recoverer cached geometry if non-zero +				if (!wlc_geometry_equals(&child->cached_geometry, &wlc_geometry_zero)) { +					child->x = child->cached_geometry.origin.x; +					child->y = child->cached_geometry.origin.y; +					child->width = child->cached_geometry.size.w; +					child->height = child->cached_geometry.size.h; +				} +			} +		} +		break; +	} +} + +static int update_gap_geometry(swayc_t *container, struct wlc_geometry *g) { +	swayc_t *ws = swayc_parent_by_type(container, C_WORKSPACE); +	swayc_t *op = ws->parent; +	int gap = container->is_floating ? 0 : swayc_gap(container); +	if (gap % 2 != 0) { +		// because gaps are implemented as "half sized margins" it's currently +		// not possible to align views properly with odd sized gaps. +		gap -= 1; +	} + +	g->origin.x = container->x + gap/2 < op->width  ? container->x + gap/2 : op->width-1; +	g->origin.y = container->y + gap/2 < op->height ? container->y + gap/2 : op->height-1; +	g->size.w = container->width > gap ? container->width - gap : 1; +	g->size.h = container->height > gap ? container->height - gap : 1; + +	if ((!config->edge_gaps && gap > 0) || (config->smart_gaps && ws->children->length == 1)) { +		// Remove gap against the workspace edges. Because a pixel is not +		// divisable, depending on gap size and the number of siblings our view +		// might be at the workspace edge without being exactly so (thus test +		// with gap, and align correctly). +		if (container->x - gap <= ws->x) { +			g->origin.x = ws->x; +			g->size.w = container->width - gap/2; +		} +		if (container->y - gap <= ws->y) { +			g->origin.y = ws->y; +			g->size.h = container->height - gap/2; +		} +		if (container->x + container->width + gap >= ws->x + ws->width) { +			g->size.w = ws->x + ws->width - g->origin.x; +		} +		if (container->y + container->height + gap >= ws->y + ws->height) { +			g->size.h = ws->y + ws->height - g->origin.y; +		} +	} + +	return gap; +} + +void update_geometry(swayc_t *container) { +	if (container->type != C_VIEW && container->type != C_CONTAINER) { +		return; +	} + +	swayc_t *workspace = swayc_parent_by_type(container, C_WORKSPACE); +	swayc_t *op = workspace->parent; +	swayc_t *parent = container->parent; + +	struct wlc_geometry geometry = { +		.origin = { +			.x = container->x < op->width ? container->x : op->width-1, +			.y = container->y < op->height ? container->y : op->height-1 +		}, +		.size = { +			.w = container->width, +			.h = container->height, +		} +	}; + +	int gap = 0; + +	// apply inner gaps to non-tabbed/stacked containers +	swayc_t *p = swayc_tabbed_stacked_ancestor(container); +	if (p == NULL) { +		gap = update_gap_geometry(container, &geometry); +	} + +	swayc_t *output = swayc_parent_by_type(container, C_OUTPUT); +	struct wlc_size size; +	output_get_scaled_size(output->handle, &size); + +	if (swayc_is_fullscreen(container)) { +		geometry.origin.x = 0; +		geometry.origin.y = 0; +		geometry.size.w = size.w; +		geometry.size.h = size.h; +		if (op->focused == workspace) { +			wlc_view_bring_to_front(container->handle); +		} + +		container->border_geometry = wlc_geometry_zero; +		container->title_bar_geometry = wlc_geometry_zero; +		border_clear(container->border); +	} else if (container->is_floating) { // allocate border for floating window +		update_border_geometry_floating(container, &geometry); +	} else if (!container->is_floating) { // allocate border for titled window +		container->border_geometry = geometry; + +		int border_top = container->border_thickness; +		int border_bottom = container->border_thickness; +		int border_left = container->border_thickness; +		int border_right = container->border_thickness; + +		// handle hide_edge_borders +		if (config->hide_edge_borders != E_NONE && (gap <= 0 || (config->smart_gaps && workspace->children->length == 1))) { +			if (config->hide_edge_borders == E_VERTICAL || config->hide_edge_borders == E_BOTH) { +				if (geometry.origin.x == workspace->x) { +					border_left = 0; +				} + +				if (geometry.origin.x + geometry.size.w == workspace->x + workspace->width) { +					border_right = 0; +				} +			} + +			if (config->hide_edge_borders == E_HORIZONTAL || config->hide_edge_borders == E_BOTH) { +				if (geometry.origin.y == workspace->y || should_hide_top_border(container, geometry.origin.y)) { +					border_top = 0; +				} + +				if (geometry.origin.y + geometry.size.h == workspace->y + workspace->height) { +					border_bottom = 0; +				} +			} + +			if (config->hide_edge_borders == E_SMART && workspace->children->length == 1) { +				border_top = 0; +				border_bottom = 0; +				border_left = 0; +				border_right = 0; +			} +		} + +		int title_bar_height = config->font_height + 4; //borders + padding + +		if (parent->layout == L_TABBED && parent->children->length > 1) { +			int i, x = 0, w, l, r; +			l = parent->children->length; +			w = geometry.size.w / l; +			r = geometry.size.w % l; +			for (i = 0; i < parent->children->length; ++i) { +				swayc_t *view = parent->children->items[i]; +				if (view == container) { +					x = w * i; +					if (i == l - 1) { +						w += r; +					} +					break; +				} +			} + +			struct wlc_geometry title_bar = { +				.origin = { +					.x = container->border_geometry.origin.x + x, +					.y = container->border_geometry.origin.y +				}, +				.size = { +					.w = w, +					.h = title_bar_height +				} +			}; +			geometry.origin.x += border_left; +			geometry.origin.y += title_bar.size.h; +			geometry.size.w -= (border_left + border_right); +			geometry.size.h -= (border_bottom + title_bar.size.h); +			container->title_bar_geometry = title_bar; +		} else if (parent->layout == L_STACKED && parent->children->length > 1) { +			int i, y = 0; +			for (i = 0; i < parent->children->length; ++i) { +				swayc_t *view = parent->children->items[i]; +				if (view == container) { +					y = title_bar_height * i; +				} +			} + +			struct wlc_geometry title_bar = { +				.origin = { +					.x = container->border_geometry.origin.x, +					.y = container->border_geometry.origin.y + y +				}, +				.size = { +					.w = container->border_geometry.size.w, +					.h = title_bar_height +				} +			}; +			title_bar_height = title_bar_height * parent->children->length; +			geometry.origin.x += border_left; +			geometry.origin.y += title_bar_height; +			geometry.size.w -= (border_left + border_right); +			geometry.size.h -= (border_bottom + title_bar_height); +			container->title_bar_geometry = title_bar; +		} else { +			switch (container->border_type) { +			case B_NONE: +				break; +			case B_PIXEL: +				geometry.origin.x += border_left; +				geometry.origin.y += border_top; +				geometry.size.w -= (border_left + border_right); +				geometry.size.h -= (border_top + border_bottom); +				break; +			case B_NORMAL: +				{ +					struct wlc_geometry title_bar = { +						.origin = { +							.x = container->border_geometry.origin.x, +							.y = container->border_geometry.origin.y +						}, +						.size = { +							.w = container->border_geometry.size.w, +							.h = title_bar_height +						} +					}; +					geometry.origin.x += border_left; +					geometry.origin.y += title_bar.size.h; +					geometry.size.w -= (border_left + border_right); +					geometry.size.h -= (border_bottom + title_bar.size.h); +					container->title_bar_geometry = title_bar; +					break; +				} +			} +		} + +		container->actual_geometry = geometry; + +		if (container->type == C_VIEW) { +			update_container_border(container); +		} +	} + +	if (container->type == C_VIEW) { +		wlc_view_set_geometry(container->handle, 0, &geometry); +	} +} + +/** + * Layout application prototypes + */ +static void apply_horiz_layout(swayc_t *container, const double x, +				const double y, const double width, +				const double height, const int start, +				const int end); +static void apply_vert_layout(swayc_t *container, const double x, +				const double y, const double width, +				const double height, const int start, +				const int end); +static void apply_tabbed_or_stacked_layout(swayc_t *container, double x, +				double y, double width, +				double height); + +static void apply_auto_layout(swayc_t *container, const double x, const double y, +				const double width, const double height, +				enum swayc_layouts group_layout, +				bool master_first); + +static void arrange_windows_r(swayc_t *container, double width, double height) { +	int i; +	if (width == -1 || height == -1) { +		swayc_log(L_DEBUG, container, "Arranging layout for %p", container); +		width = container->width; +		height = container->height; +	} +	// pixels are indivisible. if we don't round the pixels, then the view +	// calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's +	// 50 + 50 = 100). doing it here cascades properly to all width/height/x/y. +	width = floor(width); +	height = floor(height); + +	sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container, +		 container->name, container->width, container->height, container->x, +		 container->y); + +	double x = 0, y = 0; +	switch (container->type) { +	case C_ROOT: +		for (i = 0; i < container->children->length; ++i) { +			swayc_t *output = container->children->items[i]; +			sway_log(L_DEBUG, "Arranging output '%s' at %f,%f", output->name, output->x, output->y); +			arrange_windows_r(output, -1, -1); +		} +		return; +	case C_OUTPUT: +		{ +			struct wlc_size resolution; +			output_get_scaled_size(container->handle, &resolution); +			width = resolution.w; height = resolution.h; +			// output must have correct size due to e.g. seamless mouse, +			// but a workspace might be smaller depending on panels. +			container->width = width; +			container->height = height; +		} +		// arrange all workspaces: +		for (i = 0; i < container->children->length; ++i) { +			swayc_t *child = container->children->items[i]; +			arrange_windows_r(child, -1, -1); +		} +		// Bring all unmanaged views to the front +		for (i = 0; i < container->unmanaged->length; ++i) { +			wlc_handle *handle = container->unmanaged->items[i]; +			wlc_view_bring_to_front(*handle); +		} +		return; +	case C_WORKSPACE: +		{ +			swayc_t *output = swayc_parent_by_type(container, C_OUTPUT); +			width = output->width, height = output->height; +			/* TODO WLR +			for (i = 0; i < desktop_shell.panels->length; ++i) { +				struct panel_config *config = desktop_shell.panels->items[i]; +				if (config->output == output->handle) { +					struct wlc_size size = *wlc_surface_get_size(config->surface); +					sway_log(L_DEBUG, "-> Found panel for this workspace: %ux%u, position: %u", size.w, size.h, config->panel_position); +					switch (config->panel_position) { +					case DESKTOP_SHELL_PANEL_POSITION_TOP: +						y += size.h; height -= size.h; +						break; +					case DESKTOP_SHELL_PANEL_POSITION_BOTTOM: +						height -= size.h; +						break; +					case DESKTOP_SHELL_PANEL_POSITION_LEFT: +						x += size.w; width -= size.w; +						break; +					case DESKTOP_SHELL_PANEL_POSITION_RIGHT: +						width -= size.w; +						break; +					} +				} +			} +			*/ +			int gap = swayc_gap(container); +			x = container->x = x + gap; +			y = container->y = y + gap; +			width = container->width = width - gap * 2; +			height = container->height = height - gap * 2; +			sway_log(L_DEBUG, "Arranging workspace '%s' at %f, %f", container->name, container->x, container->y); +		} +		// children are properly handled below +		break; +	case C_VIEW: +		{ +			container->width = width; +			container->height = height; +			update_geometry(container); +			sway_log(L_DEBUG, "Set view to %.f x %.f @ %.f, %.f", container->width, +					container->height, container->x, container->y); +		} +		return; +	default: +		container->width = width; +		container->height = height; +		x = container->x; +		y = container->y; + +		// add gaps to top level tapped/stacked container +		if (container->parent->type == C_WORKSPACE && +			(container->layout == L_TABBED || container->layout == L_STACKED)) { +			update_geometry(container); +			width = container->border_geometry.size.w; +			height = container->border_geometry.size.h; +			x = container->border_geometry.origin.x; +			y = container->border_geometry.origin.y; +		} + +		// update container size if it's a direct child in a tabbed/stacked layout +		// if parent is a workspace, its actual_geometry won't be initialized +		if (swayc_tabbed_stacked_parent(container) != NULL && +			container->parent->type != C_WORKSPACE) { +			// Use parent actual_geometry as a base for calculating +			// container geometry +			container->width = container->parent->actual_geometry.size.w; +			container->height = container->parent->actual_geometry.size.h; +			container->x = container->parent->actual_geometry.origin.x; +			container->y = container->parent->actual_geometry.origin.y; + +			update_geometry(container); +			width = container->width = container->actual_geometry.size.w; +			height = container->height = container->actual_geometry.size.h; +			x = container->x = container->actual_geometry.origin.x; +			y = container->y = container->actual_geometry.origin.y; +		} + +		break; +	} + +	switch (container->layout) { +	case L_HORIZ: +	default: +		apply_horiz_layout(container, x, y, width, height, 0, +			container->children->length); +		break; +	case L_VERT: +		apply_vert_layout(container, x, y, width, height, 0, +			container->children->length); +		break; +	case L_TABBED: +	case L_STACKED: +		apply_tabbed_or_stacked_layout(container, x, y, width, height); +		break; +	case L_AUTO_LEFT: +		apply_auto_layout(container, x, y, width, height, L_VERT, true); +		break; +	case L_AUTO_RIGHT: +		apply_auto_layout(container, x, y, width, height, L_VERT, false); +		break; +	case L_AUTO_TOP: +		apply_auto_layout(container, x, y, width, height, L_HORIZ, true); +		break; +	case L_AUTO_BOTTOM: +		apply_auto_layout(container, x, y, width, height, L_HORIZ, false); +		break; +	} + +	// Arrage floating layouts for workspaces last +	if (container->type == C_WORKSPACE) { +		for (int i = 0; i < container->floating->length; ++i) { +			swayc_t *view = container->floating->items[i]; +			if (view->type == C_VIEW) { +				update_geometry(view); +				sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f", +					 view->width, view->height, view->x, view->y); +				if (swayc_is_fullscreen(view)) { +					wlc_view_bring_to_front(view->handle); +				} else if (!container->focused || +						!swayc_is_fullscreen(container->focused)) { +					wlc_view_bring_to_front(view->handle); +				} +			} +		} +	} +} + +void apply_horiz_layout(swayc_t *container, const double x, const double y, +			const double width, const double height, +			const int start, const int end) { +	double scale = 0; +	// Calculate total width +	for (int i = start; i < end; ++i) { +		double *old_width = &((swayc_t *)container->children->items[i])->width; +		if (*old_width <= 0) { +			if (end - start > 1) { +				*old_width = width / (end - start - 1); +			} else { +				*old_width = width; +			} +		} +		scale += *old_width; +	} +	scale = width / scale; + +	// Resize windows +	double child_x = x; +	if (scale > 0.1) { +		sway_log(L_DEBUG, "Arranging %p horizontally", container); +		swayc_t *focused = NULL; +		for (int i = start; i < end; ++i) { +			swayc_t *child = container->children->items[i]; +			sway_log(L_DEBUG, +				 "Calculating arrangement for %p:%d (will scale %f by %f)", child, +				 child->type, width, scale); +			child->x = child_x; +			child->y = y; + +			if (child == container->focused) { +				focused = child; +			} + +			if (i == end - 1) { +				double remaining_width = x + width - child_x; +				arrange_windows_r(child, remaining_width, height); +			} else { +				arrange_windows_r(child, child->width * scale, height); +			} +			child_x += child->width; +		} + +		// update focused view border last because it may +		// depend on the title bar geometry of its siblings. +		if (focused && container->children->length > 1) { +			update_container_border(focused); +		} +	} +} + +void apply_vert_layout(swayc_t *container, const double x, const double y, +			const double width, const double height, const int start, +			const int end) { +	int i; +	double scale = 0; +	// Calculate total height +	for (i = start; i < end; ++i) { +		double *old_height = &((swayc_t *)container->children->items[i])->height; +		if (*old_height <= 0) { +			if (end - start > 1) { +				*old_height = height / (end - start - 1); +			} else { +				*old_height = height; +			} +		} +		scale += *old_height; +	} +	scale = height / scale; + +	// Resize +	double child_y = y; +	if (scale > 0.1) { +		sway_log(L_DEBUG, "Arranging %p vertically", container); +		swayc_t *focused = NULL; +		for (i = start; i < end; ++i) { +			swayc_t *child = container->children->items[i]; +			sway_log(L_DEBUG, +				 "Calculating arrangement for %p:%d (will scale %f by %f)", child, +				 child->type, height, scale); +			child->x = x; +			child->y = child_y; + +			if (child == container->focused) { +				focused = child; +			} + +			if (i == end - 1) { +				double remaining_height = y + height - child_y; +				arrange_windows_r(child, width, remaining_height); +			} else { +				arrange_windows_r(child, width, child->height * scale); +			} +			child_y += child->height; +		} + +		// update focused view border last because it may +		// depend on the title bar geometry of its siblings. +		if (focused && container->children->length > 1) { +			update_container_border(focused); +		} +	} +} + +void apply_tabbed_or_stacked_layout(swayc_t *container, double x, double y, +					double width, double height) { +	int i; +	swayc_t *focused = NULL; +	for (i = 0; i < container->children->length; ++i) { +		swayc_t *child = container->children->items[i]; +		child->x = x; +		child->y = y; +		if (child == container->focused) { +			focused = child; +		} else { +			arrange_windows_r(child, width, height); +		} +	} + +	if (focused) { +		arrange_windows_r(focused, width, height); +	} +} + +void apply_auto_layout(swayc_t *container, const double x, const double y, +			const double width, const double height, +			enum swayc_layouts group_layout, +			bool master_first) { +	// Auto layout "container" in width x height @ x, y +	// using "group_layout" for each of the groups in the container. +	// There is one "master" group, plus container->nb_slave_groups. +	// Each group is layed out side by side following the "major" axis. +	// The direction of the layout used for groups is the "minor" axis. +	// Example: +	// +	//     ---- major axis --> +	//   +---------+-----------+ +	//   |         |           |   | +	//   | master  | slave 1   |   | +	//   |         +-----------+   | minor axis (direction of group_layout) +	//   |         |           |   | +	//   |         | slave 2   |   V +	//   +---------+-----------+ +	// +	//  container with three children (one master and two slaves) and +	//  a single slave group (containing slave 1 and 2). The master +	//  group and slave group are layed out using L_VERT. + +	size_t nb_groups = auto_group_count(container); + +	// the target dimension of the container along the "major" axis, each +	// group in the container will be layed out using "group_layout" along +	// the "minor" axis. +	double dim_maj; +	double pos_maj; + +	// x and y coords for the next group to be laid out. +	const double *group_x, *group_y; + +	// pos of the next group to layout along the major axis +	double pos; + +	// size of the next group along the major axis. +	double group_dim; + +	// height and width of next group to be laid out. +	const double *group_h, *group_w; + +	switch (group_layout) { +	default: +		sway_log(L_DEBUG, "Unknown layout type (%d) used in %s()", +			 group_layout, __func__); +		/* fall through */ +	case L_VERT: +		dim_maj = width; +		pos_maj = x; + +		group_x = &pos; +		group_y = &y; +		group_w = &group_dim; +		group_h = &height; +		break; +	case L_HORIZ: +		dim_maj = height; +		pos_maj = y; + +		group_x = &x; +		group_y = &pos; +		group_w = &width; +		group_h = &group_dim; +		break; +	} + +	/* Determine the dimension of each of the groups in the layout. +	 * Dimension will be width for a VERT layout and height for a HORIZ +	 * layout. */ +	double old_group_dim[nb_groups]; +	double old_dim = 0; +	for (size_t group = 0; group < nb_groups; ++group) { +		int idx; +		if (auto_group_bounds(container, group, &idx, NULL)) { +			swayc_t *child = container->children->items[idx]; +			double *dim = group_layout == L_HORIZ ? &child->height : &child->width; +			if (*dim <= 0) { +				// New child with uninitialized dimension +				*dim = dim_maj; +				if (nb_groups > 1) { +					// child gets a dimension proportional to existing groups, +					// it will be later scaled based on to the available size +					// in the major axis. +					*dim /= (nb_groups - 1); +				} +			} +			old_dim += *dim; +			old_group_dim[group] = *dim; +		} +	} +	double scale = dim_maj / old_dim; + +	/* Apply layout to each group */ +	pos = pos_maj; + +	for (size_t group = 0; group < nb_groups; ++group) { +		int start, end;	// index of first (inclusive) and last (exclusive) child in the group +		if (auto_group_bounds(container, group, &start, &end)) { +			// adjusted size of the group +			group_dim = old_group_dim[group] * scale; +			if (group == nb_groups - 1) { +				group_dim = pos_maj + dim_maj - pos; // remaining width +			} +			sway_log(L_DEBUG, "Arranging container %p column %zu, children [%d,%d[ (%fx%f+%f,%f)", +				 container, group, start, end, *group_w, *group_h, *group_x, *group_y); +			switch (group_layout) { +			default: +			case L_VERT: +				apply_vert_layout(container, *group_x, *group_y, *group_w, *group_h, start, end); +				break; +			case L_HORIZ: +				apply_horiz_layout(container, *group_x, *group_y, *group_w, *group_h, start, end); +				break; +			} + +			/* update position for next group */ +			pos += group_dim; +		} +	} +} + +void arrange_windows(swayc_t *container, double width, double height) { +	update_visibility(container); +	arrange_windows_r(container, width, height); +	layout_log(&root_container, 0); +} + +void arrange_backgrounds(void) { +	/* TODO WLR +	struct background_config *bg; +	for (int i = 0; i < desktop_shell.backgrounds->length; ++i) { +		bg = desktop_shell.backgrounds->items[i]; +		wlc_view_send_to_back(bg->handle); +	} +	*/ +} + +/** + * Get swayc in the direction of newly entered output. + */ +static swayc_t *get_swayc_in_output_direction(swayc_t *output, enum movement_direction dir) { +	if (!output) { +		return NULL; +	} + +	swayc_t *ws = swayc_focus_by_type(output, C_WORKSPACE); +	if (ws && ws->children->length > 0) { +		switch (dir) { +		case MOVE_LEFT: +			// get most right child of new output +			return ws->children->items[ws->children->length-1]; +		case MOVE_RIGHT: +			// get most left child of new output +			return ws->children->items[0]; +		case MOVE_UP: +		case MOVE_DOWN: +			{ +				swayc_t *focused_view = swayc_focus_by_type(ws, C_VIEW); +				if (focused_view && focused_view->parent) { +					swayc_t *parent = focused_view->parent; +					if (parent->layout == L_VERT) { +						if (dir == MOVE_UP) { +							// get child furthest down on new output +							return parent->children->items[parent->children->length-1]; +						} else if (dir == MOVE_DOWN) { +							// get child furthest up on new output +							return parent->children->items[0]; +						} +					} +					return focused_view; +				} +				break; +			} +		default: +			break; +		} +	} + +	return output; +} + +swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_direction dir, swayc_t *limit) { +	if (dir == MOVE_CHILD) { +		return container->focused; +	} + +	swayc_t *parent = container->parent; +	if (dir == MOVE_PARENT) { +		if (parent->type == C_OUTPUT) { +			return NULL; +		} else { +			return parent; +		} +	} + +	if (dir == MOVE_PREV || dir == MOVE_NEXT) { +		int focused_idx = index_child(container); +		if (focused_idx == -1) { +			return NULL; +		} else { +			int desired = (focused_idx + (dir == MOVE_NEXT ? 1 : -1)) % +				parent->children->length; +			if (desired < 0) { +				desired += parent->children->length; +			} +			return parent->children->items[desired]; +		} +	} + +	// If moving to an adjacent output we need a starting position (since this +	// output might border to multiple outputs). +	struct wlc_point abs_pos; +	get_absolute_center_position(container, &abs_pos); + +	if (container->type == C_VIEW && swayc_is_fullscreen(container)) { +		sway_log(L_DEBUG, "Moving from fullscreen view, skipping to output"); +		container = swayc_parent_by_type(container, C_OUTPUT); +		get_absolute_center_position(container, &abs_pos); +		swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true); +		return get_swayc_in_output_direction(output, dir); +	} + +	if (container->type == C_WORKSPACE && container->fullscreen) { +		sway_log(L_DEBUG, "Moving to fullscreen view"); +		return container->fullscreen; +	} + +	swayc_t *wrap_candidate = NULL; +	while (true) { +		// Test if we can even make a difference here +		bool can_move = false; +		int desired; +		int idx = index_child(container); +		if (parent->type == C_ROOT) { +			swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true); +			if (!output || output == container) { +				return wrap_candidate; +			} +			sway_log(L_DEBUG, "Moving between outputs"); +			return get_swayc_in_output_direction(output, dir); +		} else { +			if (is_auto_layout(parent->layout)) { +				bool is_major = parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT +					? dir == MOVE_LEFT || dir == MOVE_RIGHT +					: dir == MOVE_DOWN || dir == MOVE_UP; +				size_t gidx = auto_group_index(parent, idx); +				if (is_major) { +					size_t desired_grp = gidx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1); +					can_move = auto_group_bounds(parent, desired_grp, &desired, NULL); +				} else { +					desired = idx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1); +					int start, end; +					can_move = auto_group_bounds(parent, gidx, &start, &end) +							&& desired >= start && desired < end; +				} +			} else { +				if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { +					if (parent->layout == L_HORIZ || parent->layout == L_TABBED) { +						can_move = true; +						desired = idx + (dir == MOVE_LEFT ? -1 : 1); +					} +				} else { +					if (parent->layout == L_VERT || parent->layout == L_STACKED) { +						can_move = true; +						desired = idx + (dir == MOVE_UP ? -1 : 1); +					} +				} +			} +		} + +		if (can_move) { +			if (container->is_floating) { +				if (desired < 0) { +					wrap_candidate = parent->floating->items[parent->floating->length-1]; +				} else if (desired >= parent->floating->length){ +					wrap_candidate = parent->floating->items[0]; +				} else { +					wrap_candidate = parent->floating->items[desired]; +				} +				if (wrap_candidate) { +					wlc_view_bring_to_front(wrap_candidate->handle); +				} +				return wrap_candidate; +			} else if (desired < 0 || desired >= parent->children->length) { +				can_move = false; +				int len = parent->children->length; +				if (!wrap_candidate && len > 1) { +					if (desired < 0) { +						wrap_candidate = parent->children->items[len-1]; +					} else { +						wrap_candidate = parent->children->items[0]; +					} +					if (config->force_focus_wrapping) { +						return wrap_candidate; +					} +				} +			} else { +				sway_log(L_DEBUG, "%s cont %d-%p dir %i sibling %d: %p", __func__, +					 idx, container, dir, desired, parent->children->items[desired]); +				return parent->children->items[desired]; +			} +		} +		if (!can_move) { +			container = parent; +			parent = parent->parent; +			if (!parent || container == limit) { +				// wrapping is the last chance +				return wrap_candidate; +			} +		} +	} +} + +swayc_t *get_swayc_in_direction(swayc_t *container, enum movement_direction dir) { +	return get_swayc_in_direction_under(container, dir, NULL); +} + +void recursive_resize(swayc_t *container, double amount, enum wlc_resize_edge edge) { +	int i; +	bool layout_match = true; +	sway_log(L_DEBUG, "Resizing %p with amount: %f", container, amount); +	if (edge == WLC_RESIZE_EDGE_LEFT || edge == WLC_RESIZE_EDGE_RIGHT) { +		container->width += amount; +		layout_match = container->layout == L_HORIZ; +	} else if (edge == WLC_RESIZE_EDGE_TOP || edge == WLC_RESIZE_EDGE_BOTTOM) { +		container->height += amount; +		layout_match = container->layout == L_VERT; +	} +	if (container->type == C_VIEW) { +		update_geometry(container); +		return; +	} +	if (layout_match) { +		for (i = 0; i < container->children->length; i++) { +			recursive_resize(container->children->items[i], amount/container->children->length, edge); +		} +	} else { +		for (i = 0; i < container->children->length; i++) { +			recursive_resize(container->children->items[i], amount, edge); +		} +	} +} + +enum swayc_layouts default_layout(swayc_t *output) { +	if (config->default_layout != L_NONE) { +		return config->default_layout; +	} else if (config->default_orientation != L_NONE) { +		return config->default_orientation; +	} else if (output->width >= output->height) { +		return L_HORIZ; +	} else { +		return L_VERT; +	} +} + +bool is_auto_layout(enum swayc_layouts layout) { +	return (layout >= L_AUTO_FIRST) && (layout <= L_AUTO_LAST); +} + +/** + * Return the number of master elements in a container + */ +static inline size_t auto_master_count(const swayc_t *container) { +	sway_assert(container->children->length >= 0, "Container %p has (negative) children %d", +			container, container->children->length); +	return MIN(container->nb_master, (size_t)container->children->length); +} + +/** + * Return the number of children in the slave groups. This corresponds to the children + * that are not members of the master group. + */ +static inline size_t auto_slave_count(const swayc_t *container) { +	return container->children->length - auto_master_count(container); +} + +/** + * Return the number of slave groups in the container. + */ +size_t auto_slave_group_count(const swayc_t *container) { +	return MIN(container->nb_slave_groups, auto_slave_count(container)); +} + +/** + * Return the combined number of master and slave groups in the container. + */ +size_t auto_group_count(const swayc_t *container) { +	return auto_slave_group_count(container) +		+ (container->children->length && container->nb_master ? 1 : 0); +} + +/** + * given the index of a container's child, return the index of the first child of the group + * which index is a member of. + */ +int auto_group_start_index(const swayc_t *container, int index) { +	if (index < 0 || ! is_auto_layout(container->layout) +		|| (size_t)index < container->nb_master) { +		return 0; +	} else { +		size_t nb_slaves = auto_slave_count(container); +		size_t nb_slave_grp = auto_slave_group_count(container); +		size_t grp_sz = nb_slaves / nb_slave_grp; +		size_t remainder = nb_slaves % nb_slave_grp; +		int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master; +		int start_idx; +		if (index < idx2) { +			start_idx = ((index - container->nb_master) / grp_sz) * grp_sz + container->nb_master; +		} else { +			start_idx = idx2 + ((index - idx2) / (grp_sz + 1)) * (grp_sz + 1); +		} +		return MIN(start_idx, container->children->length); +	} +} + +/** + * given the index of a container's child, return the index of the first child of the group + * that follows the one which index is a member of. + * This makes the function usable to walk through the groups in a container. + */ +int auto_group_end_index(const swayc_t *container, int index) { +	if (index < 0 || ! is_auto_layout(container->layout)) { +		return container->children->length; +	} else { +		int nxt_idx; +		if ((size_t)index < container->nb_master) { +			nxt_idx = auto_master_count(container); +		} else { +			size_t nb_slaves = auto_slave_count(container); +			size_t nb_slave_grp = auto_slave_group_count(container); +			size_t grp_sz = nb_slaves / nb_slave_grp; +			size_t remainder = nb_slaves % nb_slave_grp; +			int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master; +			if (index < idx2) { +				nxt_idx = ((index - container->nb_master) / grp_sz + 1) * grp_sz + container->nb_master; +			} else { +				nxt_idx = idx2 + ((index - idx2) / (grp_sz + 1) + 1) * (grp_sz + 1); +			} +		} +		return MIN(nxt_idx, container->children->length); +	} +} + +/** + * return the index of the Group containing <index>th child of <container>. + * The index is the order of the group along the container's major axis (starting at 0). + */ +size_t auto_group_index(const swayc_t *container, int index) { +	if (index < 0) { +		return 0; +	} +	bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP); +	size_t nb_slaves = auto_slave_count(container); +	if ((size_t)index < container->nb_master) { +		if (master_first || nb_slaves <= 0) { +			return 0; +		} else { +			return auto_slave_group_count(container); +		} +	} else { +		size_t nb_slave_grp = auto_slave_group_count(container); +		size_t grp_sz = nb_slaves / nb_slave_grp; +		size_t remainder = nb_slaves % nb_slave_grp; +		int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master; +		size_t grp_idx; +		if (index < idx2) { +			grp_idx = (index - container->nb_master) / grp_sz; +		} else { +			grp_idx = (nb_slave_grp - remainder) + (index - idx2) / (grp_sz + 1) ; +		} +		return grp_idx + (master_first && container-> nb_master ? 1 : 0); +	} +} + +/** + * Return the first index (inclusive) and last index (exclusive) of the elements of a group in + * an auto layout. + * If the bounds of the given group can be calculated, they are returned in the start/end + * parameters (int pointers) and the return value will be true. + * The indexes are passed by reference and can be NULL. + */ +bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end) { +	size_t nb_grp = auto_group_count(container); +	if (group_index >= nb_grp) { +		return false; +	} +	bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP); +	size_t nb_master = auto_master_count(container); +	size_t nb_slave_grp = auto_slave_group_count(container); +	int g_start, g_end; +	if (nb_master && (master_first ? group_index == 0 : group_index == nb_grp - 1)) { +		g_start = 0; +		g_end = nb_master; +	} else { +		size_t nb_slaves = auto_slave_count(container); +		size_t grp_sz = nb_slaves / nb_slave_grp; +		size_t remainder = nb_slaves % nb_slave_grp; +		size_t g0 = master_first && container->nb_master ? 1 : 0; +		size_t g1 = g0 + nb_slave_grp - remainder; +		if (group_index < g1) { +			g_start = container->nb_master + (group_index - g0) * grp_sz; +			g_end = g_start + grp_sz; +		} else { +			size_t g2 = group_index - g1; +			g_start = container->nb_master +				+ (nb_slave_grp - remainder) * grp_sz +				+ g2 * (grp_sz + 1); +			g_end = g_start + grp_sz + 1; +		} +	} +	if (start) { +		*start = g_start; +	} +	if (end) { +		*end = g_end; +	} +	return true; +} diff --git a/sway/tree/output.c b/sway/tree/output.c new file mode 100644 index 00000000..c0f29c5a --- /dev/null +++ b/sway/tree/output.c @@ -0,0 +1,277 @@ +#include <strings.h> +#include <ctype.h> +#include <stdlib.h> +#include "sway/output.h" +#include "log.h" +#include "list.h" + +void output_get_scaled_size(wlc_handle handle, struct wlc_size *size) { +	*size = *wlc_output_get_resolution(handle); +	uint32_t scale = wlc_output_get_scale(handle); +	size->w /= scale; +	size->h /= scale; +} + +swayc_t *output_by_name(const char* name, const struct wlc_point *abs_pos) { +	swayc_t *output = NULL; +	// If there is no output directly next to the current one, use +	// swayc_opposite_output to wrap. +	if (strcasecmp(name, "left") == 0) { +		output = swayc_adjacent_output(NULL, MOVE_LEFT, abs_pos, true); +		if (!output) { +			output = swayc_opposite_output(MOVE_RIGHT, abs_pos); +		} +	} else if (strcasecmp(name, "right") == 0) { +		output = swayc_adjacent_output(NULL, MOVE_RIGHT, abs_pos, true); +		if (!output) { +			output = swayc_opposite_output(MOVE_LEFT, abs_pos); +		} +	} else if (strcasecmp(name, "up") == 0) { +		output = swayc_adjacent_output(NULL, MOVE_UP, abs_pos, true); +		if (!output) { +			output = swayc_opposite_output(MOVE_DOWN, abs_pos); +		} +	} else if (strcasecmp(name, "down") == 0) { +		output = swayc_adjacent_output(NULL, MOVE_DOWN, abs_pos, true); +		if (!output) { +			output = swayc_opposite_output(MOVE_UP, abs_pos); +		} +	} else { +		for(int i = 0; i < root_container.children->length; ++i) { +			swayc_t *c = root_container.children->items[i]; +			if (c->type == C_OUTPUT && strcasecmp(c->name, name) == 0) { +				return c; +			} +		} +	} +	return output; +} + +swayc_t *swayc_opposite_output(enum movement_direction dir, +		const struct wlc_point *abs_pos) { + +	// Search through all the outputs and pick the output whose edge covers the +	// given position, and is at leftmost/rightmost/upmost/downmost side of the +	// screen (decided by the direction given). +	swayc_t *opposite = NULL; +	char *dir_text = NULL; +	switch(dir) { +		case MOVE_LEFT: +		case MOVE_RIGHT: ; +			for (int i = 0; i < root_container.children->length; ++i) { +				swayc_t *c = root_container.children->items[i]; +				if (abs_pos->y >= c->y && abs_pos->y <= c->y + c->height) { +					if (!opposite) { +						opposite = c; +					} else if ((dir == MOVE_LEFT && c->x < opposite->x) +							|| (dir == MOVE_RIGHT && c->x > opposite->x)) { +						opposite = c; +					} +				} +			} +			dir_text = dir == MOVE_LEFT ? "leftmost" : "rightmost"; +			break; +		case MOVE_UP: +		case MOVE_DOWN: ; +			for (int i = 0; i < root_container.children->length; ++i) { +				swayc_t *c = root_container.children->items[i]; +				if (abs_pos->x >= c->x && abs_pos->x <= c->x + c->width) { +					if (!opposite) { +						opposite = c; +					} else if ((dir == MOVE_UP && c->y < opposite->y) +							|| (dir == MOVE_DOWN && c->y > opposite->y)) { +						opposite = c; +					} +				} +			} +			dir_text = dir == MOVE_UP ? "upmost" : "downmost"; +			break; +		default: +			sway_abort("Function called with invalid argument."); +			break; +	} +	if (opposite) { +		sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s from y-position %i", +				opposite->name, opposite->width, opposite->height, opposite->x, opposite->y, +				dir_text, abs_pos->y); +	} +	return opposite; +} + +// Position is where on the edge (as absolute position) the adjacent output should be searched for. +swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir, +		const struct wlc_point *abs_pos, bool pick_closest) { + +	if (!output) { +		output = swayc_active_output(); +	} +	// In order to find adjacent outputs we need to test that the outputs are +	// aligned on one axis (decided by the direction given) and that the given +	// position is within the edge of the adjacent output. If no such output +	// exists we pick the adjacent output within the edge that is closest to +	// the given position, if any. +	swayc_t *adjacent = NULL; +	char *dir_text = NULL; +	switch(dir) { +		case MOVE_LEFT: +		case MOVE_RIGHT: ; +			double delta_y = 0; +			for(int i = 0; i < root_container.children->length; ++i) { +				swayc_t *c = root_container.children->items[i]; +				if (c == output || c->type != C_OUTPUT) { +					continue; +				} +				bool x_aligned = dir == MOVE_LEFT ? +					c->x + c->width == output->x : +					c->x == output->x + output->width; +				if (!x_aligned) { +					continue; +				} +				if (abs_pos->y >= c->y && abs_pos->y <= c->y + c->height) { +					delta_y = 0; +					adjacent = c; +					break; +				} else if (pick_closest) { +					// track closest adjacent output +					double top_y = c->y, bottom_y = c->y + c->height; +					if (top_y >= output->y && top_y <= output->y + output->height) { +						double delta = top_y - abs_pos->y; +						if (delta < 0) delta = -delta; +						if (delta < delta_y || !adjacent) { +							delta_y = delta; +							adjacent = c; +						} +					} +					// we check both points and pick the closest +					if (bottom_y >= output->y && bottom_y <= output->y + output->height) { +						double delta = bottom_y - abs_pos->y; +						if (delta < 0) delta = -delta; +						if (delta < delta_y || !adjacent) { +							delta_y = delta; +							adjacent = c; +						} +					} +				} +			} +			dir_text = dir == MOVE_LEFT ? "left of" : "right of"; +			if (adjacent && delta_y == 0) { +				sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i)", +						adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y, +						dir_text, output->name, abs_pos->y); +			} else if (adjacent) { +				// so we end up picking the closest adjacent output because +				// there is no directly adjacent to the given position +				sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i, delta: %.0f)", +					adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y, +					dir_text, output->name, abs_pos->y, delta_y); +			} +			break; +		case MOVE_UP: +		case MOVE_DOWN: ; +			double delta_x = 0; +			for(int i = 0; i < root_container.children->length; ++i) { +				swayc_t *c = root_container.children->items[i]; +				if (c == output || c->type != C_OUTPUT) { +					continue; +				} +				bool y_aligned = dir == MOVE_UP ? +					c->y + c->height == output->y : +					c->y == output->y + output->height; +				if (!y_aligned) { +					continue; +				} +				if (abs_pos->x >= c->x && abs_pos->x <= c->x + c->width) { +					delta_x = 0; +					adjacent = c; +					break; +				} else if (pick_closest) { +					// track closest adjacent output +					double left_x = c->x, right_x = c->x + c->width; +					if (left_x >= output->x && left_x <= output->x + output->width) { +						double delta = left_x - abs_pos->x; +						if (delta < 0) delta = -delta; +						if (delta < delta_x || !adjacent) { +							delta_x = delta; +							adjacent = c; +						} +					} +					// we check both points and pick the closest +					if (right_x >= output->x && right_x <= output->x + output->width) { +						double delta = right_x - abs_pos->x; +						if (delta < 0) delta = -delta; +						if (delta < delta_x || !adjacent) { +							delta_x = delta; +							adjacent = c; +						} +					} +				} +			} +			dir_text = dir == MOVE_UP ? "above" : "below"; +			if (adjacent && delta_x == 0) { +				sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i)", +						adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y, +						dir_text, output->name, abs_pos->x); +			} else if (adjacent) { +				// so we end up picking the closest adjacent output because +				// there is no directly adjacent to the given position +				sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i, delta: %.0f)", +					adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y, +					dir_text, output->name, abs_pos->x, delta_x); +			} +			break; +		default: +			sway_abort("Function called with invalid argument."); +			break; +	} +	return adjacent; +} + +void get_absolute_position(swayc_t *container, struct wlc_point *point) { +	if (!container || !point) +		sway_abort("Need container and wlc_point (was %p, %p).", container, point); + +	if (container->type == C_OUTPUT) { +		// Coordinates are already absolute. +		point->x = container->x; +		point->y = container->y; +	} else { +		swayc_t *output = swayc_parent_by_type(container, C_OUTPUT); +		if (container->type == C_WORKSPACE) { +			// Workspace coordinates are actually wrong/arbitrary, but should +			// be same as output. +			point->x = output->x; +			point->y = output->y; +		} else { +			point->x = output->x + container->x; +			point->y = output->y + container->y; +		} +	} +} + +void get_absolute_center_position(swayc_t *container, struct wlc_point *point) { +	get_absolute_position(container, point); +	point->x += container->width/2; +	point->y += container->height/2; +} + +static int sort_workspace_cmp_qsort(const void *_a, const void *_b) { +	swayc_t *a = *(void **)_a; +	swayc_t *b = *(void **)_b; +	int retval = 0; + +	if (isdigit(a->name[0]) && isdigit(b->name[0])) { +		int a_num = strtol(a->name, NULL, 10); +		int b_num = strtol(b->name, NULL, 10); +		retval = (a_num < b_num) ? -1 : (a_num > b_num); +	} else if (isdigit(a->name[0])) { +		retval = -1; +	} else if (isdigit(b->name[0])) { +		retval = 1; +	} + +	return retval; +} + +void sort_workspaces(swayc_t *output) { +	list_stable_sort(output->children, sort_workspace_cmp_qsort); +} diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c new file mode 100644 index 00000000..14cde146 --- /dev/null +++ b/sway/tree/workspace.c @@ -0,0 +1,373 @@ +#define _XOPEN_SOURCE 500 +#include <stdlib.h> +#include <stdbool.h> +#include <limits.h> +#include <ctype.h> +#include <wlc/wlc.h> +#include <string.h> +#include <strings.h> +#include <sys/types.h> +#include "sway/ipc-server.h" +#include "sway/workspace.h" +#include "sway/layout.h" +#include "sway/container.h" +#include "sway/handlers.h" +#include "sway/config.h" +#include "sway/focus.h" +#include "stringop.h" +#include "util.h" +#include "list.h" +#include "log.h" +#include "ipc.h" + +char *prev_workspace_name = NULL; +struct workspace_by_number_data { +	int len; +	const char *cset; +	const char *name; +}; + +static bool workspace_valid_on_output(const char *output_name, const char *ws_name) { +	int i; +	for (i = 0; i < config->workspace_outputs->length; ++i) { +		struct workspace_output *wso = config->workspace_outputs->items[i]; +		if (strcasecmp(wso->workspace, ws_name) == 0) { +			if (strcasecmp(wso->output, output_name) != 0) { +				return false; +			} +		} +	} + +	return true; +} + +char *workspace_next_name(const char *output_name) { +	sway_log(L_DEBUG, "Workspace: Generating new workspace name for output %s", output_name); +	int i; +	int l = 1; +	// Scan all workspace bindings to find the next available workspace name, +	// if none are found/available then default to a number +	struct sway_mode *mode = config->current_mode; + +	int order = INT_MAX; +	char *target = NULL; +	for (i = 0; i < mode->bindings->length; ++i) { +		struct sway_binding *binding = mode->bindings->items[i]; +		char *cmdlist = strdup(binding->command); +		char *dup = cmdlist; +		char *name = NULL; + +		// workspace n +		char *cmd = argsep(&cmdlist, " "); +		if (cmdlist) { +			name = argsep(&cmdlist, ",;"); +		} + +		if (strcmp("workspace", cmd) == 0 && name) { +			sway_log(L_DEBUG, "Got valid workspace command for target: '%s'", name); +			char *_target = strdup(name); +			strip_quotes(_target); +			while (isspace(*_target)) +				_target++; + +			// Make sure that the command references an actual workspace +			// not a command about workspaces +			if (strcmp(_target, "next") == 0 || +				strcmp(_target, "prev") == 0 || +				strcmp(_target, "next_on_output") == 0 || +				strcmp(_target, "prev_on_output") == 0 || +				strcmp(_target, "number") == 0 || +				strcmp(_target, "back_and_forth") == 0 || +				strcmp(_target, "current") == 0) +			{ +				free(_target); +				free(dup); +				continue; +			} + +			// Make sure that the workspace doesn't already exist +			if (workspace_by_name(_target)) { +				free(_target); +				free(dup); +				continue; +			} + +			// make sure that the workspace can appear on the given +			// output +			if (!workspace_valid_on_output(output_name, _target)) { +				free(_target); +				free(dup); +				continue; +			} + +			if (binding->order < order) { +				order = binding->order; +				free(target); +				target = _target; +				sway_log(L_DEBUG, "Workspace: Found free name %s", _target); +			} +		} +		free(dup); +	} +	if (target != NULL) { +		return target; +	} +	// As a fall back, get the current number of active workspaces +	// and return that + 1 for the next workspace's name +	int ws_num = root_container.children->length; +	if (ws_num >= 10) { +		l = 2; +	} else if (ws_num >= 100) { +		l = 3; +	} +	char *name = malloc(l + 1); +	if (!name) { +		sway_log(L_ERROR, "Could not allocate workspace name"); +		return NULL; +	} +	sprintf(name, "%d", ws_num++); +	return name; +} + +swayc_t *workspace_create(const char* name) { +	swayc_t *parent; +	// Search for workspace<->output pair +	int i, e = config->workspace_outputs->length; +	for (i = 0; i < e; ++i) { +		struct workspace_output *wso = config->workspace_outputs->items[i]; +		if (strcasecmp(wso->workspace, name) == 0) +		{ +			// Find output to use if it exists +			e = root_container.children->length; +			for (i = 0; i < e; ++i) { +				parent = root_container.children->items[i]; +				if (strcmp(parent->name, wso->output) == 0) { +					return new_workspace(parent, name); +				} +			} +			break; +		} +	} +	// Otherwise create a new one +	parent = get_focused_container(&root_container); +	parent = swayc_parent_by_type(parent, C_OUTPUT); +	return new_workspace(parent, name); +} + +static bool _workspace_by_name(swayc_t *view, void *data) { +	return (view->type == C_WORKSPACE) && +		   (strcasecmp(view->name, (char *) data) == 0); +} + +swayc_t *workspace_by_name(const char* name) { +	if (strcmp(name, "prev") == 0) { +		return workspace_prev(); +	} +	else if (strcmp(name, "prev_on_output") == 0) { +		return workspace_output_prev(); +	} +	else if (strcmp(name, "next") == 0) { +		return workspace_next(); +	} +	else if (strcmp(name, "next_on_output") == 0) { +		return workspace_output_next(); +	} +	else if (strcmp(name, "current") == 0) { +		return swayc_active_workspace(); +	} +	else { +		return swayc_by_test(&root_container, _workspace_by_name, (void *) name); +	} +} + +static bool _workspace_by_number(swayc_t *view, void *data) { +	if (view->type != C_WORKSPACE) { +		return false; +	} +	struct workspace_by_number_data *wbnd = data; +	int a = strspn(view->name, wbnd->cset); +	return a == wbnd->len && strncmp(view->name, wbnd->name, a) == 0; +} +swayc_t *workspace_by_number(const char* name) { +	struct workspace_by_number_data wbnd = {0, "1234567890", name}; +	wbnd.len = strspn(name, wbnd.cset); +	if (wbnd.len <= 0) { +		return NULL; +	} +	return swayc_by_test(&root_container, _workspace_by_number, (void *) &wbnd); +} + +/** + * Get the previous or next workspace on the specified output. + * Wraps around at the end and beginning. + * If next is false, the previous workspace is returned, otherwise the next one is returned. + */ +swayc_t *workspace_output_prev_next_impl(swayc_t *output, bool next) { +	if (!sway_assert(output->type == C_OUTPUT, "Argument must be an output, is %d", output->type)) { +		return NULL; +	} + +	int i; +	for (i = 0; i < output->children->length; i++) { +		if (output->children->items[i] == output->focused) { +			return output->children->items[wrap(i + (next ? 1 : -1), output->children->length)]; +		} +	} + +	// Doesn't happen, at worst the for loop returns the previously active workspace +	return NULL; +} + +/** + * Get the previous or next workspace. If the first/last workspace on an output is active, + * proceed to the previous/next output's previous/next workspace. + * If next is false, the previous workspace is returned, otherwise the next one is returned. + */ +swayc_t *workspace_prev_next_impl(swayc_t *workspace, bool next) { +	if (!sway_assert(workspace->type == C_WORKSPACE, "Argument must be a workspace, is %d", workspace->type)) { +		return NULL; +	} + +	swayc_t *current_output = workspace->parent; +	int offset = next ? 1 : -1; +	int start = next ? 0 : 1; +	int end = next ? (current_output->children->length) - 1 : current_output->children->length; +	int i; +	for (i = start; i < end; i++) { +		if (current_output->children->items[i] == workspace) { +			return current_output->children->items[i + offset]; +		} +	} + +	// Given workspace is the first/last on the output, jump to the previous/next output +	int num_outputs = root_container.children->length; +	for (i = 0; i < num_outputs; i++) { +		if (root_container.children->items[i] == current_output) { +			swayc_t *next_output = root_container.children->items[wrap(i + offset, num_outputs)]; +			return workspace_output_prev_next_impl(next_output, next); +		} +	} + +	// Doesn't happen, at worst the for loop returns the previously active workspace on the active output +	return NULL; +} + +swayc_t *workspace_output_next() { +	return workspace_output_prev_next_impl(swayc_active_output(), true); +} + +swayc_t *workspace_next() { +	return workspace_prev_next_impl(swayc_active_workspace(), true); +} + +swayc_t *workspace_output_prev() { +	return workspace_output_prev_next_impl(swayc_active_output(), false); +} + +swayc_t *workspace_prev() { +	return workspace_prev_next_impl(swayc_active_workspace(), false); +} + +bool workspace_switch(swayc_t *workspace) { +	if (!workspace) { +		return false; +	} +	swayc_t *active_ws = swayc_active_workspace(); +	if (config->auto_back_and_forth && active_ws == workspace && prev_workspace_name) { +		swayc_t *new_ws = workspace_by_name(prev_workspace_name); +		workspace = new_ws ? new_ws : workspace_create(prev_workspace_name); +	} + +	if (!prev_workspace_name +			|| (strcmp(prev_workspace_name, active_ws->name) +				&& active_ws != workspace)) { +		free(prev_workspace_name); +		prev_workspace_name = malloc(strlen(active_ws->name) + 1); +		if (!prev_workspace_name) { +			sway_log(L_ERROR, "Unable to allocate previous workspace name"); +			return false; +		} +		strcpy(prev_workspace_name, active_ws->name); +	} + +	// move sticky containers +	if (swayc_parent_by_type(active_ws, C_OUTPUT) == swayc_parent_by_type(workspace, C_OUTPUT)) { +		// don't change list while traversing it, use intermediate list instead +		list_t *stickies = create_list(); +		for (int i = 0; i < active_ws->floating->length; i++) { +			swayc_t *cont = active_ws->floating->items[i]; +			if (cont->sticky) { +				list_add(stickies, cont); +			} +		} +		for (int i = 0; i < stickies->length; i++) { +			swayc_t *cont = stickies->items[i]; +			sway_log(L_DEBUG, "Moving sticky container %p to %p:%s", +					cont, workspace, workspace->name); +			swayc_t *parent = remove_child(cont); +			add_floating(workspace, cont); +			// Destroy old container if we need to +			destroy_container(parent); +		} +		list_free(stickies); +	} +	sway_log(L_DEBUG, "Switching to workspace %p:%s", workspace, workspace->name); +	if (!set_focused_container(get_focused_view(workspace))) { +		return false; +	} +	swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT); +	arrange_backgrounds(); +	arrange_windows(output, -1, -1); +	return true; +} + +swayc_t *workspace_for_pid(pid_t pid) { +	int i; +	swayc_t *ws = NULL; +	struct pid_workspace *pw = NULL; + +	sway_log(L_DEBUG, "looking for workspace for pid %d", pid); + +	// leaving this here as it's useful for debugging +	// sway_log(L_DEBUG, "all pid_workspaces"); +	// for (int k = 0; k < config->pid_workspaces->length; k++) { +	// 	pw = config->pid_workspaces->items[k]; +	// 	sway_log(L_DEBUG, "pid %d workspace %s time_added %li", *pw->pid, pw->workspace, *pw->time_added); +	// } + +	do { +		for (i = 0; i < config->pid_workspaces->length; i++) { +			pw = config->pid_workspaces->items[i]; +			pid_t *pw_pid = pw->pid; + +			if (pid == *pw_pid) { +				sway_log(L_DEBUG, "found pid_workspace for pid %d, workspace %s", pid, pw->workspace); +				break; // out of for loop +			} + +			pw = NULL; +		} + +		if (pw) { +			break; // out of do-while loop +		} + +		pid = get_parent_pid(pid); +		// no sense in looking for matches for pid 0. +		// also, if pid == getpid(), that is the compositor's +		// pid, which definitely isn't helpful +	} while (pid > 0 && pid != getpid()); + +	if (pw) { +		ws = workspace_by_name(pw->workspace); + +		if (!ws) { +			sway_log(L_DEBUG, "Creating workspace %s for pid %d because it disappeared", pw->workspace, pid); +			ws = workspace_create(pw->workspace); +		} + +		list_del(config->pid_workspaces, i); +	} + +	return ws; +} | 
