]> git.sur5r.net Git - i3/i3/blob - src/floating.c
ab41ed609d255cdae57e39b3e5c17e3036b2ee8c
[i3/i3] / src / floating.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009-2010 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  * src/floating.c: contains all functions for handling floating clients
11  *
12  */
13
14
15 #include "all.h"
16
17 extern xcb_connection_t *conn;
18
19 void floating_enable(Con *con, bool automatic) {
20     if (con_is_floating(con)) {
21         LOG("Container is already in floating mode, not doing anything.\n");
22         return;
23     }
24
25     /* 1: detach the container from its parent */
26     /* TODO: refactor this with tree_close() */
27     TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
28     TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
29
30     con_fix_percent(con->parent, WINDOW_REMOVE);
31
32     /* 2: create a new container to render the decoration on, add
33      * it as a floating window to the workspace */
34     Con *nc = con_new(NULL);
35     nc->parent = con_get_workspace(con);
36     nc->rect = con->rect;
37     /* add pixels for the decoration */
38     /* TODO: don’t add them when the user automatically puts new windows into
39      * 1pixel/borderless mode */
40     nc->rect.height += 17 + 2;
41     nc->rect.width += 4;
42     nc->orientation = NO_ORIENTATION;
43     nc->type = CT_FLOATING_CON;
44     TAILQ_INSERT_TAIL(&(nc->parent->floating_head), nc, floating_windows);
45     TAILQ_INSERT_TAIL(&(nc->parent->focus_head), nc, focused);
46
47     /* 3: attach the child to the new parent container */
48     con->old_parent = con->parent;
49     con->parent = nc;
50     con->floating = FLOATING_USER_ON;
51
52     /* Some clients (like GIMP’s color picker window) get mapped
53      * to (0, 0), so we push them to a reasonable position
54      * (centered over their leader) */
55     if (nc->rect.x == 0 && nc->rect.y == 0) {
56         /* TODO: client_leader support */
57         nc->rect.x = 400;
58         nc->rect.y = 400;
59     }
60
61     TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
62     TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
63 }
64
65 void floating_disable(Con *con, bool automatic) {
66     if (!con_is_floating(con)) {
67         LOG("Container isn't floating, not doing anything.\n");
68         return;
69     }
70
71     assert(con->old_parent != NULL);
72
73     /* 1: detach from parent container */
74     TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
75     TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
76
77     /* 2: kill parent container */
78     TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows);
79     TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused);
80     tree_close(con->parent, false);
81
82     /* 3: re-attach to previous parent */
83     con->parent = con->old_parent;
84     TAILQ_INSERT_TAIL(&(con->parent->nodes_head), con, nodes);
85     TAILQ_INSERT_TAIL(&(con->parent->focus_head), con, focused);
86
87     con->floating = FLOATING_USER_OFF;
88
89     con_fix_percent(con->parent, WINDOW_ADD);
90 }
91
92
93 /*
94  * Toggles floating mode for the given container.
95  *
96  * If the automatic flag is set to true, this was an automatic update by a change of the
97  * window class from the application which can be overwritten by the user.
98  *
99  */
100 void toggle_floating_mode(Con *con, bool automatic) {
101         //i3Font *font = load_font(conn, config.font);
102
103         /* see if the client is already floating */
104         if (con_is_floating(con)) {
105                 LOG("already floating, re-setting to tiling\n");
106
107                 floating_disable(con, automatic);
108                 return;
109         }
110
111         floating_enable(con, automatic);
112
113
114 #if 0
115         if (client->dock) {
116                 DLOG("Not putting dock client into floating mode\n");
117                 return;
118         }
119
120         if (con == NULL) {
121                 DLOG("This client is already in floating (container == NULL), re-inserting\n");
122                 Client *next_tiling;
123                 Workspace *ws = client->workspace;
124                 SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients)
125                         if (!client_is_floating(next_tiling))
126                                 break;
127                 /* If there are no tiling clients on this workspace, there can only be one
128                  * container: the first one */
129                 if (next_tiling == TAILQ_END(&(ws->focus_stack)))
130                         con = ws->table[0][0];
131                 else con = next_tiling->container;
132
133                 /* Remove the client from the list of floating clients */
134                 TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients);
135
136                 DLOG("destination container = %p\n", con);
137                 Client *old_focused = con->currently_focused;
138                 /* Preserve position/size */
139                 memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
140
141                 client->floating = FLOATING_USER_OFF;
142                 client->container = con;
143
144                 if (old_focused != NULL && !old_focused->dock)
145                         CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
146                 else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
147
148                 DLOG("Re-inserted the window.\n");
149                 con->currently_focused = client;
150
151                 client_set_below_floating(conn, client);
152
153                 render_container(conn, con);
154                 xcb_flush(conn);
155
156                 return;
157         }
158
159         DLOG("Entering floating for client %08x\n", client->child);
160
161         /* Remove the client of its container */
162         client_remove_from_container(conn, client, con, false);
163         client->container = NULL;
164
165         /* Add the client to the list of floating clients for its workspace */
166         TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
167
168         if (con->currently_focused == client) {
169                 DLOG("Need to re-adjust currently_focused\n");
170                 /* Get the next client in the focus stack for this particular container */
171                 con->currently_focused = get_last_focused_client(conn, con, NULL);
172         }
173
174         if (automatic)
175                 client->floating = FLOATING_AUTO_ON;
176         else client->floating = FLOATING_USER_ON;
177
178         /* Initialize the floating position from the position in tiling mode, if this
179          * client never was floating (x == -1) */
180         if (client->floating_rect.x == -1) {
181                 /* Copy over the position */
182                 client->floating_rect.x = client->rect.x;
183                 client->floating_rect.y = client->rect.y;
184
185                 /* Copy size the other direction */
186                 client->child_rect.width = client->floating_rect.width;
187                 client->child_rect.height = client->floating_rect.height;
188
189                 client->rect.width = client->child_rect.width + 2 + 2;
190                 client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
191
192                 DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
193                                 client->floating_rect.width, client->floating_rect.height);
194         } else {
195                 /* If the client was already in floating before we restore the old position / size */
196                 DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
197                         client->floating_rect.width, client->floating_rect.height);
198                 memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
199         }
200
201         /* Raise the client */
202         xcb_raise_window(conn, client->frame);
203         reposition_client(conn, client);
204         resize_client(conn, client);
205         /* redecorate_window flushes */
206         redecorate_window(conn, client);
207
208         /* Re-render the tiling layout of this container */
209         render_container(conn, con);
210         xcb_flush(conn);
211 #endif
212 }
213
214 #if 0
215 /*
216  * Removes the floating client from its workspace and attaches it to the new workspace.
217  * This is centralized here because it may happen if you move it via keyboard and
218  * if you move it using your mouse.
219  *
220  */
221 void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
222         /* Remove from focus stack and list of floating clients */
223         SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
224         TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
225
226         if (client->workspace->fullscreen_client == client)
227                 client->workspace->fullscreen_client = NULL;
228
229         /* Insert into destination focus stack and list of floating clients */
230         client->workspace = new_workspace;
231         SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
232         TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
233         if (client->fullscreen)
234                 client->workspace->fullscreen_client = client;
235 }
236
237 /*
238  * This is an ugly data structure which we need because there is no standard
239  * way of having nested functions (only available as a gcc extension at the
240  * moment, clang doesn’t support it) or blocks (only available as a clang
241  * extension and only on Mac OS X systems at the moment).
242  *
243  */
244 struct resize_callback_params {
245         border_t border;
246         xcb_button_press_event_t *event;
247 };
248
249 DRAGGING_CB(resize_callback) {
250         struct resize_callback_params *params = extra;
251         xcb_button_press_event_t *event = params->event;
252         switch (params->border) {
253                 case BORDER_RIGHT: {
254                         int new_width = old_rect->width + (new_x - event->root_x);
255                         if ((new_width < 0) ||
256                             (new_width < client_min_width(client) && client->rect.width >= new_width))
257                                 return;
258                         client->rect.width = new_width;
259                         break;
260                 }
261
262                 case BORDER_BOTTOM: {
263                         int new_height = old_rect->height + (new_y - event->root_y);
264                         if ((new_height < 0) ||
265                             (new_height < client_min_height(client) && client->rect.height >= new_height))
266                                 return;
267                         client->rect.height = old_rect->height + (new_y - event->root_y);
268                         break;
269                 }
270
271                 case BORDER_TOP: {
272                         int new_height = old_rect->height + (event->root_y - new_y);
273                         if ((new_height < 0) ||
274                             (new_height < client_min_height(client) && client->rect.height >= new_height))
275                                 return;
276
277                         client->rect.y = old_rect->y + (new_y - event->root_y);
278                         client->rect.height = new_height;
279                         break;
280                 }
281
282                 case BORDER_LEFT: {
283                         int new_width = old_rect->width + (event->root_x - new_x);
284                         if ((new_width < 0) ||
285                             (new_width < client_min_width(client) && client->rect.width >= new_width))
286                                 return;
287                         client->rect.x = old_rect->x + (new_x - event->root_x);
288                         client->rect.width = new_width;
289                         break;
290                 }
291         }
292
293         /* Push the new position/size to X11 */
294         reposition_client(conn, client);
295         resize_client(conn, client);
296         xcb_flush(conn);
297 }
298
299
300 /*
301  * Called whenever the user clicks on a border (not the titlebar!) of a floating window.
302  * Determines on which border the user clicked and launches the drag_pointer function
303  * with the resize_callback.
304  *
305  */
306 int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
307         DLOG("floating border click\n");
308
309         border_t border;
310
311         if (event->event_y < 2)
312                 border = BORDER_TOP;
313         else if (event->event_y >= (client->rect.height - 2))
314                 border = BORDER_BOTTOM;
315         else if (event->event_x <= 2)
316                 border = BORDER_LEFT;
317         else if (event->event_x >= (client->rect.width - 2))
318                 border = BORDER_RIGHT;
319         else {
320                 DLOG("Not on any border, not doing anything.\n");
321                 return 1;
322         }
323
324         DLOG("border = %d\n", border);
325
326         struct resize_callback_params params = { border, event };
327
328         drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, &params);
329
330         return 1;
331 }
332
333 #endif
334 DRAGGING_CB(drag_window_callback) {
335         struct xcb_button_press_event_t *event = extra;
336
337         /* Reposition the client correctly while moving */
338         con->rect.x = old_rect->x + (new_x - event->root_x);
339         con->rect.y = old_rect->y + (new_y - event->root_y);
340         /* TODO: don’t re-render the whole tree just because we change
341          * coordinates of a floating window */
342         tree_render();
343         x_push_changes(croot);
344 }
345
346 /*
347  * Called when the user clicked on the titlebar of a floating window.
348  * Calls the drag_pointer function with the drag_window callback
349  *
350  */
351 void floating_drag_window(Con *con, xcb_button_press_event_t *event) {
352         DLOG("floating_drag_window\n");
353
354         drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
355         tree_render();
356 }
357
358 /*
359  * This is an ugly data structure which we need because there is no standard
360  * way of having nested functions (only available as a gcc extension at the
361  * moment, clang doesn’t support it) or blocks (only available as a clang
362  * extension and only on Mac OS X systems at the moment).
363  *
364  */
365 struct resize_window_callback_params {
366     border_t corner;
367     bool proportional;
368     xcb_button_press_event_t *event;
369 };
370
371 DRAGGING_CB(resize_window_callback) {
372     struct resize_window_callback_params *params = extra;
373     xcb_button_press_event_t *event = params->event;
374     border_t corner = params->corner;
375
376     int32_t dest_x = con->rect.x;
377     int32_t dest_y = con->rect.y;
378     uint32_t dest_width;
379     uint32_t dest_height;
380
381     double ratio = (double) old_rect->width / old_rect->height;
382
383     /* First guess: We resize by exactly the amount the mouse moved,
384      * taking into account in which corner the client was grabbed */
385     if (corner & BORDER_LEFT)
386             dest_width = old_rect->width - (new_x - event->root_x);
387     else dest_width = old_rect->width + (new_x - event->root_x);
388
389     if (corner & BORDER_TOP)
390             dest_height = old_rect->height - (new_y - event->root_y);
391     else dest_height = old_rect->height + (new_y - event->root_y);
392
393     /* TODO: minimum window size */
394 #if 0
395     /* Obey minimum window size */
396     dest_width = max(dest_width, client_min_width(client));
397     dest_height = max(dest_height, client_min_height(client));
398 #endif
399
400     /* User wants to keep proportions, so we may have to adjust our values */
401     if (params->proportional) {
402             dest_width = max(dest_width, (int) (dest_height * ratio));
403             dest_height = max(dest_height, (int) (dest_width / ratio));
404     }
405
406     /* If not the lower right corner is grabbed, we must also reposition
407      * the client by exactly the amount we resized it */
408     if (corner & BORDER_LEFT)
409             dest_x = old_rect->x + (old_rect->width - dest_width);
410
411     if (corner & BORDER_TOP)
412             dest_y = old_rect->y + (old_rect->height - dest_height);
413
414     con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
415
416     /* TODO: don’t re-render the whole tree just because we change
417      * coordinates of a floating window */
418     tree_render();
419     x_push_changes(croot);
420 }
421
422 /*
423  * Called when the user clicked on a floating window while holding the
424  * floating_modifier and the right mouse button.
425  * Calls the drag_pointer function with the resize_window callback
426  *
427  */
428 void floating_resize_window(Con *con, bool proportional,
429                             xcb_button_press_event_t *event) {
430     DLOG("floating_resize_window\n");
431
432     /* corner saves the nearest corner to the original click. It contains
433      * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
434     border_t corner = 0;
435
436     if (event->event_x <= (con->rect.width / 2))
437             corner |= BORDER_LEFT;
438     else corner |= BORDER_RIGHT;
439
440     if (event->event_y <= (con->rect.height / 2))
441             corner |= BORDER_TOP;
442     else corner |= BORDER_RIGHT;
443
444     struct resize_window_callback_params params = { corner, proportional, event };
445
446     drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
447 }
448
449 /*
450  * This function grabs your pointer and lets you drag stuff around (borders).
451  * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
452  * and the given callback will be called with the parameters specified (client,
453  * border on which the click originally was), the original rect of the client,
454  * the event and the new coordinates (x, y).
455  *
456  */
457 void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t
458                 confine_to, border_t border, callback_t callback, void *extra)
459 {
460         xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
461         uint32_t new_x, new_y;
462         Rect old_rect;
463         if (con != NULL)
464                 memcpy(&old_rect, &(con->rect), sizeof(Rect));
465
466         /* Grab the pointer */
467         /* TODO: returncode */
468         xcb_grab_pointer(conn, 
469                         false,               /* get all pointer events specified by the following mask */
470                         root,                /* grab the root window */
471                         XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */
472                         XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
473                         XCB_GRAB_MODE_ASYNC, /* keyboard mode */
474                         confine_to,          /* confine_to = in which window should the cursor stay */
475                         XCB_NONE,            /* don’t display a special cursor */
476                         XCB_CURRENT_TIME);
477
478         /* Go into our own event loop */
479         xcb_flush(conn);
480
481         xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
482         /* I’ve always wanted to have my own eventhandler… */
483         while ((inside_event = xcb_wait_for_event(conn))) {
484                 /* We now handle all events we can get using xcb_poll_for_event */
485                 do {
486                         /* Same as get_event_handler in xcb */
487                         int nr = inside_event->response_type;
488                         if (nr == 0) {
489                                 /* An error occured */
490                                 //handle_event(NULL, conn, inside_event);
491                                 free(inside_event);
492                                 continue;
493                         }
494                         assert(nr < 256);
495                         nr &= XCB_EVENT_RESPONSE_TYPE_MASK;
496                         assert(nr >= 2);
497
498                         switch (nr) {
499                                 case XCB_BUTTON_RELEASE:
500                                         goto done;
501
502                                 case XCB_MOTION_NOTIFY:
503                                         /* motion_notify events are saved for later */
504                                         FREE(last_motion_notify);
505                                         last_motion_notify = inside_event;
506                                         break;
507
508                                 case XCB_UNMAP_NOTIFY:
509                                         DLOG("Unmap-notify, aborting\n");
510                                         xcb_event_handle(&evenths, inside_event);
511                                         goto done;
512
513                                 default:
514                                         DLOG("Passing to original handler\n");
515                                         /* Use original handler */
516                                         xcb_event_handle(&evenths, inside_event);
517                                         break;
518                         }
519                         if (last_motion_notify != inside_event)
520                                 free(inside_event);
521                 } while ((inside_event = xcb_poll_for_event(conn)) != NULL);
522
523                 if (last_motion_notify == NULL)
524                         continue;
525
526                 new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
527                 new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
528
529                 callback(con, &old_rect, new_x, new_y, extra);
530                 FREE(last_motion_notify);
531         }
532 done:
533         xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
534         xcb_flush(conn);
535 }
536
537 #if 0
538 /*
539  * Changes focus in the given direction for floating clients.
540  *
541  * Changing to the left/right means going to the previous/next floating client,
542  * changing to top/bottom means cycling through the Z-index.
543  *
544  */
545 void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
546         DLOG("floating focus\n");
547
548         if (direction == D_LEFT || direction == D_RIGHT) {
549                 /* Go to the next/previous floating client */
550                 Client *client;
551
552                 while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) :
553                                                         TAILQ_NEXT(currently_focused, floating_clients))) !=
554                        TAILQ_END(&(currently_focused->workspace->floating_clients))) {
555                         if (!client->floating)
556                                 continue;
557                         set_focus(conn, client, true);
558                         return;
559                 }
560         }
561 }
562
563 /*
564  * Moves the client 10px to the specified direction.
565  *
566  */
567 void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
568         DLOG("floating move\n");
569
570         Rect destination = currently_focused->rect;
571         Rect *screen = &(currently_focused->workspace->output->rect);
572
573         switch (direction) {
574                 case D_LEFT:
575                         destination.x -= 10;
576                         break;
577                 case D_RIGHT:
578                         destination.x += 10;
579                         break;
580                 case D_UP:
581                         destination.y -= 10;
582                         break;
583                 case D_DOWN:
584                         destination.y += 10;
585                         break;
586                 /* to make static analyzers happy */
587                 default:
588                         break;
589         }
590
591         /* Prevent windows from vanishing completely */
592         if ((int32_t)(destination.x + destination.width - 5) <= (int32_t)screen->x ||
593             (int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) ||
594             (int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y ||
595             (int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) {
596                 DLOG("boundary check failed, not moving\n");
597                 return;
598         }
599
600         currently_focused->rect = destination;
601         reposition_client(conn, currently_focused);
602
603         /* Because reposition_client does not send a faked configure event (only resize does),
604          * we need to initiate that on our own */
605         fake_absolute_configure_notify(conn, currently_focused);
606         /* fake_absolute_configure_notify flushes */
607 }
608
609 /*
610  * Hides all floating clients (or show them if they are currently hidden) on
611  * the specified workspace.
612  *
613  */
614 void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
615         Client *client;
616
617         workspace->floating_hidden = !workspace->floating_hidden;
618         DLOG("floating_hidden is now: %d\n", workspace->floating_hidden);
619         TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
620                 if (workspace->floating_hidden)
621                         client_unmap(conn, client);
622                 else client_map(conn, client);
623         }
624
625         /* If we just unmapped all floating windows we should ensure that the focus
626          * is set correctly, that ist, to the first non-floating client in stack */
627         if (workspace->floating_hidden)
628                 SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) {
629                         if (client_is_floating(client))
630                                 continue;
631                         set_focus(conn, client, true);
632                         return;
633                 }
634
635         xcb_flush(conn);
636 }
637 #endif