From 47a798ac4a5b7ac22dcd101220804032db44b7c1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Aug 2009 19:51:51 +0200 Subject: [PATCH] Implement assignments of workspaces to screens, big cleanup of workspace code Please test this! Plug in screens, unplug them, use your video projector, change resolutions, etc. To use the assignments, use the following syntax: workspace [screen ] [name] Where screen can be one of: (It is not provided that these numbers stay constant, so use with care) x (Coordinates where the screen starts, so 1280 will be fine to match the screen right of the main screen if your main screen is 1280 pixels width. However, 1281 will not match) x Some examples follow: workspace 1 screen 0 workspace 1 screen 1 workspace 1 screen 1280x0 workspace 2 screen 1280 workspace 3 screen x0 workspace 3 screen 1 www workspace 4 screen 0 mail --- include/commands.h | 3 - include/data.h | 7 ++ include/util.h | 21 ---- include/workspace.h | 33 +++++ include/xinerama.h | 7 ++ src/commands.c | 115 +---------------- src/config.c | 26 +++- src/handlers.c | 5 +- src/layout.c | 2 +- src/mainx.c | 11 +- src/manage.c | 11 +- src/util.c | 59 --------- src/workspace.c | 294 ++++++++++++++++++++++++++++++++++++++++++++ src/xinerama.c | 144 ++++++++++++++-------- 14 files changed, 475 insertions(+), 263 deletions(-) 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; - - /* Re-use the old bar window */ - screen->bar = workspaces[c].screen->bar; - screen->bargc = workspaces[c].screen->bargc; - - Rect bar_rect = {screen->rect.x, - screen->rect.height - (font->height + 6), - screen->rect.x + screen->rect.width, - font->height + 6}; - - 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)); - - /* Copy the list head for the dock clients */ - screen->dock_clients = workspaces[c].screen->dock_clients; - - /* Update the dimensions */ - memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect)); - workspaces[c].screen = screen; + + TAILQ_FOREACH(old_screen, virtual_screens, screens) { + if (old_screen->num != screen_count) + continue; + + LOG("Found a matching screen\n"); + /* Use the same workspace */ + screen->current_workspace = old_screen->current_workspace; + + /* 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); + + Rect bar_rect = {screen->rect.x, + screen->rect.height - (font->height + 6), + screen->rect.x + screen->rect.width, + font->height + 6}; + + 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)); + + /* 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); -- 2.39.5