i3

src/client.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  * client.c: holds all client-specific functions
00011  *
00012  */
00013 #include <stdlib.h>
00014 #include <string.h>
00015 #include <assert.h>
00016 #include <limits.h>
00017 
00018 #include <xcb/xcb.h>
00019 #include <xcb/xcb_icccm.h>
00020 
00021 #include "data.h"
00022 #include "i3.h"
00023 #include "xcb.h"
00024 #include "util.h"
00025 #include "queue.h"
00026 #include "layout.h"
00027 #include "client.h"
00028 #include "table.h"
00029 #include "workspace.h"
00030 #include "config.h"
00031 #include "log.h"
00032 
00033 /*
00034  * Removes the given client from the container, either because it will be inserted into another
00035  * one or because it was unmapped
00036  *
00037  */
00038 void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
00039         CIRCLEQ_REMOVE(&(container->clients), client, clients);
00040 
00041         if (remove_from_focusstack)
00042                 SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
00043 
00044         /* If the container will be empty now and is in stacking mode, we need to
00045            unmap the stack_win */
00046         if (CIRCLEQ_EMPTY(&(container->clients)) &&
00047             (container->mode == MODE_STACK ||
00048              container->mode == MODE_TABBED)) {
00049                 DLOG("Unmapping stack window\n");
00050                 struct Stack_Window *stack_win = &(container->stack_win);
00051                 stack_win->rect.height = 0;
00052                 xcb_unmap_window(conn, stack_win->window);
00053                 xcb_flush(conn);
00054         }
00055 }
00056 
00057 /*
00058  * Warps the pointer into the given client (in the middle of it, to be specific), therefore
00059  * selecting it
00060  *
00061  */
00062 void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
00063         int mid_x = client->rect.width / 2,
00064             mid_y = client->rect.height / 2;
00065         xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
00066 }
00067 
00068 /*
00069  * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
00070  *
00071  */
00072 static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
00073         xcb_get_property_cookie_t cookie;
00074         xcb_get_wm_protocols_reply_t protocols;
00075         bool result = false;
00076 
00077         cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
00078         if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
00079                 return false;
00080 
00081         /* Check if the client’s protocols have the requested atom set */
00082         for (uint32_t i = 0; i < protocols.atoms_len; i++)
00083                 if (protocols.atoms[i] == atom)
00084                         result = true;
00085 
00086         xcb_get_wm_protocols_reply_wipe(&protocols);
00087 
00088         return result;
00089 }
00090 
00091 /*
00092  * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
00093  *
00094  */
00095 void client_kill(xcb_connection_t *conn, Client *window) {
00096         /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
00097         if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
00098                 LOG("Killing window the hard way\n");
00099                 xcb_kill_client(conn, window->child);
00100                 return;
00101         }
00102 
00103         xcb_client_message_event_t ev;
00104 
00105         memset(&ev, 0, sizeof(xcb_client_message_event_t));
00106 
00107         ev.response_type = XCB_CLIENT_MESSAGE;
00108         ev.window = window->child;
00109         ev.type = atoms[WM_PROTOCOLS];
00110         ev.format = 32;
00111         ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
00112         ev.data.data32[1] = XCB_CURRENT_TIME;
00113 
00114         LOG("Sending WM_DELETE to the client\n");
00115         xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
00116         xcb_flush(conn);
00117 }
00118 
00119 /*
00120  * Checks if the given window class and title match the given client
00121  * Window title is passed as "normal" string and as UCS-2 converted string for
00122  * matching _NET_WM_NAME capable clients as well as those using legacy hints.
00123  *
00124  */
00125 bool client_matches_class_name(Client *client, char *to_class, char *to_title,
00126                                char *to_title_ucs, int to_title_ucs_len) {
00127         /* Check if the given class is part of the window class */
00128         if ((client->window_class_instance == NULL ||
00129              strcasestr(client->window_class_instance, to_class) == NULL) &&
00130             (client->window_class_class == NULL ||
00131              strcasestr(client->window_class_class, to_class) == NULL))
00132                 return false;
00133 
00134         /* If no title was given, we’re done */
00135         if (to_title == NULL)
00136                 return true;
00137 
00138         if (client->name_len > -1) {
00139                 /* UCS-2 converted window titles */
00140                 if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
00141                         return false;
00142         } else {
00143                 /* Legacy hints */
00144                 if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
00145                         return false;
00146         }
00147 
00148         return true;
00149 }
00150 
00151 /*
00152  * Enters fullscreen mode for the given client. This is called by toggle_fullscreen
00153  * and when moving a fullscreen client to another screen.
00154  *
00155  */
00156 void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) {
00157         Workspace *workspace;
00158         Output *output;
00159         Rect r;
00160 
00161         if (global) {
00162                 TAILQ_FOREACH(output, &outputs, outputs) {
00163                         if (!output->active)
00164                                 continue;
00165 
00166                         if (output->current_workspace->fullscreen_client == NULL)
00167                                 continue;
00168 
00169                         LOG("Not entering global fullscreen mode, there already "
00170                             "is a fullscreen client on output %s.\n", output->name);
00171                         return;
00172                 }
00173 
00174                 r = (Rect) { UINT_MAX, UINT_MAX, 0,0 };
00175                 Output *output;
00176 
00177                 /* Set fullscreen_client for each active workspace.
00178                  * Expand the rectangle to contain all outputs. */
00179                 TAILQ_FOREACH(output, &outputs, outputs) {
00180                         if (!output->active)
00181                                 continue;
00182 
00183                         output->current_workspace->fullscreen_client = client;
00184 
00185                         /* Temporarily abuse width/heigth as coordinates of the lower right corner */
00186                         if (r.x > output->rect.x)
00187                                 r.x = output->rect.x;
00188                         if (r.y > output->rect.y)
00189                                 r.y = output->rect.y;
00190                         if (r.x + r.width < output->rect.x + output->rect.width)
00191                                 r.width = output->rect.x + output->rect.width;
00192                         if (r.y + r.height < output->rect.y + output->rect.height)
00193                                 r.height = output->rect.y + output->rect.height;
00194                 }
00195 
00196                 /* Putting them back to their original meaning */
00197                 r.height -= r.x;
00198                 r.width -= r.y;
00199 
00200                 LOG("Entering global fullscreen mode...\n");
00201         } else {
00202                 workspace = client->workspace;
00203                 if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) {
00204                         LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
00205                         return;
00206                 }
00207 
00208                 workspace->fullscreen_client = client;
00209                 r = workspace->rect;
00210 
00211                 LOG("Entering fullscreen mode...\n");
00212         }
00213 
00214         client->fullscreen = true;
00215 
00216         /* We just entered fullscreen mode, let’s configure the window */
00217         DLOG("child itself will be at %dx%d with size %dx%d\n",
00218                         r.x, r.y, r.width, r.height);
00219 
00220         xcb_set_window_rect(conn, client->frame, r);
00221 
00222         /* Child’s coordinates are relative to the parent (=frame) */
00223         r.x = 0;
00224         r.y = 0;
00225         xcb_set_window_rect(conn, client->child, r);
00226 
00227         /* Raise the window */
00228         uint32_t values[] = { XCB_STACK_MODE_ABOVE };
00229         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
00230 
00231         /* Update _NET_WM_STATE */
00232         values[0] = atoms[_NET_WM_STATE_FULLSCREEN];
00233         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 1, values);
00234 
00235         fake_configure_notify(conn, r, client->child);
00236 
00237         xcb_flush(conn);
00238 }
00239 
00240 /*
00241  * Leaves fullscreen mode for the current client. This is called by toggle_fullscreen.
00242  *
00243  */
00244 void client_leave_fullscreen(xcb_connection_t *conn, Client *client) {
00245         LOG("leaving fullscreen mode\n");
00246         client->fullscreen = false;
00247         Workspace *ws;
00248         TAILQ_FOREACH(ws, workspaces, workspaces)
00249                 if (ws->fullscreen_client == client)
00250                         ws->fullscreen_client = NULL;
00251 
00252         if (client_is_floating(client)) {
00253                 /* For floating clients it’s enough if we just reconfigure that window (in fact,
00254                  * re-rendering the layout will not update the client.) */
00255                 reposition_client(conn, client);
00256                 resize_client(conn, client);
00257                 /* redecorate_window flushes */
00258                 redecorate_window(conn, client);
00259         } else {
00260                 client_set_below_floating(conn, client);
00261 
00262                 /* Because the coordinates of the window haven’t changed, it would not be
00263                    re-configured if we don’t set the following flag */
00264                 client->force_reconfigure = true;
00265                 /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
00266                 render_layout(conn);
00267         }
00268 
00269         /* Update _NET_WM_STATE */
00270         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[_NET_WM_STATE], ATOM, 32, 0, NULL);
00271 
00272         xcb_flush(conn);
00273 }
00274 
00275 /*
00276  * Toggles fullscreen mode for the given client. It updates the data structures and
00277  * reconfigures (= resizes/moves) the client and its frame to the full size of the
00278  * screen. When leaving fullscreen, re-rendering the layout is forced.
00279  *
00280  */
00281 void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
00282         /* dock clients cannot enter fullscreen mode */
00283         assert(!client->dock);
00284 
00285         if (!client->fullscreen) {
00286                 client_enter_fullscreen(conn, client, false);
00287         } else {
00288                 client_leave_fullscreen(conn, client);
00289         }
00290 }
00291 
00292 /*
00293  * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
00294  *
00295  */
00296 void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) {
00297         /* dock clients cannot enter fullscreen mode */
00298         assert(!client->dock);
00299 
00300         if (!client->fullscreen) {
00301                 client_enter_fullscreen(conn, client, true);
00302         } else {
00303                 client_leave_fullscreen(conn, client);
00304         }
00305 }
00306 
00307 /*
00308  * Sets the position of the given client in the X stack to the highest (tiling layer is always
00309  * on the same position, so this doesn’t matter) below the first floating client, so that
00310  * floating windows are always on top.
00311  *
00312  */
00313 void client_set_below_floating(xcb_connection_t *conn, Client *client) {
00314         /* Ensure that it is below all floating clients */
00315         Workspace *ws = client->workspace;
00316         Client *first_floating = TAILQ_FIRST(&(ws->floating_clients));
00317         if (first_floating == TAILQ_END(&(ws->floating_clients)))
00318                 return;
00319 
00320         DLOG("Setting below floating\n");
00321         uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
00322         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
00323 
00324         if (client->workspace->fullscreen_client == NULL)
00325                 return;
00326 
00327         DLOG("(and below fullscreen)\n");
00328         /* Ensure that the window is still below the fullscreen window */
00329         values[0] = client->workspace->fullscreen_client->frame;
00330         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
00331 }
00332 
00333 /*
00334  * Returns true if the client is floating. Makes the code more beatiful, as floating
00335  * is not simply a boolean, but also saves whether the user selected the current state
00336  * or whether it was automatically set.
00337  *
00338  */
00339 bool client_is_floating(Client *client) {
00340         return (client->floating >= FLOATING_AUTO_ON);
00341 }
00342 
00343 /*
00344  * Change the border type for the given client to normal (n), 1px border (p) or
00345  * completely borderless (b) without actually re-rendering the layout. Useful
00346  * for calling it when initializing a new client.
00347  *
00348  */
00349 bool client_init_border(xcb_connection_t *conn, Client *client, char border_type) {
00350         switch (border_type) {
00351                 case 'n':
00352                         LOG("Changing to normal border\n");
00353                         client->titlebar_position = TITLEBAR_TOP;
00354                         client->borderless = false;
00355                         return true;
00356                 case 'p':
00357                         LOG("Changing to 1px border\n");
00358                         client->titlebar_position = TITLEBAR_OFF;
00359                         client->borderless = false;
00360                         return true;
00361                 case 'b':
00362                         LOG("Changing to borderless\n");
00363                         client->titlebar_position = TITLEBAR_OFF;
00364                         client->borderless = true;
00365                         return true;
00366                 default:
00367                         LOG("Unknown border mode\n");
00368                         return false;
00369         }
00370 }
00371 
00372 /*
00373  * Change the border type for the given client to normal (n), 1px border (p) or
00374  * completely borderless (b).
00375  *
00376  */
00377 void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
00378         if (!client_init_border(conn, client, border_type))
00379                 return;
00380 
00381         /* Ensure that the child’s position inside our window gets updated */
00382         client->force_reconfigure = true;
00383 
00384         /* For clients inside a container, we can simply render the container */
00385         if (client->container != NULL)
00386                 render_container(conn, client->container);
00387         else {
00388                 /* If the client is floating, directly push its size */
00389                 if (client_is_floating(client))
00390                         resize_client(conn, client);
00391                 /* Otherwise, it may be a dock client, thus render the whole layout */
00392                 else render_layout(conn);
00393         }
00394 
00395         redecorate_window(conn, client);
00396 }
00397 
00398 /*
00399  * Unmap the client, correctly setting any state which is needed.
00400  *
00401  */
00402 void client_unmap(xcb_connection_t *conn, Client *client) {
00403         /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
00404         long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
00405         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
00406 
00407         xcb_unmap_window(conn, client->frame);
00408 }
00409 
00410 /*
00411  * Map the client, correctly restoring any state needed.
00412  *
00413  */
00414 void client_map(xcb_connection_t *conn, Client *client) {
00415         /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
00416          * Also, xprop(1) needs that to work. */
00417         long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
00418         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
00419 
00420         xcb_map_window(conn, client->frame);
00421 }
00422 
00423 /*
00424  * Set the given mark for this client. Used for jumping to the client
00425  * afterwards (like m<mark> and '<mark> in vim).
00426  *
00427  */
00428 void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
00429         if (client->mark != NULL)
00430                 free(client->mark);
00431         client->mark = sstrdup(mark);
00432 
00433         /* Make sure no other client has this mark set */
00434         Client *current;
00435         Workspace *ws;
00436         TAILQ_FOREACH(ws, workspaces, workspaces)
00437                 SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
00438                         if (current == client ||
00439                             current->mark == NULL ||
00440                             strcmp(current->mark, mark) != 0)
00441                                 continue;
00442 
00443                         free(current->mark);
00444                         current->mark = NULL;
00445                         /* We can break here since there can only be one other
00446                          * client with this mark. */
00447                         break;
00448                 }
00449 }
00450 
00451 /*
00452  * Returns the minimum height of a specific window. The height is calculated
00453  * by using 2 pixels (for the client window itself), possibly padding this to
00454  * comply with the client’s base_height and then adding the decoration height.
00455  *
00456  */
00457 uint32_t client_min_height(Client *client) {
00458         uint32_t height = max(2, client->base_height);
00459         i3Font *font = load_font(global_conn, config.font);
00460 
00461         if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
00462                 return height;
00463 
00464         if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
00465                 return height + 2;
00466 
00467         return height + font->height + 2 + 2;
00468 }
00469 
00470 /*
00471  * See client_min_height.
00472  *
00473  */
00474 uint32_t client_min_width(Client *client) {
00475         uint32_t width = max(2, client->base_width);
00476 
00477         if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
00478                 return width;
00479 
00480         if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
00481                 return width + 2;
00482 
00483         return width + 2 + 2;
00484 }