X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fworkspace.c;h=4a16f3d1c7b31d587466bcc4b27cc0570ae80b75;hb=e1554479326b1e9bd280187f7978e3b5b5a98baa;hp=94efd47b6e2455cf0151f7a41b0366d9022aeed8;hpb=9c3ece56d20db392610ae3106d570ee47c5831e8;p=i3%2Fi3 diff --git a/src/workspace.c b/src/workspace.c index 94efd47b..4a16f3d1 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -11,6 +11,7 @@ * */ #include "all.h" +#include "yajl_utils.h" /* Stores a copy of the name of the last used workspace for the workspace * back-and-forth switching. */ @@ -45,7 +46,7 @@ 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)); + GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num)); if (workspace == NULL) { LOG("Creating new workspace \"%s\"\n", num); @@ -53,14 +54,23 @@ Con *workspace_get(const char *num, bool *created) { 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); + + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (strcmp(assignment->name, num) == 0) { + DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output); + GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); + break; + } else if (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); + GREP_FIRST(output, croot, !strcmp(child->name, assignment->output)); + } } + 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 @@ -74,16 +84,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; @@ -91,11 +92,13 @@ 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(); if (created != NULL) *created = true; - } - else if (created != NULL) { + } else if (created != NULL) { *created = false; } @@ -124,7 +127,9 @@ Con *create_workspace_on_output(Output *output, Con *content) { 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 != '\0') + 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", @@ -138,12 +143,16 @@ 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++; + 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; + } FREE(ws->name); - ws->name = strdup(target); - if (ws->name[strlen(ws->name)-1] == '"') - ws->name[strlen(ws->name)-1] = '\0'; + ws->name = target_name; DLOG("trying name *%s*\n", ws->name); /* Ensure that this workspace is not assigned to a different output — @@ -165,20 +174,13 @@ Con *create_workspace_on_output(Output *output, Con *content) { current = NULL; TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); + GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); exists = (current != NULL); if (!exists) { /* 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; @@ -192,17 +194,16 @@ Con *create_workspace_on_output(Output *output, Con *content) { while (exists) { c++; - FREE(ws->name); - sasprintf(&(ws->name), "%d", c); + ws->num = c; current = NULL; TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); + GREP_FIRST(current, output_get_content(out), child->num == ws->num); exists = (current != NULL); - DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists); + DLOG("result for ws %d: exists = %d\n", c, exists); } - ws->num = c; + sasprintf(&(ws->name), "%d", c); } con_attach(ws, content, false); @@ -212,12 +213,12 @@ Con *create_workspace_on_output(Output *output, Con *content) { ws->fullscreen_mode = CF_OUTPUT; + ws->workspace_layout = config.default_layout; _workspace_apply_default_orientation(ws); return ws; } - /* * Returns true if the workspace is currently visible. Especially important for * multi-monitor environments, as they can have multiple currenlty active @@ -308,15 +309,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 + * focusing the con. + * + */ +static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) { + Con *con = w->data; + + if (con->urgent) { + 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)); + ipc_send_window_event("urgent", con); + tree_render(); + } + + ev_timer_stop(main_loop, con->urgency_timer); + FREE(con->urgency_timer); +} static void _workspace_show(Con *workspace) { Con *current, *old = NULL; /* safe-guard against showing i3-internal workspaces like __i3_scratch */ - if (workspace->name[0] == '_' && workspace->name[1] == '_') + if (con_is_internal(workspace)) return; /* disable fullscreen for the other workspaces and get the workspace we are @@ -339,30 +361,83 @@ static void _workspace_show(Con *workspace) { /* 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 - * the corresponding workspace is cleaned up. */ - - FREE(previous_workspace_name); - if (current) - previous_workspace_name = sstrdup(current->name); + * the corresponding workspace is cleaned up. + * NOTE: Internal cons such as __i3_scratch (when a scratchpad window is + * 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); + } + } workspace_reassign_sticky(workspace); - LOG("switching to %p\n", workspace); + DLOG("switching to %p / %s\n", workspace, workspace->name); Con *next = con_descend_focused(workspace); + /* Memorize current output */ + Con *old_output = con_get_output(focused); + + /* Display urgency hint for a while if the newly visible workspace would + * 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 + * the timer callback in case the container is focused at the time of + * its expiration */ + focused->urgent = true; + workspace->urgent = true; + + 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)); + /* 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); + 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); + ev_timer_again(main_loop, focused->urgency_timer); + } + } else + con_focus(next); + + 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 + * 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. */ if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) { /* 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); + yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL); tree_close(old, DONT_KILL_WINDOW, false, false); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); + + y(free); + + ewmh_update_number_of_desktops(); + ewmh_update_desktop_names(); + ewmh_update_desktop_viewport(); } } - /* Memorize current output */ - Con *old_output = con_get_output(focused); - - con_focus(next); workspace->fullscreen_mode = CF_OUTPUT; LOG("focused now = %p / %s\n", focused, focused->name); @@ -374,8 +449,6 @@ static void _workspace_show(Con *workspace) { /* Update the EWMH hints */ ewmh_update_current_desktop(); - - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); } /* @@ -392,8 +465,7 @@ void workspace_show(Con *workspace) { */ void workspace_show_by_name(const char *num) { Con *workspace; - bool changed_num_workspaces; - workspace = workspace_get(num, &changed_num_workspaces); + workspace = workspace_get(num, NULL); _workspace_show(workspace); } @@ -401,7 +473,7 @@ void workspace_show_by_name(const char *num) { * Focuses the next workspace. * */ -Con* workspace_next(void) { +Con *workspace_next(void) { Con *current = con_get_workspace(focused); Con *next = NULL; Con *output; @@ -413,7 +485,7 @@ Con* workspace_next(void) { /* If currently a numbered workspace, find next numbered workspace. */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -434,7 +506,7 @@ Con* workspace_next(void) { bool found_current = false; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -453,7 +525,7 @@ Con* workspace_next(void) { if (!next) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -471,7 +543,7 @@ 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 *output; @@ -485,7 +557,7 @@ Con* workspace_prev(void) { /* If numbered workspace, find previous numbered workspace. */ TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE || child->num == -1) @@ -504,7 +576,7 @@ Con* workspace_prev(void) { bool found_current = false; TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -523,7 +595,7 @@ Con* workspace_prev(void) { if (!prev) { TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -538,15 +610,14 @@ 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. */ @@ -563,8 +634,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) { @@ -598,10 +669,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) { @@ -614,7 +685,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)) @@ -664,15 +735,31 @@ void workspace_back_and_forth(void) { workspace_show_by_name(previous_workspace_name); } +/* + * Returns the previously focused workspace con, or NULL if unavailable. + * + */ +Con *workspace_back_and_forth_get(void) { + if (!previous_workspace_name) { + DLOG("no previous workspace name set."); + return NULL; + } + + Con *workspace; + workspace = workspace_get(previous_workspace_name, NULL); + + return workspace; +} + 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; } @@ -688,7 +775,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); } /* @@ -700,7 +787,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 1: create a new split container */ Con *split = con_new(NULL, NULL); split->parent = ws; - split->split = true; /* 2: copy layout from workspace */ split->layout = ws->layout; @@ -752,7 +838,6 @@ Con *workspace_attach_to(Con *ws) { /* 1: create a new split container */ Con *new = con_new(NULL, NULL); new->parent = ws; - new->split = true; /* 2: set the requested layout on the split con */ new->layout = ws->workspace_layout; @@ -761,5 +846,147 @@ 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. + * + * The container inherits the layout from the workspace. + */ +Con *workspace_encapsulate(Con *ws) { + if (TAILQ_EMPTY(&(ws->nodes_head))) { + ELOG("Workspace %p / %s has no children to encapsulate\n", ws, ws->name); + return NULL; + } + + Con *new = con_new(NULL, NULL); + new->parent = ws; + new->layout = ws->layout; + + 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)); + con_detach(child); + con_attach(child, new, true); + } + + 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, char *name) { + LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name); + + Con *current_output_con = con_get_output(ws); + if (!current_output_con) { + ELOG("Could not get the output container for workspace %p / %s.\n", ws, ws->name); + return false; + } + + Output *current_output = get_output_by_name(current_output_con->name); + if (!current_output) { + ELOG("Cannot get current output. This is a bug in i3.\n"); + return false; + } + Output *output = get_output_from_string(current_output, name); + if (!output) { + ELOG("Could not get output from string \"%s\"\n", name); + 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->nodes_head)); + LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name); + + 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 (assignment->output == NULL || strcmp(assignment->output, current_output->name) != 0) + continue; + + /* check if this workspace is already attached to the tree */ + Con *workspace = NULL, *out; + TAILQ_FOREACH(out, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(out), + !strcasecmp(child->name, assignment->name)); + if (workspace != 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); + } + + /* 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; +}