]> git.sur5r.net Git - i3/i3/blob - src/client.c
Refactor workspaces to be stored in a TAILQ instead of an array
[i3/i3] / src / client.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  * client.c: holds all client-specific functions
11  *
12  */
13 #include <stdlib.h>
14 #include <string.h>
15 #include <assert.h>
16
17 #include <xcb/xcb.h>
18 #include <xcb/xcb_icccm.h>
19
20 #include "data.h"
21 #include "i3.h"
22 #include "xcb.h"
23 #include "util.h"
24 #include "queue.h"
25 #include "layout.h"
26 #include "client.h"
27 #include "table.h"
28 #include "workspace.h"
29
30 /*
31  * Removes the given client from the container, either because it will be inserted into another
32  * one or because it was unmapped
33  *
34  */
35 void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) {
36         CIRCLEQ_REMOVE(&(container->clients), client, clients);
37
38         if (remove_from_focusstack)
39                 SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
40
41         /* If the container will be empty now and is in stacking mode, we need to
42            unmap the stack_win */
43         if (CIRCLEQ_EMPTY(&(container->clients)) &&
44             (container->mode == MODE_STACK ||
45              container->mode == MODE_TABBED)) {
46                 LOG("Unmapping stack window\n");
47                 struct Stack_Window *stack_win = &(container->stack_win);
48                 stack_win->rect.height = 0;
49                 xcb_unmap_window(conn, stack_win->window);
50                 xcb_flush(conn);
51         }
52 }
53
54 /*
55  * Warps the pointer into the given client (in the middle of it, to be specific), therefore
56  * selecting it
57  *
58  */
59 void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
60         int mid_x = client->rect.width / 2,
61             mid_y = client->rect.height / 2;
62         xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
63 }
64
65 /*
66  * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
67  *
68  */
69 static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
70         xcb_get_property_cookie_t cookie;
71         xcb_get_wm_protocols_reply_t protocols;
72         bool result = false;
73
74         cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
75         if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
76                 return false;
77
78         /* Check if the client’s protocols have the requested atom set */
79         for (uint32_t i = 0; i < protocols.atoms_len; i++)
80                 if (protocols.atoms[i] == atom)
81                         result = true;
82
83         xcb_get_wm_protocols_reply_wipe(&protocols);
84
85         return result;
86 }
87
88 /*
89  * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
90  *
91  */
92 void client_kill(xcb_connection_t *conn, Client *window) {
93         /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
94         if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
95                 LOG("Killing window the hard way\n");
96                 xcb_kill_client(conn, window->child);
97                 return;
98         }
99
100         xcb_client_message_event_t ev;
101
102         memset(&ev, 0, sizeof(xcb_client_message_event_t));
103
104         ev.response_type = XCB_CLIENT_MESSAGE;
105         ev.window = window->child;
106         ev.type = atoms[WM_PROTOCOLS];
107         ev.format = 32;
108         ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
109         ev.data.data32[1] = XCB_CURRENT_TIME;
110
111         LOG("Sending WM_DELETE to the client\n");
112         xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
113         xcb_flush(conn);
114 }
115
116 /*
117  * Checks if the given window class and title match the given client
118  * Window title is passed as "normal" string and as UCS-2 converted string for
119  * matching _NET_WM_NAME capable clients as well as those using legacy hints.
120  *
121  */
122 bool client_matches_class_name(Client *client, char *to_class, char *to_title,
123                                char *to_title_ucs, int to_title_ucs_len) {
124         /* Check if the given class is part of the window class */
125         if (client->window_class == NULL || strcasestr(client->window_class, to_class) == NULL)
126                 return false;
127
128         /* If no title was given, we’re done */
129         if (to_title == NULL)
130                 return true;
131
132         if (client->name_len > -1) {
133                 /* UCS-2 converted window titles */
134                 if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
135                         return false;
136         } else {
137                 /* Legacy hints */
138                 if (client->name == NULL || strcasestr(client->name, to_title) == NULL)
139                         return false;
140         }
141
142         return true;
143 }
144
145 /*
146  * Enters fullscreen mode for the given client. This is called by toggle_fullscreen
147  * and when moving a fullscreen client to another screen.
148  *
149  */
150 void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
151         Workspace *workspace = client->workspace;
152
153         if (workspace->fullscreen_client != NULL) {
154                 LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
155                 return;
156         }
157
158         client->fullscreen = true;
159         workspace->fullscreen_client = client;
160         LOG("Entering fullscreen mode...\n");
161         /* We just entered fullscreen mode, let’s configure the window */
162         uint32_t mask = XCB_CONFIG_WINDOW_X |
163                         XCB_CONFIG_WINDOW_Y |
164                         XCB_CONFIG_WINDOW_WIDTH |
165                         XCB_CONFIG_WINDOW_HEIGHT;
166         uint32_t values[4] = {workspace->rect.x,
167                               workspace->rect.y,
168                               workspace->rect.width,
169                               workspace->rect.height};
170
171         LOG("child itself will be at %dx%d with size %dx%d\n",
172                         values[0], values[1], values[2], values[3]);
173
174         xcb_configure_window(conn, client->frame, mask, values);
175
176         /* Child’s coordinates are relative to the parent (=frame) */
177         values[0] = 0;
178         values[1] = 0;
179         xcb_configure_window(conn, client->child, mask, values);
180
181         /* Raise the window */
182         values[0] = XCB_STACK_MODE_ABOVE;
183         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
184
185         Rect child_rect = workspace->rect;
186         child_rect.x = child_rect.y = 0;
187         fake_configure_notify(conn, child_rect, client->child);
188
189         xcb_flush(conn);
190 }
191
192 /*
193  * Toggles fullscreen mode for the given client. It updates the data structures and
194  * reconfigures (= resizes/moves) the client and its frame to the full size of the
195  * screen. When leaving fullscreen, re-rendering the layout is forced.
196  *
197  */
198 void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
199         /* dock clients cannot enter fullscreen mode */
200         assert(!client->dock);
201
202         Workspace *workspace = client->workspace;
203
204         if (!client->fullscreen) {
205                 client_enter_fullscreen(conn, client);
206                 return;
207         }
208
209         LOG("leaving fullscreen mode\n");
210         client->fullscreen = false;
211         workspace->fullscreen_client = NULL;
212         if (client_is_floating(client)) {
213                 /* For floating clients it’s enough if we just reconfigure that window (in fact,
214                  * re-rendering the layout will not update the client.) */
215                 reposition_client(conn, client);
216                 resize_client(conn, client);
217                 /* redecorate_window flushes */
218                 redecorate_window(conn, client);
219         } else {
220                 client_set_below_floating(conn, client);
221
222                 /* Because the coordinates of the window haven’t changed, it would not be
223                    re-configured if we don’t set the following flag */
224                 client->force_reconfigure = true;
225                 /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
226                 render_layout(conn);
227         }
228
229         xcb_flush(conn);
230 }
231
232 /*
233  * Sets the position of the given client in the X stack to the highest (tiling layer is always
234  * on the same position, so this doesn’t matter) below the first floating client, so that
235  * floating windows are always on top.
236  *
237  */
238 void client_set_below_floating(xcb_connection_t *conn, Client *client) {
239         /* Ensure that it is below all floating clients */
240         Workspace *ws = client->workspace;
241         Client *first_floating = TAILQ_FIRST(&(ws->floating_clients));
242         if (first_floating != TAILQ_END(&(ws->floating_clients))) {
243                 LOG("Setting below floating\n");
244                 uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
245                 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
246         }
247 }
248
249 /*
250  * Returns true if the client is floating. Makes the code more beatiful, as floating
251  * is not simply a boolean, but also saves whether the user selected the current state
252  * or whether it was automatically set.
253  *
254  */
255 bool client_is_floating(Client *client) {
256         return (client->floating >= FLOATING_AUTO_ON);
257 }
258
259 /*
260  * Change the border type for the given client to normal (n), 1px border (p) or
261  * completely borderless (b).
262  *
263  */
264 void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
265         switch (border_type) {
266                 case 'n':
267                         LOG("Changing to normal border\n");
268                         client->titlebar_position = TITLEBAR_TOP;
269                         client->borderless = false;
270                         break;
271                 case 'p':
272                         LOG("Changing to 1px border\n");
273                         client->titlebar_position = TITLEBAR_OFF;
274                         client->borderless = false;
275                         break;
276                 case 'b':
277                         LOG("Changing to borderless\n");
278                         client->titlebar_position = TITLEBAR_OFF;
279                         client->borderless = true;
280                         break;
281                 default:
282                         LOG("Unknown border mode\n");
283                         return;
284         }
285
286         /* Ensure that the child’s position inside our window gets updated */
287         client->force_reconfigure = true;
288
289         /* For clients inside a container, we can simply render the container.
290          * If the client is floating, we need to render the whole layout */
291         if (client->container != NULL)
292                 render_container(conn, client->container);
293         else render_layout(conn);
294
295         redecorate_window(conn, client);
296 }
297
298 /*
299  * Unmap the client, correctly setting any state which is needed.
300  *
301  */
302 void client_unmap(xcb_connection_t *conn, Client *client) {
303         /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */
304         long data[] = { XCB_WM_STATE_WITHDRAWN, XCB_NONE };
305         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
306
307         xcb_unmap_window(conn, client->frame);
308 }
309
310 /*
311  * Map the client, correctly restoring any state needed.
312  *
313  */
314 void client_map(xcb_connection_t *conn, Client *client) {
315         /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
316          * Also, xprop(1) needs that to work. */
317         long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
318         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
319
320         xcb_map_window(conn, client->frame);
321 }
322
323 /*
324  * Set the given mark for this client. Used for jumping to the client
325  * afterwards (like m<mark> and '<mark> in vim).
326  *
327  */
328 void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
329         if (client->mark != NULL)
330                 free(client->mark);
331         client->mark = sstrdup(mark);
332
333         /* Make sure no other client has this mark set */
334         Client *current;
335         Workspace *ws;
336         TAILQ_FOREACH(ws, workspaces, workspaces)
337                 SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
338                         if (current == client ||
339                             current->mark == NULL ||
340                             strcmp(current->mark, mark) != 0)
341                                 continue;
342
343                         free(current->mark);
344                         current->mark = NULL;
345                         /* We can break here since there can only be one other
346                          * client with this mark. */
347                         break;
348                 }
349 }