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; +} |