diff --git a/include/commands.h b/include/commands.h index 30ea1110..fbad973b 100644 --- a/include/commands.h +++ b/include/commands.h @@ -16,9 +16,6 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction); -/** Switches to the given workspace */ -void show_workspace(xcb_connection_t *conn, int workspace); - /** Parses a command, see file CMDMODE for more information */ void parse_command(xcb_connection_t *conn, const char *command); diff --git a/include/data.h b/include/data.h index ad9f7a05..f40de86e 100644 --- a/include/data.h +++ b/include/data.h @@ -194,6 +194,13 @@ struct Workspace { /** Are the floating clients on this workspace currently hidden? */ bool floating_hidden; + /** A specifier on which this workspace would like to be (if + * the screen is available). screen := | */ + char *preferred_screen; + + /** Temporary flag needed for re-querying xinerama screens */ + bool reassigned; + /** the client who is started in fullscreen mode on this workspace, * NULL if there is none */ Client *fullscreen_client; diff --git a/include/util.h b/include/util.h index f4c9d539..0b335749 100644 --- a/include/util.h +++ b/include/util.h @@ -130,27 +130,6 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen); Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude); -/** - * Unmaps all clients (and stack windows) of the given workspace. - * - * This needs to be called separately when temporarily rendering a workspace - * which is not the active workspace to force reconfiguration of all clients, - * like in src/xinerama.c when re-assigning a workspace to another screen. - * - */ -void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws); - -/** - * Unmaps all clients (and stack windows) of the given workspace. - * - * This needs to be called separately when temporarily rendering - * a workspace which is not the active workspace to force - * reconfiguration of all clients, like in src/xinerama.c when - * re-assigning a workspace to another screen. - * - */ -void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws); - /** * Sets the given client as focused by updating the data structures correctly, * updating the X input focus and finally re-decorating both windows (to diff --git a/include/workspace.h b/include/workspace.h index 9c69e55b..31406a26 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -11,6 +11,7 @@ #include #include "data.h" +#include "xinerama.h" #ifndef _WORKSPACE_H #define _WORKSPACE_H @@ -32,4 +33,36 @@ void workspace_set_name(Workspace *ws, const char *name); */ bool workspace_is_visible(Workspace *ws); +/** Switches to the given workspace */ +void workspace_show(xcb_connection_t *conn, int workspace); + +/** + * Initializes the given workspace if it is not already initialized. The given + * screen is to be understood as a fallback, if the workspace itself either + * was not assigned to a particular screen or cannot be placed there because + * the screen is not attached at the moment. + * + */ +void workspace_initialize(Workspace *ws, i3Screen *screen); + +/** + * Gets the first unused workspace for the given screen, taking into account + * the preferred_screen setting of every workspace (workspace assignments). + * + */ +Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen); + +/** + * Unmaps all clients (and stack windows) of the given workspace. + * + * This needs to be called separately when temporarily rendering a workspace + * which is not the active workspace to force reconfiguration of all clients, + * like in src/xinerama.c when re-assigning a workspace to another screen. + * + */ +void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws); + + +void workspace_map_clients(xcb_connection_t *conn, Workspace *ws); + #endif diff --git a/include/xinerama.h b/include/xinerama.h index 5b01789f..d4e7bb56 100644 --- a/include/xinerama.h +++ b/include/xinerama.h @@ -16,6 +16,13 @@ TAILQ_HEAD(screens_head, Screen); extern struct screens_head *virtual_screens; +/** + * Returns true if both screen objects describe the same screen (checks their + * size and position). + * + */ +bool screens_are_equal(i3Screen *screen1, i3Screen *screen2); + /** * We have just established a connection to the X server and need the initial * Xinerama information to setup workspaces for each screen. diff --git a/src/commands.c b/src/commands.c index 670d1cb8..f53e4335 100644 --- a/src/commands.c +++ b/src/commands.c @@ -113,7 +113,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } LOG("Switching to ws %d\n", target->current_workspace + 1); - show_workspace(conn, target->current_workspace + 1); + workspace_show(conn, target->current_workspace + 1); return; } @@ -630,113 +630,6 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa set_focus(conn, current_client, true); } -/* - * Switches to the given workspace - * - */ -void show_workspace(xcb_connection_t *conn, int workspace) { - Client *client; - bool need_warp = false; - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */ - Workspace *t_ws = &(workspaces[workspace-1]); - - LOG("show_workspace(%d)\n", workspace); - - /* Store current_row/current_col */ - c_ws->current_row = current_row; - c_ws->current_col = current_col; - - /* Check if the workspace has not been used yet */ - if (t_ws->screen == NULL) { - LOG("initializing new workspace, setting num to %d\n", workspace); - t_ws->screen = c_ws->screen; - /* Copy the dimensions from the virtual screen */ - memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); - } - - if (c_ws->screen != t_ws->screen) { - /* We need to switch to the other screen first */ - LOG("moving over to other screen.\n"); - - /* Store the old client */ - Client *old_client = CUR_CELL->currently_focused; - - c_ws = &(workspaces[t_ws->screen->current_workspace]); - current_col = c_ws->current_col; - current_row = c_ws->current_row; - if (CUR_CELL->currently_focused != NULL) - need_warp = true; - else { - Rect *dims = &(c_ws->screen->rect); - xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, - dims->x + (dims->width / 2), dims->y + (dims->height / 2)); - } - - /* Re-decorate the old client, it’s not focused anymore */ - if ((old_client != NULL) && !old_client->dock) - redecorate_window(conn, old_client); - else xcb_flush(conn); - } - - /* Check if we need to change something or if we’re already there */ - if (c_ws->screen->current_workspace == (workspace-1)) { - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused != SLIST_END(&(c_ws->focus_stack))) { - set_focus(conn, last_focused, true); - if (need_warp) { - client_warp_pointer_into(conn, last_focused); - xcb_flush(conn); - } - } - - return; - } - - t_ws->screen->current_workspace = workspace-1; - Workspace *old_workspace = c_ws; - c_ws = &workspaces[workspace-1]; - - /* Unmap all clients of the old workspace */ - unmap_workspace(conn, old_workspace); - - current_row = c_ws->current_row; - current_col = c_ws->current_col; - LOG("new current row = %d, current col = %d\n", current_row, current_col); - - ignore_enter_notify_forall(conn, c_ws, true); - - /* Map all clients on the new workspace */ - FOR_TABLE(c_ws) - CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients) - xcb_map_window(conn, client->frame); - - /* Map all floating clients */ - if (!c_ws->floating_hidden) - TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients) - xcb_map_window(conn, client->frame); - - /* Map all stack windows, if any */ - struct Stack_Window *stack_win; - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->container->workspace == c_ws) - xcb_map_window(conn, stack_win->window); - - ignore_enter_notify_forall(conn, c_ws, false); - - /* Restore focus on the new workspace */ - Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (last_focused != SLIST_END(&(c_ws->focus_stack))) { - set_focus(conn, last_focused, true); - if (need_warp) { - client_warp_pointer_into(conn, last_focused); - xcb_flush(conn); - } - } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); - - render_layout(conn); -} - /* * Jumps to the given window class / title. * Title is matched using strstr, that is, matches if it appears anywhere @@ -782,7 +675,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { } /* Move to the target workspace */ - show_workspace(conn, ws); + workspace_show(conn, ws); if (result < 3) return; @@ -909,7 +802,7 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { } if (t_ws->screen != NULL) - show_workspace(conn, i+1); + workspace_show(conn, i+1); } /* @@ -1086,7 +979,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (*rest == '\0') { /* No rest? This was a workspace number, not a times specification */ - show_workspace(conn, times); + workspace_show(conn, times); return; } diff --git a/src/config.c b/src/config.c index 86f0381e..65bfd67d 100644 --- a/src/config.c +++ b/src/config.c @@ -349,9 +349,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, continue; } - /* name "workspace number" "name of the workspace" */ - if (strcasecmp(key, "name") == 0) { - LOG("name workspace: %s\n",value); + /* workspace "workspace number" [screen ] ["name of the workspace"] + * with screen := | , e.g. screen 1280 or screen 1 */ + if (strcasecmp(key, "name") == 0 || strcasecmp(key, "workspace") == 0) { + LOG("workspace: %s\n",value); char *ws_str = sstrdup(value); char *end = strchr(ws_str, ' '); if (end == NULL) @@ -371,11 +372,30 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, char *name = value; name += strlen(ws_str) + 1; + if (strncasecmp(name, "screen ", strlen("screen ")) == 0) { + char *screen = strdup(name + strlen("screen ")); + if ((end = strchr(screen, ' ')) != NULL) + *end = '\0'; + LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen); + workspaces[ws_num - 1].preferred_screen = screen; + + name += strlen("screen ") + strlen(screen); + + } + + /* Strip leading whitespace */ + while (*name != '\0' && *name == ' ') + name++; + + LOG("rest to parse = %s\n", name); + if (name == '\0') { free(ws_str); continue; } + LOG("setting name to \"%s\"\n", name); + workspace_set_name(&(workspaces[ws_num - 1]), name); free(ws_str); continue; diff --git a/src/handlers.c b/src/handlers.c index d5a1e8fd..280750ba 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -35,6 +35,7 @@ #include "client.h" #include "manage.h" #include "floating.h" +#include "workspace.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -337,7 +338,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e int add = (event->detail == XCB_BUTTON_INDEX_4 ? -1 : 1); for (int i = c_ws->num + add; (i >= 0) && (i < 10); i += add) if (workspaces[i].screen == screen) { - show_workspace(conn, i+1); + workspace_show(conn, i+1); return true; } return true; @@ -352,7 +353,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e i, drawn, workspaces[i].text_width); if (event->event_x > (drawn + 1) && event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) { - show_workspace(conn, i+1); + workspace_show(conn, i+1); return true; } diff --git a/src/layout.c b/src/layout.c index d90460a4..c0471173 100644 --- a/src/layout.c +++ b/src/layout.c @@ -413,7 +413,7 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) { LOG("client is at %d, should be at %d\n", client->rect.y, *height); if (client->force_reconfigure | - update_if_necessary(&(client->rect.x), 0) | + update_if_necessary(&(client->rect.x), r_ws->rect.x) | update_if_necessary(&(client->rect.y), *height)) reposition_client(conn, client); diff --git a/src/mainx.c b/src/mainx.c index 3ae04857..cde6200d 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -413,8 +413,6 @@ int main(int argc, char *argv[], char *env[]) { xcb_flush(conn); - manage_existing_windows(conn, &prophs, root); - /* Get pointer position to see on which screen we’re starting */ xcb_query_pointer_reply_t *reply; if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) { @@ -427,10 +425,11 @@ int main(int argc, char *argv[], char *env[]) { LOG("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y); return 0; } - if (screen->current_workspace != 0) { - LOG("Ok, I need to go to the other workspace\n"); - c_ws = &workspaces[screen->current_workspace]; - } + + LOG("Starting on %d\n", screen->current_workspace); + c_ws = &workspaces[screen->current_workspace]; + + manage_existing_windows(conn, &prophs, root); /* Create the UNIX domain socket for IPC */ if (config.ipc_socket_path != NULL) { diff --git a/src/manage.c b/src/manage.c index 70d12488..14c058fd 100644 --- a/src/manage.c +++ b/src/manage.c @@ -342,12 +342,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, LOG("Changing container/workspace and unmapping the client\n"); Workspace *t_ws = &(workspaces[assign->workspace-1]); - if (t_ws->screen == NULL) { - LOG("initializing new workspace, setting num to %d\n", assign->workspace); - t_ws->screen = c_ws->screen; - /* Copy the dimensions from the virtual screen */ - memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); - } + workspace_initialize(t_ws, c_ws->screen); new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; new->workspace = t_ws; @@ -445,8 +440,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Map the window first to avoid flickering */ xcb_map_window(conn, child); - if (map_frame) + if (map_frame) { + LOG("Mapping client\n"); xcb_map_window(conn, new->frame); + } if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) { /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ if (new->workspace->fullscreen_client == NULL) { diff --git a/src/util.c b/src/util.c index 8b400864..d1acc744 100644 --- a/src/util.c +++ b/src/util.c @@ -224,65 +224,6 @@ Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Cl return NULL; } -/* - * Unmaps all clients (and stack windows) of the given workspace. - * - * This needs to be called separately when temporarily rendering - * a workspace which is not the active workspace to force - * reconfiguration of all clients, like in src/xinerama.c when - * re-assigning a workspace to another screen. - * - */ -void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { - Client *client; - struct Stack_Window *stack_win; - - /* Ignore notify events because they would cause focus to be changed */ - ignore_enter_notify_forall(conn, u_ws, true); - - /* Unmap all clients of the given workspace */ - int unmapped_clients = 0; - FOR_TABLE(u_ws) - CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { - LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); - xcb_unmap_window(conn, client->frame); - unmapped_clients++; - } - - /* To find floating clients, we traverse the focus stack */ - SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { - if (!client_is_floating(client)) - continue; - - LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); - - xcb_unmap_window(conn, client->frame); - unmapped_clients++; - } - - /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least - * if it is not the current workspace. */ - if (unmapped_clients == 0 && u_ws != c_ws) { - /* Re-assign the workspace of all dock clients which use this workspace */ - Client *dock; - LOG("workspace %p is empty\n", u_ws); - SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { - if (dock->workspace != u_ws) - continue; - - LOG("Re-assigning dock client to c_ws (%p)\n", c_ws); - dock->workspace = c_ws; - } - u_ws->screen = NULL; - } - - /* Unmap the stack windows on the given workspace, if any */ - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->container->workspace == u_ws) - xcb_unmap_window(conn, stack_win->window); - - ignore_enter_notify_forall(conn, u_ws, false); -} /* * Sets the given client as focused by updating the data structures correctly, diff --git a/src/workspace.c b/src/workspace.c index e04ea2a9..d9925743 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -12,6 +12,8 @@ */ #include #include +#include +#include #include #include "util.h" @@ -19,6 +21,11 @@ #include "i3.h" #include "config.h" #include "xcb.h" +#include "table.h" +#include "xinerama.h" +#include "layout.h" +#include "workspace.h" +#include "client.h" /* * Sets the name (or just its number) for the given workspace. This has to @@ -57,3 +64,290 @@ void workspace_set_name(Workspace *ws, const char *name) { bool workspace_is_visible(Workspace *ws) { return (ws->screen->current_workspace == ws->num); } + +/* + * Switches to the given workspace + * + */ +void workspace_show(xcb_connection_t *conn, int workspace) { + bool need_warp = false; + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */ + Workspace *t_ws = &(workspaces[workspace-1]); + + LOG("show_workspace(%d)\n", workspace); + + /* Store current_row/current_col */ + c_ws->current_row = current_row; + c_ws->current_col = current_col; + + /* Check if the workspace has not been used yet */ + workspace_initialize(t_ws, c_ws->screen); + + if (c_ws->screen != t_ws->screen) { + /* We need to switch to the other screen first */ + LOG("moving over to other screen.\n"); + + /* Store the old client */ + Client *old_client = CUR_CELL->currently_focused; + + c_ws = &(workspaces[t_ws->screen->current_workspace]); + current_col = c_ws->current_col; + current_row = c_ws->current_row; + if (CUR_CELL->currently_focused != NULL) + need_warp = true; + else { + Rect *dims = &(c_ws->screen->rect); + xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, + dims->x + (dims->width / 2), dims->y + (dims->height / 2)); + } + + /* Re-decorate the old client, it’s not focused anymore */ + if ((old_client != NULL) && !old_client->dock) + redecorate_window(conn, old_client); + else xcb_flush(conn); + } + + /* Check if we need to change something or if we’re already there */ + if (c_ws->screen->current_workspace == (workspace-1)) { + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused != SLIST_END(&(c_ws->focus_stack))) { + set_focus(conn, last_focused, true); + if (need_warp) { + client_warp_pointer_into(conn, last_focused); + xcb_flush(conn); + } + } + + return; + } + + t_ws->screen->current_workspace = workspace-1; + Workspace *old_workspace = c_ws; + c_ws = &workspaces[workspace-1]; + + /* Unmap all clients of the old workspace */ + workspace_unmap_clients(conn, old_workspace); + + current_row = c_ws->current_row; + current_col = c_ws->current_col; + LOG("new current row = %d, current col = %d\n", current_row, current_col); + + workspace_map_clients(conn, c_ws); + + /* Restore focus on the new workspace */ + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused != SLIST_END(&(c_ws->focus_stack))) { + set_focus(conn, last_focused, true); + if (need_warp) { + client_warp_pointer_into(conn, last_focused); + xcb_flush(conn); + } + } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); + + render_layout(conn); +} + + +/* + * Parses the preferred_screen property of a workspace. You can either specify + * the screen number (it is not given that the screen numbering always stays + * the same) or the screen coordinates (exact coordinates, e.g. 1280 will match + * the screen starting at x=1280, but 1281 will not). For coordinates, you can + * either specify an x coordinate ("1280") or an y coordinate ("x800") or both + * ("1280x800"). + * + */ +static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) { + i3Screen *screen; + char *rest; + int preferred_screen = strtol(preference, &rest, 10); + + LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen); + + if ((rest == preference) || (preferred_screen >= num_screens)) { + int x = INT_MAX, y = INT_MAX; + if (strchr(preference, 'x') != NULL) { + /* Check if only the y coordinate was specified */ + if (*preference == 'x') + y = atoi(preference+1); + else { + x = atoi(preference); + y = atoi(strchr(preference, 'x') + 1); + } + } else { + x = atoi(preference); + } + + LOG("Looking for screen at %d x %d\n", x, y); + + TAILQ_FOREACH(screen, slist, screens) + if ((x == INT_MAX || screen->rect.x == x) && + (y == INT_MAX || screen->rect.y == y)) { + LOG("found %p\n", screen); + return screen; + } + + LOG("none found\n"); + return NULL; + } else { + int c = 0; + TAILQ_FOREACH(screen, slist, screens) + if (c++ == preferred_screen) + return screen; + } + + return NULL; +} + +/* + * Initializes the given workspace if it is not already initialized. The given + * screen is to be understood as a fallback, if the workspace itself either + * was not assigned to a particular screen or cannot be placed there because + * the screen is not attached at the moment. + * + */ +void workspace_initialize(Workspace *ws, i3Screen *screen) { + if (ws->screen != NULL) { + LOG("Workspace already initialized\n"); + return; + } + + /* If this workspace has no preferred screen or if the screen it wants + * to be on is not available at the moment, we initialize it with + * the screen which was given */ + if (ws->preferred_screen == NULL || + (ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL) + ws->screen = screen; + else { LOG("yay, found assignment\n"); } + + /* Copy the dimensions from the virtual screen */ + memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect)); +} + +/* + * Gets the first unused workspace for the given screen, taking into account + * the preferred_screen setting of every workspace (workspace assignments). + * + */ +Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) { + Workspace *result = NULL; + + for (int c = 0; c < 10; c++) { + Workspace *ws = &(workspaces[c]); + if (ws->preferred_screen == NULL || + !screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen)) + continue; + + result = ws; + break; + } + + if (result == NULL) { + /* No assignment found, returning first unused workspace */ + for (int c = 0; c < 10; c++) { + if (workspaces[c].screen != NULL) + continue; + + result = &(workspaces[c]); + break; + } + } + + if (result != NULL) { + workspace_initialize(result, screen); + return result; + } + + LOG("WARNING: No free workspace found to assign!\n"); + return NULL; +} + +/* + * Maps all clients (and stack windows) of the given workspace. + * + */ +void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) { + Client *client; + + ignore_enter_notify_forall(conn, ws, true); + + /* Map all clients on the new workspace */ + FOR_TABLE(ws) + CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients) + xcb_map_window(conn, client->frame); + + /* Map all floating clients */ + if (!ws->floating_hidden) + TAILQ_FOREACH(client, &(ws->floating_clients), floating_clients) + xcb_map_window(conn, client->frame); + + /* Map all stack windows, if any */ + struct Stack_Window *stack_win; + SLIST_FOREACH(stack_win, &stack_wins, stack_windows) + if (stack_win->container->workspace == ws) + xcb_map_window(conn, stack_win->window); + + ignore_enter_notify_forall(conn, ws, false); +} + +/* + * Unmaps all clients (and stack windows) of the given workspace. + * + * This needs to be called separately when temporarily rendering + * a workspace which is not the active workspace to force + * reconfiguration of all clients, like in src/xinerama.c when + * re-assigning a workspace to another screen. + * + */ +void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { + Client *client; + struct Stack_Window *stack_win; + + /* Ignore notify events because they would cause focus to be changed */ + ignore_enter_notify_forall(conn, u_ws, true); + + /* Unmap all clients of the given workspace */ + int unmapped_clients = 0; + FOR_TABLE(u_ws) + CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { + LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); + xcb_unmap_window(conn, client->frame); + unmapped_clients++; + } + + /* To find floating clients, we traverse the focus stack */ + SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { + if (!client_is_floating(client)) + continue; + + LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); + + xcb_unmap_window(conn, client->frame); + unmapped_clients++; + } + + /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least + * if it is not the current workspace. */ + if (unmapped_clients == 0 && u_ws != c_ws) { + /* Re-assign the workspace of all dock clients which use this workspace */ + Client *dock; + LOG("workspace %p is empty\n", u_ws); + SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { + if (dock->workspace != u_ws) + continue; + + LOG("Re-assigning dock client to c_ws (%p)\n", c_ws); + dock->workspace = c_ws; + } + u_ws->screen = NULL; + } + + /* Unmap the stack windows on the given workspace, if any */ + SLIST_FOREACH(stack_win, &stack_wins, stack_windows) + if (stack_win->container->workspace == u_ws) + xcb_unmap_window(conn, stack_win->window); + + ignore_enter_notify_forall(conn, u_ws, false); +} + diff --git a/src/xinerama.c b/src/xinerama.c index 1b410e03..d7ac5f29 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -27,6 +27,7 @@ #include "layout.h" #include "xcb.h" #include "config.h" +#include "workspace.h" /* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens * (xrandr --same-as) */ @@ -34,6 +35,25 @@ struct screens_head *virtual_screens; static bool xinerama_enabled = true; +/* + * Returns true if both screen objects describe the same screen (checks their + * size and position). + * + */ +bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) { + /* If one of both objects (or both) are NULL, we cannot compare them */ + if (screen1 == NULL || screen2 == NULL) + return false; + + /* If the pointers are equal, take the short-circuit */ + if (screen1 == screen2) + return true; + + /* Compare their size - other properties are not relevant to determine + * if a screen is equal to another one */ + return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0); +} + /* * Looks in virtual_screens for the i3Screen whose start coordinates are x, y * @@ -120,8 +140,6 @@ static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspac SLIST_INIT(&(screen->dock_clients)); - /* Copy dimensions */ - memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect)); LOG("that is virtual screen at %d x %d with %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); } @@ -175,7 +193,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis for (int screen = 0; screen < screens; screen++) { i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); - if (s!= NULL) { + if (s != NULL) { /* This screen already exists. We use the littlest screen so that the user can always see the complete workspace */ s->rect.width = min(s->rect.width, screen_info[screen].width); @@ -235,13 +253,14 @@ void initialize_xinerama(xcb_connection_t *conn) { FREE(reply); - i3Screen *s; + i3Screen *screen; num_screens = 0; /* Just go through each workspace and associate as many screens as we can. */ - TAILQ_FOREACH(s, virtual_screens, screens) { - s->num = num_screens; - initialize_screen(conn, s, &(workspaces[num_screens])); + TAILQ_FOREACH(screen, virtual_screens, screens) { + screen->num = num_screens; num_screens++; + Workspace *ws = get_first_workspace_for_screen(virtual_screens, screen); + initialize_screen(conn, screen, ws); } } @@ -268,65 +287,85 @@ void xinerama_requery_screens(xcb_connection_t *conn) { query_screens(conn, new_screens); i3Screen *first = TAILQ_FIRST(new_screens), - *screen; + *screen, + *old_screen; int screen_count = 0; + /* Mark each workspace which currently is assigned to a screen, so we + * can garbage-collect afterwards */ + for (int c = 0; c < 10; c++) + workspaces[c].reassigned = (workspaces[c].screen == NULL); + TAILQ_FOREACH(screen, new_screens, screens) { screen->num = screen_count; screen->current_workspace = -1; - for (int c = 0; c < 10; c++) - if ((workspaces[c].screen != NULL) && - (workspaces[c].screen->num == screen_count)) { - LOG("Found a matching screen\n"); - /* Try to use the same workspace, if it’s available */ - if (workspaces[c].screen->current_workspace) - screen->current_workspace = workspaces[c].screen->current_workspace; - if (screen->current_workspace == -1) - screen->current_workspace = c; + TAILQ_FOREACH(old_screen, virtual_screens, screens) { + if (old_screen->num != screen_count) + continue; - /* Re-use the old bar window */ - screen->bar = workspaces[c].screen->bar; - screen->bargc = workspaces[c].screen->bargc; + LOG("Found a matching screen\n"); + /* Use the same workspace */ + screen->current_workspace = old_screen->current_workspace; - Rect bar_rect = {screen->rect.x, - screen->rect.height - (font->height + 6), - screen->rect.x + screen->rect.width, - font->height + 6}; + /* Re-use the old bar window */ + screen->bar = old_screen->bar; + screen->bargc = old_screen->bargc; + LOG("old_screen->bar = %p\n", old_screen->bar); - xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x)); + Rect bar_rect = {screen->rect.x, + screen->rect.height - (font->height + 6), + screen->rect.x + screen->rect.width, + font->height + 6}; - /* Copy the list head for the dock clients */ - screen->dock_clients = workspaces[c].screen->dock_clients; + LOG("configuring bar to be at %d x %d with %d x %d\n", + bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width); + xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x)); - /* Update the dimensions */ - memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect)); - workspaces[c].screen = screen; + /* Copy the list head for the dock clients */ + screen->dock_clients = old_screen->dock_clients; + + /* Update the dimensions */ + for (int c = 0; c < 10; c++) { + Workspace *ws = &(workspaces[c]); + if (ws->screen != old_screen) + continue; + + LOG("re-assigning ws %d\n", ws->num); + memcpy(&(ws->rect), &(screen->rect), sizeof(Rect)); + ws->screen = screen; + ws->reassigned = true; } + + break; + } if (screen->current_workspace == -1) { - /* Create a new workspace for this screen, it’s new */ - for (int c = 0; c < 10; c++) - if (workspaces[c].screen == NULL) { - LOG("fix: initializing new workspace, setting num to %d\n", c); - initialize_screen(conn, screen, &(workspaces[c])); - break; - } + /* Find the first unused workspace, preferring the ones + * which are assigned to this screen and initialize + * the screen with it. */ + LOG("getting first ws for screen %p\n", screen); + Workspace *ws = get_first_workspace_for_screen(new_screens, screen); + initialize_screen(conn, screen, ws); + + /* As this workspace just got visible (we got a new screen + * without workspace), we need to map its clients */ + workspace_map_clients(conn, ws); } screen_count++; } /* Check for workspaces which are out of bounds */ for (int c = 0; c < 10; c++) { - if ((workspaces[c].screen == NULL) || (workspaces[c].screen->num < num_screens)) + if (workspaces[c].reassigned) continue; /* f_ws is a shortcut to the workspace to fix */ Workspace *f_ws = &(workspaces[c]); Client *client; - LOG("Closing bar window\n"); + LOG("Closing bar window (%p)\n", f_ws->screen->bar); xcb_destroy_window(conn, f_ws->screen->bar); LOG("Workspace %d's screen out of bounds, assigning to first screen\n", c+1); @@ -342,10 +381,15 @@ void xinerama_requery_screens(xcb_connection_t *conn) { render_workspace(conn, first, f_ws); /* …unless we want to see them at the moment, we should hide that workspace */ - if (first->current_workspace == c) + if (workspace_is_visible(f_ws)) continue; - unmap_workspace(conn, f_ws); + workspace_unmap_clients(conn, f_ws); + + if (c_ws == f_ws) { + LOG("Need to adjust c_ws...\n"); + c_ws = &(workspaces[first->current_workspace]); + } } xcb_flush(conn);