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