• Main Page
  • Data Structures
  • Files
  • File List
  • Globals

src/workspace.c

Go to the documentation of this file.
00001 /*
00002  * vim:ts=8:expandtab
00003  *
00004  * i3 - an improved dynamic tiling window manager
00005  *
00006  * © 2009-2010 Michael Stapelberg and contributors
00007  *
00008  * See file LICENSE for license information.
00009  *
00010  * workspace.c: Functions for modifying workspaces
00011  *
00012  */
00013 #include <stdio.h>
00014 #include <stdlib.h>
00015 #include <string.h>
00016 #include <limits.h>
00017 #include <err.h>
00018 
00019 #include "util.h"
00020 #include "data.h"
00021 #include "i3.h"
00022 #include "config.h"
00023 #include "xcb.h"
00024 #include "table.h"
00025 #include "randr.h"
00026 #include "layout.h"
00027 #include "workspace.h"
00028 #include "client.h"
00029 #include "log.h"
00030 #include "ewmh.h"
00031 #include "ipc.h"
00032 
00033 /*
00034  * Returns a pointer to the workspace with the given number (starting at 0),
00035  * creating the workspace if necessary (by allocating the necessary amount of
00036  * memory and initializing the data structures correctly).
00037  *
00038  */
00039 Workspace *workspace_get(int number) {
00040         Workspace *ws = NULL;
00041         TAILQ_FOREACH(ws, workspaces, workspaces)
00042                 if (ws->num == number)
00043                         return ws;
00044 
00045         /* If we are still there, we could not find the requested workspace. */
00046         int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num;
00047 
00048         DLOG("We need to initialize that one, last ws = %d\n", last_ws);
00049 
00050         for (int c = last_ws; c < number; c++) {
00051                 DLOG("Creating new ws\n");
00052 
00053                 ws = scalloc(sizeof(Workspace));
00054                 ws->num = c+1;
00055                 TAILQ_INIT(&(ws->floating_clients));
00056                 expand_table_cols(ws);
00057                 expand_table_rows(ws);
00058                 workspace_set_name(ws, NULL);
00059 
00060                 TAILQ_INSERT_TAIL(workspaces, ws, workspaces);
00061 
00062                 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
00063         }
00064         DLOG("done\n");
00065 
00066         ewmh_update_workarea();
00067 
00068         return ws;
00069 }
00070 
00071 /*
00072  * Sets the name (or just its number) for the given workspace. This has to
00073  * be called for every workspace as the rendering function
00074  * (render_internal_bar) relies on workspace->name and workspace->name_len
00075  * being ready-to-use.
00076  *
00077  */
00078 void workspace_set_name(Workspace *ws, const char *name) {
00079         char *label;
00080         int ret;
00081 
00082         if (name != NULL)
00083                 ret = asprintf(&label, "%d: %s", ws->num + 1, name);
00084         else ret = asprintf(&label, "%d", ws->num + 1);
00085 
00086         if (ret == -1)
00087                 errx(1, "asprintf() failed");
00088 
00089         FREE(ws->name);
00090         FREE(ws->utf8_name);
00091 
00092         ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
00093         if (config.font != NULL)
00094                 ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
00095         else ws->text_width = 0;
00096         ws->utf8_name = label;
00097 }
00098 
00099 /*
00100  * Returns true if the workspace is currently visible. Especially important for
00101  * multi-monitor environments, as they can have multiple currenlty active
00102  * workspaces.
00103  *
00104  */
00105 bool workspace_is_visible(Workspace *ws) {
00106         return (ws->output != NULL && ws->output->current_workspace == ws);
00107 }
00108 
00109 /*
00110  * Switches to the given workspace
00111  *
00112  */
00113 void workspace_show(xcb_connection_t *conn, int workspace) {
00114         bool need_warp = false;
00115         /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */
00116         Workspace *t_ws = workspace_get(workspace-1);
00117 
00118         DLOG("show_workspace(%d)\n", workspace);
00119 
00120         /* Store current_row/current_col */
00121         c_ws->current_row = current_row;
00122         c_ws->current_col = current_col;
00123 
00124         /* Check if the workspace has not been used yet */
00125         workspace_initialize(t_ws, c_ws->output, false);
00126 
00127         if (c_ws->output != t_ws->output) {
00128                 /* We need to switch to the other output first */
00129                 DLOG("moving over to other output.\n");
00130 
00131                 /* Store the old client */
00132                 Client *old_client = CUR_CELL->currently_focused;
00133 
00134                 c_ws = t_ws->output->current_workspace;
00135                 current_col = c_ws->current_col;
00136                 current_row = c_ws->current_row;
00137                 if (CUR_CELL->currently_focused != NULL)
00138                         need_warp = true;
00139                 else {
00140                         Rect *dims = &(c_ws->output->rect);
00141                         xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
00142                                          dims->x + (dims->width / 2), dims->y + (dims->height / 2));
00143                 }
00144 
00145                 /* Re-decorate the old client, it’s not focused anymore */
00146                 if ((old_client != NULL) && !old_client->dock)
00147                         redecorate_window(conn, old_client);
00148                 else xcb_flush(conn);
00149 
00150                 /* We need to check if a global fullscreen-client is blocking
00151                  * the t_ws and if necessary switch that to local fullscreen */
00152                 Client* client = c_ws->fullscreen_client;
00153                 if (client != NULL && client->workspace != c_ws) {
00154                         if (c_ws->fullscreen_client->workspace != c_ws)
00155                                 c_ws->fullscreen_client = NULL;
00156                         client_enter_fullscreen(conn, client, false);
00157                 }
00158         }
00159 
00160         /* Check if we need to change something or if we’re already there */
00161         if (c_ws->output->current_workspace->num == (workspace-1)) {
00162                 Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
00163                 if (last_focused != SLIST_END(&(c_ws->focus_stack)))
00164                         set_focus(conn, last_focused, true);
00165                 if (need_warp) {
00166                         client_warp_pointer_into(conn, last_focused);
00167                         xcb_flush(conn);
00168                 }
00169 
00170                 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
00171 
00172                 return;
00173         }
00174 
00175         Workspace *old_workspace = c_ws;
00176         c_ws = t_ws->output->current_workspace = workspace_get(workspace-1);
00177 
00178         /* Unmap all clients of the old workspace */
00179         workspace_unmap_clients(conn, old_workspace);
00180 
00181         current_row = c_ws->current_row;
00182         current_col = c_ws->current_col;
00183         DLOG("new current row = %d, current col = %d\n", current_row, current_col);
00184 
00185         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
00186 
00187         workspace_map_clients(conn, c_ws);
00188 
00189         /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and
00190          * render_layout afterwards, there is a short flickering on the source
00191          * workspace (assign ws 3 to output 0, ws 4 to output 1, create single
00192          * client on ws 4, move it to ws 3, switch to ws 3, you’ll see the
00193          * flickering). */
00194 
00195         /* Restore focus on the new workspace */
00196         Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
00197         if (last_focused != SLIST_END(&(c_ws->focus_stack)))
00198                 set_focus(conn, last_focused, true);
00199         else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
00200 
00201         render_layout(conn);
00202 
00203         /* We can warp the pointer only after the window has been
00204          * reconfigured in render_layout, otherwise the pointer will
00205          * be warped to the old position, which will not work when we
00206          * moved it to another output. */
00207         if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) {
00208                 client_warp_pointer_into(conn, last_focused);
00209                 xcb_flush(conn);
00210         }
00211 }
00212 
00213 /*
00214  * Assigns the given workspace to the given output by correctly updating its
00215  * state and reconfiguring all the clients on this workspace.
00216  *
00217  * This is called when initializing a output and when re-assigning it to a
00218  * different output which just got available (if you configured it to be on
00219  * output 1 and you just plugged in output 1).
00220  *
00221  */
00222 void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) {
00223         Client *client;
00224         bool empty = true;
00225         bool visible = workspace_is_visible(ws);
00226 
00227         ws->output = output;
00228 
00229         /* Copy the dimensions from the virtual output */
00230         memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
00231 
00232         ewmh_update_workarea();
00233 
00234         /* Force reconfiguration for each client on that workspace */
00235         SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
00236                 client->force_reconfigure = true;
00237                 empty = false;
00238         }
00239 
00240         if (empty)
00241                 return;
00242 
00243         /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
00244         render_workspace(global_conn, output, ws);
00245 
00246         /* …unless we want to see them at the moment, we should hide that workspace */
00247         if (visible && !hide_it)
00248                 return;
00249 
00250         /* however, if this is the current workspace, we only need to adjust
00251          * the output’s current_workspace pointer (and must not unmap the
00252          * windows) */
00253         if (c_ws == ws) {
00254                 DLOG("Need to adjust output->current_workspace...\n");
00255                 output->current_workspace = c_ws;
00256                 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
00257                 return;
00258         }
00259 
00260         workspace_unmap_clients(global_conn, ws);
00261 }
00262 
00263 /*
00264  * Initializes the given workspace if it is not already initialized. The given
00265  * screen is to be understood as a fallback, if the workspace itself either
00266  * was not assigned to a particular screen or cannot be placed there because
00267  * the screen is not attached at the moment.
00268  *
00269  */
00270 void workspace_initialize(Workspace *ws, Output *output, bool recheck) {
00271         Output *old_output;
00272 
00273         if (ws->output != NULL && !recheck) {
00274                 DLOG("Workspace already initialized\n");
00275                 return;
00276         }
00277 
00278         old_output = ws->output;
00279 
00280         /* If this workspace has no preferred output or if the output it wants
00281          * to be on is not available at the moment, we initialize it with
00282          * the output which was given */
00283         if (ws->preferred_output == NULL ||
00284             (ws->output = get_output_by_name(ws->preferred_output)) == NULL)
00285                 ws->output = output;
00286 
00287         DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output);
00288         /* If the assignment did not change, we do not need to update anything */
00289         if (old_output != NULL && ws->output == old_output)
00290                 return;
00291 
00292         workspace_assign_to(ws, ws->output, false);
00293 }
00294 
00295 /*
00296  * Gets the first unused workspace for the given screen, taking into account
00297  * the preferred_output setting of every workspace (workspace assignments).
00298  *
00299  */
00300 Workspace *get_first_workspace_for_output(Output *output) {
00301         Workspace *result = NULL;
00302 
00303         Workspace *ws;
00304         TAILQ_FOREACH(ws, workspaces, workspaces) {
00305                 if (ws->preferred_output == NULL ||
00306                     get_output_by_name(ws->preferred_output) != output)
00307                         continue;
00308 
00309                 result = ws;
00310                 break;
00311         }
00312 
00313         if (result == NULL) {
00314                 /* No assignment found, returning first unused workspace */
00315                 TAILQ_FOREACH(ws, workspaces, workspaces) {
00316                         if (ws->output != NULL)
00317                                 continue;
00318 
00319                         result = ws;
00320                         break;
00321                 }
00322         }
00323 
00324         if (result == NULL) {
00325                 DLOG("No existing free workspace found to assign, creating a new one\n");
00326 
00327                 int last_ws = 0;
00328                 TAILQ_FOREACH(ws, workspaces, workspaces)
00329                         last_ws = ws->num;
00330                 result = workspace_get(last_ws + 1);
00331         }
00332 
00333         workspace_initialize(result, output, false);
00334         return result;
00335 }
00336 
00337 /*
00338  * Maps all clients (and stack windows) of the given workspace.
00339  *
00340  */
00341 void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) {
00342         Client *client;
00343 
00344         ignore_enter_notify_forall(conn, ws, true);
00345 
00346         /* Map all clients on the new workspace */
00347         FOR_TABLE(ws)
00348                 CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
00349                         client_map(conn, client);
00350 
00351         /* Map all floating clients */
00352         if (!ws->floating_hidden)
00353                 TAILQ_FOREACH(client, &(ws->floating_clients), floating_clients)
00354                         client_map(conn, client);
00355 
00356         /* Map all stack windows, if any */
00357         struct Stack_Window *stack_win;
00358         SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
00359                 if (stack_win->container->workspace == ws && stack_win->rect.height > 0)
00360                         xcb_map_window(conn, stack_win->window);
00361 
00362         ignore_enter_notify_forall(conn, ws, false);
00363 }
00364 
00365 /*
00366  * Unmaps all clients (and stack windows) of the given workspace.
00367  *
00368  * This needs to be called separately when temporarily rendering
00369  * a workspace which is not the active workspace to force
00370  * reconfiguration of all clients, like in src/xinerama.c when
00371  * re-assigning a workspace to another screen.
00372  *
00373  */
00374 void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
00375         Client *client;
00376         struct Stack_Window *stack_win;
00377 
00378         /* Ignore notify events because they would cause focus to be changed */
00379         ignore_enter_notify_forall(conn, u_ws, true);
00380 
00381         /* Unmap all clients of the given workspace */
00382         int unmapped_clients = 0;
00383         FOR_TABLE(u_ws)
00384                 CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
00385                         DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
00386                         client_unmap(conn, client);
00387                         unmapped_clients++;
00388                 }
00389 
00390         /* To find floating clients, we traverse the focus stack */
00391         SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
00392                 if (!client_is_floating(client))
00393                         continue;
00394 
00395                 DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
00396 
00397                 client_unmap(conn, client);
00398                 unmapped_clients++;
00399         }
00400 
00401         /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
00402          * if it is not the current workspace. */
00403         if (unmapped_clients == 0 && u_ws != c_ws) {
00404                 /* Re-assign the workspace of all dock clients which use this workspace */
00405                 Client *dock;
00406                 DLOG("workspace %p is empty\n", u_ws);
00407                 SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) {
00408                         if (dock->workspace != u_ws)
00409                                 continue;
00410 
00411                         DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
00412                         dock->workspace = c_ws;
00413                 }
00414                 u_ws->output = NULL;
00415         }
00416 
00417         /* Unmap the stack windows on the given workspace, if any */
00418         SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
00419                 if (stack_win->container->workspace == u_ws)
00420                         xcb_unmap_window(conn, stack_win->window);
00421 
00422         ignore_enter_notify_forall(conn, u_ws, false);
00423 }
00424 
00425 /*
00426  * Goes through all clients on the given workspace and updates the workspace’s
00427  * urgent flag accordingly.
00428  *
00429  */
00430 void workspace_update_urgent_flag(Workspace *ws) {
00431         Client *current;
00432         bool old_flag = ws->urgent;
00433         bool urgent = false;
00434 
00435         SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
00436                 if (!current->urgent)
00437                         continue;
00438 
00439                 urgent = true;
00440                 break;
00441         }
00442 
00443         ws->urgent = urgent;
00444 
00445         if (old_flag != urgent)
00446                 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
00447 }
00448 
00449 /*
00450  * Returns the width of the workspace.
00451  *
00452  */
00453 int workspace_width(Workspace *ws) {
00454         return ws->rect.width;
00455 }
00456 
00457 /*
00458  * Returns the effective height of the workspace (without the internal bar and
00459  * without dock clients).
00460  *
00461  */
00462 int workspace_height(Workspace *ws) {
00463         int height = ws->rect.height;
00464         i3Font *font = load_font(global_conn, config.font);
00465 
00466         /* Reserve space for dock clients */
00467         Client *client;
00468         SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients)
00469                 height -= client->desired_height;
00470 
00471         /* Space for the internal bar */
00472         if (!config.disable_workspace_bar)
00473                 height -= (font->height + 6);
00474 
00475         return height;
00476 }

Generated on Fri Feb 18 2011 for i3 by  doxygen 1.7.1