X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fworkspace.c;h=2af88d73de1f915d67717c530a9331393c1e3274;hb=e2d6117b8c2cef91c5209eac3e5b0721b3522eb0;hp=5a0913bfcc77852c1084fe2f2908097000714b8c;hpb=4baf944ef0d930bde3493767b1d3e9e5abf4731b;p=i3%2Fi3 diff --git a/src/workspace.c b/src/workspace.c index 5a0913bf..2af88d73 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -1,10 +1,8 @@ -#undef I3__FILE__ -#define I3__FILE__ "workspace.c" /* * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * workspace.c: Modifying workspaces, accessing them, moving containers to * workspaces. @@ -13,11 +11,44 @@ #include "all.h" #include "yajl_utils.h" -#include +/* + * Stores a copy of the name of the last used workspace for the workspace + * back-and-forth switching. + * + */ +char *previous_workspace_name = NULL; + +/* NULL-terminated list of workspace names (in order) extracted from + * keybindings. */ +static char **binding_workspace_names = NULL; + +/* + * Returns the workspace with the given name or NULL if such a workspace does + * not exist. + * + */ +Con *get_existing_workspace_by_name(const char *name) { + Con *output, *workspace = NULL; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, name)); + } + + return workspace; +} + +/* + * Returns the workspace with the given number or NULL if such a workspace does + * not exist. + * + */ +Con *get_existing_workspace_by_num(int num) { + Con *output, *workspace = NULL; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + GREP_FIRST(workspace, output_get_content(output), child->num == num); + } -/* Stores a copy of the name of the last used workspace for the workspace - * back-and-forth switching. */ -static char *previous_workspace_name = NULL; + return workspace; +} /* * Sets ws->layout to splith/splitv if default_orientation was specified in the @@ -31,6 +62,7 @@ static void _workspace_apply_default_orientation(Con *ws) { if (config.default_orientation == NO_ORIENTATION) { Con *output = con_get_output(ws); ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH; + ws->rect = output->rect; DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n", output->rect.width, output->rect.height, ws->layout); } else { @@ -38,6 +70,52 @@ static void _workspace_apply_default_orientation(Con *ws) { } } +/* + * Returns the first output that is assigned to a workspace specified by the + * given name or number or NULL if no such output exists. If there is a + * workspace with a matching name and another workspace with a matching number, + * the output assigned to the first one is returned. + * The order of the 'ws_assignments' queue is respected: if multiple assignments + * match the specified workspace, the first one is returned. + * If 'name' is NULL it will be ignored. + * If 'parsed_num' is -1 it will be ignored. + * + */ +static Con *get_assigned_output(const char *name, long parsed_num) { + Con *output = NULL; + struct Workspace_Assignment *assignment; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (name && strcmp(assignment->name, name) == 0) { + DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output); + Output *assigned_by_name = get_output_by_name(assignment->output, true); + if (assigned_by_name) { + /* When the name matches exactly, skip numbered assignments. */ + return assigned_by_name->con; + } + } else if (!output && /* Only keep the first numbered assignment. */ + parsed_num != -1 && + name_is_digits(assignment->name) && + ws_name_to_number(assignment->name) == parsed_num) { + DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output); + Output *assigned_by_num = get_output_by_name(assignment->output, true); + if (assigned_by_num) { + output = assigned_by_num->con; + } + } + } + + return output; +} + +/* + * Returns true if the first output assigned to a workspace with the given + * workspace assignment is the same as the given output. + */ +bool output_triggers_assignment(Output *output, struct Workspace_Assignment *assignment) { + Con *assigned = get_assigned_output(assignment->name, -1); + return assigned && assigned == output->con; +} + /* * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of @@ -45,25 +123,22 @@ static void _workspace_apply_default_orientation(Con *ws) { * */ Con *workspace_get(const char *num, bool *created) { - Con *output, *workspace = NULL; - - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num)); + Con *workspace = get_existing_workspace_by_name(num); if (workspace == NULL) { LOG("Creating new workspace \"%s\"\n", num); - /* unless an assignment is found, we will create this workspace on the current output */ - output = con_get_output(focused); - /* look for assignments */ - struct Workspace_Assignment *assignment; - TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (strcmp(assignment->name, num) != 0) - continue; - LOG("Found workspace assignment to output \"%s\"\n", assignment->output); - GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); - break; + /* We set workspace->num to the number if this workspace’s name begins + * with a positive number. Otherwise it’s a named ws and num will be + * -1. */ + long parsed_num = ws_name_to_number(num); + + Con *output = get_assigned_output(num, parsed_num); + /* if an assignment is not found, we create this workspace on the current output */ + if (!output) { + output = con_get_output(focused); } + Con *content = output_get_content(output); LOG("got output %p with content %p\n", output, content); /* We need to attach this container after setting its type. con_attach @@ -77,16 +152,7 @@ Con *workspace_get(const char *num, bool *created) { FREE(workspace->name); workspace->name = sstrdup(num); workspace->workspace_layout = config.default_layout; - /* We set ->num to the number if this workspace’s name begins with a - * positive number. Otherwise it’s a named ws and num will be -1. */ - char *endptr = NULL; - long parsed_num = strtol(num, &endptr, 10); - if (parsed_num == LONG_MIN || - parsed_num == LONG_MAX || - parsed_num < 0 || - endptr == num) - workspace->num = -1; - else workspace->num = parsed_num; + workspace->num = parsed_num; LOG("num = %d\n", workspace->num); workspace->parent = content; @@ -94,11 +160,14 @@ Con *workspace_get(const char *num, bool *created) { con_attach(workspace, content, false); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); + ipc_send_workspace_event("init", workspace, NULL); + ewmh_update_number_of_desktops(); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); if (created != NULL) *created = true; - } - else if (created != NULL) { + } else if (created != NULL) { *created = false; } @@ -106,28 +175,30 @@ Con *workspace_get(const char *num, bool *created) { } /* - * Returns a pointer to a new workspace in the given output. The workspace - * is created attached to the tree hierarchy through the given content - * container. + * Extracts workspace names from keybindings (e.g. “web” from “bindsym $mod+1 + * workspace web”), so that when an output needs a workspace, i3 can start with + * the first configured one. Needs to be called before reorder_bindings() so + * that the config-file order is used, not the i3-internal order. * */ -Con *create_workspace_on_output(Output *output, Con *content) { - /* add a workspace to this output */ - Con *out, *current; - char *name; - bool exists = true; - Con *ws = con_new(NULL, NULL); - ws->type = CT_WORKSPACE; - - /* try the configured workspace bindings first to find a free name */ +void extract_workspace_names_from_bindings(void) { Binding *bind; + int n = 0; + if (binding_workspace_names != NULL) { + for (int i = 0; binding_workspace_names[i] != NULL; i++) { + free(binding_workspace_names[i]); + } + FREE(binding_workspace_names); + } TAILQ_FOREACH(bind, bindings, bindings) { DLOG("binding with command %s\n", bind->command); if (strlen(bind->command) < strlen("workspace ") || strncasecmp(bind->command, "workspace", strlen("workspace")) != 0) continue; DLOG("relevant command = %s\n", bind->command); - char *target = bind->command + strlen("workspace "); + const char *target = bind->command + strlen("workspace "); + while (*target == ' ' || *target == '\t') + target++; /* We check if this is the workspace * next/prev/next_on_output/prev_on_output/back_and_forth/number command. * Beware: The workspace names "next", "prev", "next_on_output", @@ -141,47 +212,53 @@ Con *create_workspace_on_output(Output *output, Con *content) { strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 || strncasecmp(target, "current", strlen("current")) == 0) continue; - if (*target == '"') - target++; - FREE(ws->name); - ws->name = strdup(target); - if (ws->name[strlen(ws->name)-1] == '"') - ws->name[strlen(ws->name)-1] = '\0'; - DLOG("trying name *%s*\n", ws->name); + char *target_name = parse_string(&target, false); + if (target_name == NULL) + continue; + if (strncasecmp(target_name, "__", strlen("__")) == 0) { + LOG("Cannot create workspace \"%s\". Names starting with __ are i3-internal.\n", target); + free(target_name); + continue; + } + DLOG("Saving workspace name \"%s\"\n", target_name); + binding_workspace_names = srealloc(binding_workspace_names, ++n * sizeof(char *)); + binding_workspace_names[n - 1] = target_name; + } + binding_workspace_names = srealloc(binding_workspace_names, ++n * sizeof(char *)); + binding_workspace_names[n - 1] = NULL; +} + +/* + * Returns a pointer to a new workspace in the given output. The workspace + * is created attached to the tree hierarchy through the given content + * container. + * + */ +Con *create_workspace_on_output(Output *output, Con *content) { + /* add a workspace to this output */ + char *name; + bool exists = true; + Con *ws = con_new(NULL, NULL); + ws->type = CT_WORKSPACE; + + /* try the configured workspace bindings first to find a free name */ + for (int n = 0; binding_workspace_names[n] != NULL; n++) { + char *target_name = binding_workspace_names[n]; /* Ensure that this workspace is not assigned to a different output — * otherwise we would create it, then move it over to its output, then * find a new workspace, etc… */ - bool assigned = false; - struct Workspace_Assignment *assignment; - TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (strcmp(assignment->name, ws->name) != 0 || - strcmp(assignment->output, output->name) == 0) - continue; - - assigned = true; - break; - } - - if (assigned) + Con *assigned = get_assigned_output(target_name, -1); + if (assigned && assigned != output->con) { continue; + } - current = NULL; - TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); - - exists = (current != NULL); + exists = (get_existing_workspace_by_name(target_name) != NULL); if (!exists) { + ws->name = sstrdup(target_name); /* Set ->num to the number of the workspace, if the name actually * is a number or starts with a number */ - char *endptr = NULL; - long parsed_num = strtol(ws->name, &endptr, 10); - if (parsed_num == LONG_MIN || - parsed_num == LONG_MAX || - parsed_num < 0 || - endptr == ws->name) - ws->num = -1; - else ws->num = parsed_num; + ws->num = ws_name_to_number(ws->name); LOG("Used number %d for workspace with name %s\n", ws->num, ws->name); break; @@ -194,18 +271,12 @@ Con *create_workspace_on_output(Output *output, Con *content) { int c = 0; while (exists) { c++; - - FREE(ws->name); - sasprintf(&(ws->name), "%d", c); - - current = NULL; - TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); - exists = (current != NULL); - - DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists); + Con *assigned = get_assigned_output(NULL, c); + exists = (get_existing_workspace_by_num(c) || (assigned && assigned != output->con)); + DLOG("result for ws %d: exists = %d\n", c, exists); } ws->num = c; + sasprintf(&(ws->name), "%d", c); } con_attach(ws, content, false); @@ -221,7 +292,6 @@ Con *create_workspace_on_output(Output *output, Con *content) { return ws; } - /* * Returns true if the workspace is currently visible. Especially important for * multi-monitor environments, as they can have multiple currenlty active @@ -241,7 +311,7 @@ bool workspace_is_visible(Con *ws) { * XXX: we need to clean up all this recursive walking code. * */ -Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) { +static Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) { Con *current; TAILQ_FOREACH(current, &(con->nodes_head), nodes) { @@ -312,62 +382,36 @@ static void workspace_reassign_sticky(Con *con) { } TAILQ_FOREACH(current, &(con->floating_head), floating_windows) - workspace_reassign_sticky(current); + workspace_reassign_sticky(current); } /* * Callback to reset the urgent flag of the given con to false. May be started by - * _workspace_show to avoid urgency hints being lost by switching to a workspace + * workspace_show to avoid urgency hints being lost by switching to a workspace * focusing the con. * */ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) { Con *con = w->data; - DLOG("Resetting urgency flag of con %p by timer\n", con); - con->urgent = false; - con_update_parents_urgency(con); - workspace_update_urgent_flag(con_get_workspace(con)); - tree_render(); - ev_timer_stop(main_loop, con->urgency_timer); FREE(con->urgency_timer); + + if (con->urgent) { + DLOG("Resetting urgency flag of con %p by timer\n", con); + con_set_urgency(con, false); + con_update_parents_urgency(con); + workspace_update_urgent_flag(con_get_workspace(con)); + ipc_send_window_event("urgent", con); + tree_render(); + } } /* - * For the "focus" event we send, along the usual "change" field, also the - * current and previous workspace, in "current" and "old" respectively. + * Switches to the given workspace + * */ -static void ipc_send_workspace_focus_event(Con *current, Con *old) { - setlocale(LC_NUMERIC, "C"); - yajl_gen gen = ygenalloc(); - - y(map_open); - - ystr("change"); - ystr("focus"); - - ystr("current"); - dump_node(gen, current, false); - - ystr("old"); - if (old == NULL) - y(null); - else - dump_node(gen, old, false); - - y(map_close); - - const unsigned char *payload; - ylength length; - y(get_buf, &payload, &length); - - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); - y(free); - setlocale(LC_NUMERIC, ""); -} - -static void _workspace_show(Con *workspace) { +void workspace_show(Con *workspace) { Con *current, *old = NULL; /* safe-guard against showing i3-internal workspaces like __i3_scratch */ @@ -391,6 +435,13 @@ static void _workspace_show(Con *workspace) { return; } + /* Used to correctly update focus when pushing sticky windows. Holds the + * previously focused container in the same output as workspace. For + * example, if a sticky window is focused and then we switch focus to a + * workspace in another output and then switch to a third workspace in the + * first output, the sticky window needs to be refocused. */ + Con *old_focus = old ? con_descend_focused(old) : NULL; + /* Remember currently focused workspace for switching back to it later with * the 'workspace back_and_forth' command. * NOTE: We have to duplicate the name as the original will be freed when @@ -399,10 +450,8 @@ static void _workspace_show(Con *workspace) { * focused) are skipped, see bug #868. */ if (current && !con_is_internal(current)) { FREE(previous_workspace_name); - if (current) { - previous_workspace_name = sstrdup(current->name); - DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name); - } + previous_workspace_name = sstrdup(current->name); + DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name); } workspace_reassign_sticky(workspace); @@ -417,6 +466,7 @@ static void _workspace_show(Con *workspace) { * focus and thereby immediately destroy it */ if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) { /* focus for now… */ + next->urgent = false; con_focus(next); /* … but immediately reset urgency flags; they will be set to false by @@ -427,26 +477,26 @@ static void _workspace_show(Con *workspace) { if (focused->urgency_timer == NULL) { DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n", - focused, workspace); - focused->urgency_timer = scalloc(sizeof(struct ev_timer)); + focused, workspace); + focused->urgency_timer = scalloc(1, sizeof(struct ev_timer)); /* use a repeating timer to allow for easy resets */ ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb, - config.workspace_urgency_timer, config.workspace_urgency_timer); + config.workspace_urgency_timer, config.workspace_urgency_timer); focused->urgency_timer->data = focused; ev_timer_start(main_loop, focused->urgency_timer); } else { DLOG("Resetting urgency timer of con %p on workspace %p\n", - focused, workspace); + focused, workspace); ev_timer_again(main_loop, focused->urgency_timer); } } else con_focus(next); - ipc_send_workspace_focus_event(workspace, old); + ipc_send_workspace_event("focus", workspace, current); DLOG("old = %p / %s\n", old, (old ? old->name : "(null)")); /* Close old workspace if necessary. This must be done *after* doing - * urgency handling, because tree_close() will do a con_focus() on the next + * urgency handling, because tree_close_internal() will do a con_focus() on the next * client, which will clear the urgency flag too early. Also, there is no * way for con_focus() to know about when to clear urgency immediately and * when to defer it. */ @@ -454,8 +504,25 @@ static void _workspace_show(Con *workspace) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); - tree_close(old, DONT_KILL_WINDOW, false, false); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL); + tree_close_internal(old, DONT_KILL_WINDOW, false); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); + + y(free); + + /* Avoid calling output_push_sticky_windows later with a freed container. */ + if (old == old_focus) { + old_focus = NULL; + } + + ewmh_update_number_of_desktops(); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); } } @@ -470,14 +537,9 @@ static void _workspace_show(Con *workspace) { /* Update the EWMH hints */ ewmh_update_current_desktop(); -} -/* - * Switches to the given workspace - * - */ -void workspace_show(Con *workspace) { - _workspace_show(workspace); + /* Push any sticky windows to the now visible workspace. */ + output_push_sticky_windows(old_focus); } /* @@ -487,43 +549,22 @@ void workspace_show(Con *workspace) { void workspace_show_by_name(const char *num) { Con *workspace; workspace = workspace_get(num, NULL); - _workspace_show(workspace); + workspace_show(workspace); } /* * Focuses the next workspace. * */ -Con* workspace_next(void) { +Con *workspace_next(void) { Con *current = con_get_workspace(focused); - Con *next = NULL; + Con *next = NULL, *first = NULL, *first_opposite = NULL; Con *output; if (current->num == -1) { /* If currently a named workspace, find next named workspace. */ - next = TAILQ_NEXT(current, nodes); - } else { - /* If currently a numbered workspace, find next numbered workspace. */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (child->num == -1) - break; - /* Need to check child against current and next because we are - * traversing multiple lists and thus are not guaranteed the - * relative order between the list of workspaces. */ - if (current->num < child->num && (!next || child->num < next->num)) - next = child; - } - } - } - - /* Find next named workspace. */ - if (!next) { + if ((next = TAILQ_NEXT(current, nodes)) != NULL) + return next; bool found_current = false; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ @@ -532,18 +573,20 @@ Con* workspace_next(void) { NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; + if (!first) + first = child; + if (!first_opposite || (child->num != -1 && child->num < first_opposite->num)) + first_opposite = child; if (child == current) { - found_current = 1; - } else if (child->num == -1 && (current->num != -1 || found_current)) { + found_current = true; + } else if (child->num == -1 && found_current) { next = child; - goto workspace_next_end; + return next; } } } - } - - /* Find first workspace. */ - if (!next) { + } else { + /* If currently a numbered workspace, find next numbered workspace. */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ if (con_is_internal(output)) @@ -551,12 +594,24 @@ Con* workspace_next(void) { NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) continue; - if (!next || (child->num != -1 && child->num < next->num)) + if (!first || (child->num != -1 && child->num < first->num)) + first = child; + if (!first_opposite && child->num == -1) + first_opposite = child; + if (child->num == -1) + break; + /* Need to check child against current and next because we are + * traversing multiple lists and thus are not guaranteed the + * relative order between the list of workspaces. */ + if (current->num < child->num && (!next || child->num < next->num)) next = child; } } } -workspace_next_end: + + if (!next) + next = first_opposite ? first_opposite : first; + return next; } @@ -564,9 +619,9 @@ workspace_next_end: * Focuses the previous workspace. * */ -Con* workspace_prev(void) { +Con *workspace_prev(void) { Con *current = con_get_workspace(focused); - Con *prev = NULL; + Con *prev = NULL, *first_opposite = NULL, *last = NULL; Con *output; if (current->num == -1) { @@ -574,6 +629,28 @@ Con* workspace_prev(void) { prev = TAILQ_PREV(current, nodes_head, nodes); if (prev && prev->num != -1) prev = NULL; + if (!prev) { + bool found_current = false; + TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { + /* Skip outputs starting with __, they are internal. */ + if (con_is_internal(output)) + continue; + NODES_FOREACH_REVERSE(output_get_content(output)) { + if (child->type != CT_WORKSPACE) + continue; + if (!last) + last = child; + if (!first_opposite || (child->num != -1 && child->num > first_opposite->num)) + first_opposite = child; + if (child == current) { + found_current = true; + } else if (child->num == -1 && found_current) { + prev = child; + return prev; + } + } + } + } } else { /* If numbered workspace, find previous numbered workspace. */ TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { @@ -581,7 +658,13 @@ Con* workspace_prev(void) { if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE || child->num == -1) + if (child->type != CT_WORKSPACE) + continue; + if (!last || (child->num != -1 && last->num < child->num)) + last = child; + if (!first_opposite && child->num == -1) + first_opposite = child; + if (child->num == -1) continue; /* Need to check child against current and previous because we * are traversing multiple lists and thus are not guaranteed @@ -592,54 +675,20 @@ Con* workspace_prev(void) { } } - /* Find previous named workspace. */ - if (!prev) { - bool found_current = false; - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (child == current) { - found_current = true; - } else if (child->num == -1 && (current->num != -1 || found_current)) { - prev = child; - goto workspace_prev_end; - } - } - } - } - - /* Find last workspace. */ - if (!prev) { - TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { - /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) - continue; - NODES_FOREACH_REVERSE(output_get_content(output)) { - if (child->type != CT_WORKSPACE) - continue; - if (!prev || child->num > prev->num) - prev = child; - } - } - } + if (!prev) + prev = first_opposite ? first_opposite : last; -workspace_prev_end: return prev; } - /* * Focuses the next workspace on the same output. * */ -Con* workspace_next_on_output(void) { +Con *workspace_next_on_output(void) { Con *current = con_get_workspace(focused); Con *next = NULL; - Con *output = con_get_output(focused); + Con *output = con_get_output(focused); if (current->num == -1) { /* If currently a named workspace, find next named workspace. */ @@ -656,8 +705,8 @@ Con* workspace_next_on_output(void) { * relative order between the list of workspaces. */ if (current->num < child->num && (!next || child->num < next->num)) next = child; - } } + } /* Find next named workspace. */ if (!next) { @@ -666,7 +715,7 @@ Con* workspace_next_on_output(void) { if (child->type != CT_WORKSPACE) continue; if (child == current) { - found_current = 1; + found_current = true; } else if (child->num == -1 && (current->num != -1 || found_current)) { next = child; goto workspace_next_on_output_end; @@ -691,10 +740,10 @@ workspace_next_on_output_end: * Focuses the previous workspace on same output. * */ -Con* workspace_prev_on_output(void) { +Con *workspace_prev_on_output(void) { Con *current = con_get_workspace(focused); Con *prev = NULL; - Con *output = con_get_output(focused); + Con *output = con_get_output(focused); DLOG("output = %s\n", output->name); if (current->num == -1) { @@ -707,7 +756,7 @@ Con* workspace_prev_on_output(void) { NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE || child->num == -1) continue; - /* Need to check child against current and previous because we + /* Need to check child against current and previous because we * are traversing multiple lists and thus are not guaranteed * the relative order between the list of workspaces. */ if (current->num > child->num && (!prev || child->num > prev->num)) @@ -750,7 +799,7 @@ workspace_prev_on_output_end: */ void workspace_back_and_forth(void) { if (!previous_workspace_name) { - DLOG("No previous workspace name set. Not switching."); + DLOG("No previous workspace name set. Not switching.\n"); return; } @@ -763,7 +812,7 @@ void workspace_back_and_forth(void) { */ Con *workspace_back_and_forth_get(void) { if (!previous_workspace_name) { - DLOG("no previous workspace name set."); + DLOG("No previous workspace name set.\n"); return NULL; } @@ -776,12 +825,12 @@ Con *workspace_back_and_forth_get(void) { static bool get_urgency_flag(Con *con) { Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) - if (child->urgent || get_urgency_flag(child)) - return true; + if (child->urgent || get_urgency_flag(child)) + return true; TAILQ_FOREACH(child, &(con->floating_head), floating_windows) - if (child->urgent || get_urgency_flag(child)) - return true; + if (child->urgent || get_urgency_flag(child)) + return true; return false; } @@ -797,7 +846,7 @@ void workspace_update_urgent_flag(Con *ws) { DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent); if (old_flag != ws->urgent) - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); + ipc_send_workspace_event("urgent", ws, NULL); } /* @@ -813,9 +862,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 2: copy layout from workspace */ split->layout = ws->layout; - Con *old_focused = TAILQ_FIRST(&(ws->focus_head)); - /* 3: move the existing cons of this workspace below the new con */ + Con **focus_order = get_focus_order(ws); + DLOG("Moving cons\n"); while (!TAILQ_EMPTY(&(ws->nodes_head))) { Con *child = TAILQ_FIRST(&(ws->nodes_head)); @@ -823,6 +872,9 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { con_attach(child, split, true); } + set_focus_order(split, focus_order); + free(focus_order); + /* 4: switch workspace layout */ ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout); @@ -833,9 +885,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 6: fix the percentages */ con_fix_percent(ws); - - if (old_focused) - con_focus(old_focused); } /* @@ -868,10 +917,13 @@ Con *workspace_attach_to(Con *ws) { DLOG("Attaching new split %p to workspace %p\n", new, ws); con_attach(new, ws, false); + /* 5: fix the percentages */ + con_fix_percent(ws); + return new; } -/** +/* * Creates a new container and re-parents all of children from the given * workspace into it. * @@ -887,9 +939,10 @@ Con *workspace_encapsulate(Con *ws) { new->parent = ws; new->layout = ws->layout; - DLOG("Moving children of workspace %p / %s into container %p\n", - ws, ws->name, new); + Con **focus_order = get_focus_order(ws); + DLOG("Moving children of workspace %p / %s into container %p\n", + ws, ws->name, new); Con *child; while (!TAILQ_EMPTY(&(ws->nodes_head))) { child = TAILQ_FIRST(&(ws->nodes_head)); @@ -897,7 +950,112 @@ Con *workspace_encapsulate(Con *ws) { con_attach(child, new, true); } + set_focus_order(new, focus_order); + free(focus_order); + con_attach(new, ws, true); return new; } + +/* + * Move the given workspace to the specified output. + * This returns true if and only if moving the workspace was successful. + */ +bool workspace_move_to_output(Con *ws, Output *output) { + LOG("Trying to move workspace %p / %s to output %p / \"%s\".\n", ws, ws->name, output, output_primary_name(output)); + + Output *current_output = get_output_for_con(ws); + if (current_output == NULL) { + ELOG("Cannot get current output. This is a bug in i3.\n"); + return false; + } + + Con *content = output_get_content(output->con); + LOG("got output %p with content %p\n", output, content); + + Con *previously_visible_ws = TAILQ_FIRST(&(content->focus_head)); + if (previously_visible_ws) { + DLOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name); + } else { + DLOG("No previously visible workspace on output.\n"); + } + + bool workspace_was_visible = workspace_is_visible(ws); + if (con_num_children(ws->parent) == 1) { + LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name); + + /* check if we can find a workspace assigned to this output */ + bool used_assignment = false; + struct Workspace_Assignment *assignment; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (!output_triggers_assignment(current_output, assignment)) { + continue; + } + /* check if this workspace is already attached to the tree */ + if (get_existing_workspace_by_name(assignment->name) != NULL) { + continue; + } + + /* so create the workspace referenced to by this assignment */ + LOG("Creating workspace from assignment %s.\n", assignment->name); + workspace_get(assignment->name, NULL); + used_assignment = true; + break; + } + + /* if we couldn't create the workspace using an assignment, create + * it on the output */ + if (!used_assignment) + create_workspace_on_output(current_output, ws->parent); + + /* notify the IPC listeners */ + ipc_send_workspace_event("init", ws, NULL); + } + DLOG("Detaching\n"); + + /* detach from the old output and attach to the new output */ + Con *old_content = ws->parent; + con_detach(ws); + if (workspace_was_visible) { + /* The workspace which we just detached was visible, so focus + * the next one in the focus-stack. */ + Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head)); + LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name); + workspace_show(focus_ws); + } + con_attach(ws, content, false); + + /* fix the coordinates of the floating containers */ + Con *floating_con; + TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows) + floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect)); + + ipc_send_workspace_event("move", ws, NULL); + if (workspace_was_visible) { + /* Focus the moved workspace on the destination output. */ + workspace_show(ws); + } + + if (!previously_visible_ws) { + return true; + } + + /* NB: We cannot simply work with previously_visible_ws since it might + * have been cleaned up by workspace_show() already, depending on the + * focus order/number of other workspaces on the output. + * Instead, we loop through the available workspaces and only work with + * previously_visible_ws if we still find it. */ + TAILQ_FOREACH(ws, &(content->nodes_head), nodes) { + if (ws != previously_visible_ws) + continue; + + /* Call the on_remove_child callback of the workspace which previously + * was visible on the destination output. Since it is no longer + * visible, it might need to get cleaned up. */ + CALL(previously_visible_ws, on_remove_child); + break; + } + + return true; +}