i3
|
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 * src/floating.c: contains all functions for handling floating clients 00011 * 00012 */ 00013 #include <stdlib.h> 00014 #include <string.h> 00015 #include <assert.h> 00016 00017 #include <xcb/xcb.h> 00018 #include <xcb/xcb_event.h> 00019 00020 #include "i3.h" 00021 #include "config.h" 00022 #include "data.h" 00023 #include "util.h" 00024 #include "xcb.h" 00025 #include "debug.h" 00026 #include "layout.h" 00027 #include "client.h" 00028 #include "floating.h" 00029 #include "workspace.h" 00030 #include "log.h" 00031 00032 /* 00033 * Toggles floating mode for the given client. 00034 * Correctly takes care of the position/size (separately stored for tiling/floating mode) 00035 * and repositions/resizes/redecorates the client. 00036 * 00037 * If the automatic flag is set to true, this was an automatic update by a change of the 00038 * window class from the application which can be overwritten by the user. 00039 * 00040 */ 00041 void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) { 00042 Container *con = client->container; 00043 i3Font *font = load_font(conn, config.font); 00044 00045 if (client->dock) { 00046 DLOG("Not putting dock client into floating mode\n"); 00047 return; 00048 } 00049 00050 if (con == NULL) { 00051 DLOG("This client is already in floating (container == NULL), re-inserting\n"); 00052 Client *next_tiling; 00053 Workspace *ws = client->workspace; 00054 SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients) 00055 if (!client_is_floating(next_tiling)) 00056 break; 00057 /* If there are no tiling clients on this workspace, there can only be one 00058 * container: the first one */ 00059 if (next_tiling == TAILQ_END(&(ws->focus_stack))) 00060 con = ws->table[0][0]; 00061 else con = next_tiling->container; 00062 00063 /* Remove the client from the list of floating clients */ 00064 TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients); 00065 00066 DLOG("destination container = %p\n", con); 00067 Client *old_focused = con->currently_focused; 00068 /* Preserve position/size */ 00069 memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); 00070 00071 client->floating = FLOATING_USER_OFF; 00072 client->container = con; 00073 00074 if (old_focused != NULL && !old_focused->dock) 00075 CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients); 00076 else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients); 00077 00078 DLOG("Re-inserted the window.\n"); 00079 con->currently_focused = client; 00080 00081 client_set_below_floating(conn, client); 00082 00083 render_container(conn, con); 00084 xcb_flush(conn); 00085 00086 return; 00087 } 00088 00089 DLOG("Entering floating for client %08x\n", client->child); 00090 00091 /* Remove the client of its container */ 00092 client_remove_from_container(conn, client, con, false); 00093 client->container = NULL; 00094 00095 /* Add the client to the list of floating clients for its workspace */ 00096 TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); 00097 00098 if (con->currently_focused == client) { 00099 DLOG("Need to re-adjust currently_focused\n"); 00100 /* Get the next client in the focus stack for this particular container */ 00101 con->currently_focused = get_last_focused_client(conn, con, NULL); 00102 } 00103 00104 if (automatic) 00105 client->floating = FLOATING_AUTO_ON; 00106 else client->floating = FLOATING_USER_ON; 00107 00108 /* Initialize the floating position from the position in tiling mode, if this 00109 * client never was floating (x == -1) */ 00110 if (client->floating_rect.x == -1) { 00111 /* Copy over the position */ 00112 client->floating_rect.x = client->rect.x; 00113 client->floating_rect.y = client->rect.y; 00114 00115 /* Copy size the other direction */ 00116 client->child_rect.width = client->floating_rect.width; 00117 client->child_rect.height = client->floating_rect.height; 00118 00119 client->rect.width = client->child_rect.width + 2 + 2; 00120 client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2; 00121 00122 DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, 00123 client->floating_rect.width, client->floating_rect.height); 00124 } else { 00125 /* If the client was already in floating before we restore the old position / size */ 00126 DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, 00127 client->floating_rect.width, client->floating_rect.height); 00128 memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); 00129 } 00130 00131 /* Raise the client */ 00132 xcb_raise_window(conn, client->frame); 00133 reposition_client(conn, client); 00134 resize_client(conn, client); 00135 /* redecorate_window flushes */ 00136 redecorate_window(conn, client); 00137 00138 /* Re-render the tiling layout of this container */ 00139 render_container(conn, con); 00140 xcb_flush(conn); 00141 } 00142 00143 /* 00144 * Removes the floating client from its workspace and attaches it to the new workspace. 00145 * This is centralized here because it may happen if you move it via keyboard and 00146 * if you move it using your mouse. 00147 * 00148 */ 00149 void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { 00150 /* Remove from focus stack and list of floating clients */ 00151 SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); 00152 TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); 00153 00154 if (client->workspace->fullscreen_client == client) 00155 client->workspace->fullscreen_client = NULL; 00156 00157 /* Insert into destination focus stack and list of floating clients */ 00158 client->workspace = new_workspace; 00159 SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); 00160 TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); 00161 if (client->fullscreen) 00162 client->workspace->fullscreen_client = client; 00163 } 00164 00165 /* 00166 * This is an ugly data structure which we need because there is no standard 00167 * way of having nested functions (only available as a gcc extension at the 00168 * moment, clang doesn’t support it) or blocks (only available as a clang 00169 * extension and only on Mac OS X systems at the moment). 00170 * 00171 */ 00172 struct resize_callback_params { 00173 border_t border; 00174 xcb_button_press_event_t *event; 00175 }; 00176 00177 DRAGGING_CB(resize_callback) { 00178 struct resize_callback_params *params = extra; 00179 xcb_button_press_event_t *event = params->event; 00180 switch (params->border) { 00181 case BORDER_RIGHT: { 00182 int new_width = old_rect->width + (new_x - event->root_x); 00183 if ((new_width < 0) || 00184 (new_width < client_min_width(client) && client->rect.width >= new_width)) 00185 return; 00186 client->rect.width = new_width; 00187 break; 00188 } 00189 00190 case BORDER_BOTTOM: { 00191 int new_height = old_rect->height + (new_y - event->root_y); 00192 if ((new_height < 0) || 00193 (new_height < client_min_height(client) && client->rect.height >= new_height)) 00194 return; 00195 client->rect.height = old_rect->height + (new_y - event->root_y); 00196 break; 00197 } 00198 00199 case BORDER_TOP: { 00200 int new_height = old_rect->height + (event->root_y - new_y); 00201 if ((new_height < 0) || 00202 (new_height < client_min_height(client) && client->rect.height >= new_height)) 00203 return; 00204 00205 client->rect.y = old_rect->y + (new_y - event->root_y); 00206 client->rect.height = new_height; 00207 break; 00208 } 00209 00210 case BORDER_LEFT: { 00211 int new_width = old_rect->width + (event->root_x - new_x); 00212 if ((new_width < 0) || 00213 (new_width < client_min_width(client) && client->rect.width >= new_width)) 00214 return; 00215 client->rect.x = old_rect->x + (new_x - event->root_x); 00216 client->rect.width = new_width; 00217 break; 00218 } 00219 } 00220 00221 /* Push the new position/size to X11 */ 00222 reposition_client(conn, client); 00223 resize_client(conn, client); 00224 xcb_flush(conn); 00225 } 00226 00227 00228 /* 00229 * Called whenever the user clicks on a border (not the titlebar!) of a floating window. 00230 * Determines on which border the user clicked and launches the drag_pointer function 00231 * with the resize_callback. 00232 * 00233 */ 00234 int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { 00235 DLOG("floating border click\n"); 00236 00237 border_t border; 00238 00239 if (event->event_y < 2) 00240 border = BORDER_TOP; 00241 else if (event->event_y >= (client->rect.height - 2)) 00242 border = BORDER_BOTTOM; 00243 else if (event->event_x <= 2) 00244 border = BORDER_LEFT; 00245 else if (event->event_x >= (client->rect.width - 2)) 00246 border = BORDER_RIGHT; 00247 else { 00248 DLOG("Not on any border, not doing anything.\n"); 00249 return 1; 00250 } 00251 00252 DLOG("border = %d\n", border); 00253 00254 struct resize_callback_params params = { border, event }; 00255 00256 drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, ¶ms); 00257 00258 return 1; 00259 } 00260 00261 DRAGGING_CB(drag_window_callback) { 00262 struct xcb_button_press_event_t *event = extra; 00263 00264 /* Reposition the client correctly while moving */ 00265 client->rect.x = old_rect->x + (new_x - event->root_x); 00266 client->rect.y = old_rect->y + (new_y - event->root_y); 00267 reposition_client(conn, client); 00268 /* Because reposition_client does not send a faked configure event (only resize does), 00269 * we need to initiate that on our own */ 00270 fake_absolute_configure_notify(conn, client); 00271 /* fake_absolute_configure_notify flushes */ 00272 } 00273 00274 /* 00275 * Called when the user clicked on the titlebar of a floating window. 00276 * Calls the drag_pointer function with the drag_window callback 00277 * 00278 */ 00279 void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { 00280 DLOG("floating_drag_window\n"); 00281 00282 drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); 00283 } 00284 00285 /* 00286 * This is an ugly data structure which we need because there is no standard 00287 * way of having nested functions (only available as a gcc extension at the 00288 * moment, clang doesn’t support it) or blocks (only available as a clang 00289 * extension and only on Mac OS X systems at the moment). 00290 * 00291 */ 00292 struct resize_window_callback_params { 00293 border_t corner; 00294 bool proportional; 00295 xcb_button_press_event_t *event; 00296 }; 00297 00298 DRAGGING_CB(resize_window_callback) { 00299 struct resize_window_callback_params *params = extra; 00300 xcb_button_press_event_t *event = params->event; 00301 border_t corner = params->corner; 00302 00303 int32_t dest_x = client->rect.x; 00304 int32_t dest_y = client->rect.y; 00305 uint32_t dest_width; 00306 uint32_t dest_height; 00307 00308 double ratio = (double) old_rect->width / old_rect->height; 00309 00310 /* First guess: We resize by exactly the amount the mouse moved, 00311 * taking into account in which corner the client was grabbed */ 00312 if (corner & BORDER_LEFT) 00313 dest_width = old_rect->width - (new_x - event->root_x); 00314 else dest_width = old_rect->width + (new_x - event->root_x); 00315 00316 if (corner & BORDER_TOP) 00317 dest_height = old_rect->height - (new_y - event->root_y); 00318 else dest_height = old_rect->height + (new_y - event->root_y); 00319 00320 /* Obey minimum window size */ 00321 dest_width = max(dest_width, client_min_width(client)); 00322 dest_height = max(dest_height, client_min_height(client)); 00323 00324 /* User wants to keep proportions, so we may have to adjust our values */ 00325 if (params->proportional) { 00326 dest_width = max(dest_width, (int) (dest_height * ratio)); 00327 dest_height = max(dest_height, (int) (dest_width / ratio)); 00328 } 00329 00330 /* If not the lower right corner is grabbed, we must also reposition 00331 * the client by exactly the amount we resized it */ 00332 if (corner & BORDER_LEFT) 00333 dest_x = old_rect->x + (old_rect->width - dest_width); 00334 00335 if (corner & BORDER_TOP) 00336 dest_y = old_rect->y + (old_rect->height - dest_height); 00337 00338 client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height }; 00339 00340 /* resize_client flushes */ 00341 resize_client(conn, client); 00342 } 00343 00344 /* 00345 * Called when the user clicked on a floating window while holding the 00346 * floating_modifier and the right mouse button. 00347 * Calls the drag_pointer function with the resize_window callback 00348 * 00349 */ 00350 void floating_resize_window(xcb_connection_t *conn, Client *client, 00351 bool proportional, xcb_button_press_event_t *event) { 00352 DLOG("floating_resize_window\n"); 00353 00354 /* corner saves the nearest corner to the original click. It contains 00355 * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */ 00356 border_t corner = 0; 00357 00358 if (event->event_x <= (client->rect.width / 2)) 00359 corner |= BORDER_LEFT; 00360 else corner |= BORDER_RIGHT; 00361 00362 if (event->event_y <= (client->rect.height / 2)) 00363 corner |= BORDER_TOP; 00364 else corner |= BORDER_RIGHT; 00365 00366 struct resize_window_callback_params params = { corner, proportional, event }; 00367 00368 drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); 00369 } 00370 00371 00372 /* 00373 * This function grabs your pointer and lets you drag stuff around (borders). 00374 * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received 00375 * and the given callback will be called with the parameters specified (client, 00376 * border on which the click originally was), the original rect of the client, 00377 * the event and the new coordinates (x, y). 00378 * 00379 */ 00380 void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, 00381 xcb_window_t confine_to, border_t border, callback_t callback, void *extra) { 00382 uint32_t new_x, new_y; 00383 Rect old_rect; 00384 if (client != NULL) 00385 memcpy(&old_rect, &(client->rect), sizeof(Rect)); 00386 00387 /* Grab the pointer */ 00388 /* TODO: returncode */ 00389 xcb_grab_pointer(conn, 00390 false, /* get all pointer events specified by the following mask */ 00391 root, /* grab the root window */ 00392 XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ 00393 XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ 00394 XCB_GRAB_MODE_ASYNC, /* keyboard mode */ 00395 confine_to, /* confine_to = in which window should the cursor stay */ 00396 XCB_NONE, /* don’t display a special cursor */ 00397 XCB_CURRENT_TIME); 00398 00399 /* Go into our own event loop */ 00400 xcb_flush(conn); 00401 00402 xcb_generic_event_t *inside_event, *last_motion_notify = NULL; 00403 /* I’ve always wanted to have my own eventhandler… */ 00404 while ((inside_event = xcb_wait_for_event(conn))) { 00405 /* We now handle all events we can get using xcb_poll_for_event */ 00406 do { 00407 /* Same as get_event_handler in xcb */ 00408 int nr = inside_event->response_type; 00409 if (nr == 0) { 00410 /* An error occured */ 00411 handle_event(NULL, conn, inside_event); 00412 free(inside_event); 00413 continue; 00414 } 00415 assert(nr < 256); 00416 nr &= XCB_EVENT_RESPONSE_TYPE_MASK; 00417 assert(nr >= 2); 00418 00419 switch (nr) { 00420 case XCB_BUTTON_RELEASE: 00421 goto done; 00422 00423 case XCB_MOTION_NOTIFY: 00424 /* motion_notify events are saved for later */ 00425 FREE(last_motion_notify); 00426 last_motion_notify = inside_event; 00427 break; 00428 00429 case XCB_UNMAP_NOTIFY: 00430 DLOG("Unmap-notify, aborting\n"); 00431 xcb_event_handle(&evenths, inside_event); 00432 goto done; 00433 00434 default: 00435 DLOG("Passing to original handler\n"); 00436 /* Use original handler */ 00437 xcb_event_handle(&evenths, inside_event); 00438 break; 00439 } 00440 if (last_motion_notify != inside_event) 00441 free(inside_event); 00442 } while ((inside_event = xcb_poll_for_event(conn)) != NULL); 00443 00444 if (last_motion_notify == NULL) 00445 continue; 00446 00447 new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; 00448 new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; 00449 00450 callback(conn, client, &old_rect, new_x, new_y, extra); 00451 FREE(last_motion_notify); 00452 } 00453 done: 00454 xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); 00455 xcb_flush(conn); 00456 } 00457 00458 /* 00459 * Changes focus in the given direction for floating clients. 00460 * 00461 * Changing to the left/right means going to the previous/next floating client, 00462 * changing to top/bottom means cycling through the Z-index. 00463 * 00464 */ 00465 void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { 00466 DLOG("floating focus\n"); 00467 00468 if (direction == D_LEFT || direction == D_RIGHT) { 00469 /* Go to the next/previous floating client */ 00470 Client *client; 00471 00472 while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) : 00473 TAILQ_NEXT(currently_focused, floating_clients))) != 00474 TAILQ_END(&(currently_focused->workspace->floating_clients))) { 00475 if (!client->floating) 00476 continue; 00477 set_focus(conn, client, true); 00478 return; 00479 } 00480 } 00481 } 00482 00483 /* 00484 * Moves the client 10px to the specified direction. 00485 * 00486 */ 00487 void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { 00488 DLOG("floating move\n"); 00489 00490 if (currently_focused->fullscreen) { 00491 DLOG("Cannot move fullscreen windows\n"); 00492 return; 00493 } 00494 00495 Rect destination = currently_focused->rect; 00496 Rect *screen = &(currently_focused->workspace->output->rect); 00497 00498 switch (direction) { 00499 case D_LEFT: 00500 destination.x -= 10; 00501 break; 00502 case D_RIGHT: 00503 destination.x += 10; 00504 break; 00505 case D_UP: 00506 destination.y -= 10; 00507 break; 00508 case D_DOWN: 00509 destination.y += 10; 00510 break; 00511 /* to make static analyzers happy */ 00512 default: 00513 break; 00514 } 00515 00516 /* Prevent windows from vanishing completely */ 00517 if ((int32_t)(destination.x + destination.width - 5) <= (int32_t)screen->x || 00518 (int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) || 00519 (int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y || 00520 (int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) { 00521 DLOG("boundary check failed, not moving\n"); 00522 return; 00523 } 00524 00525 currently_focused->rect = destination; 00526 reposition_client(conn, currently_focused); 00527 00528 /* Because reposition_client does not send a faked configure event (only resize does), 00529 * we need to initiate that on our own */ 00530 fake_absolute_configure_notify(conn, currently_focused); 00531 /* fake_absolute_configure_notify flushes */ 00532 } 00533 00534 /* 00535 * Hides all floating clients (or show them if they are currently hidden) on 00536 * the specified workspace. 00537 * 00538 */ 00539 void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { 00540 Client *client; 00541 00542 workspace->floating_hidden = !workspace->floating_hidden; 00543 DLOG("floating_hidden is now: %d\n", workspace->floating_hidden); 00544 TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) { 00545 if (workspace->floating_hidden) 00546 client_unmap(conn, client); 00547 else client_map(conn, client); 00548 } 00549 00550 /* If we just unmapped all floating windows we should ensure that the focus 00551 * is set correctly, that ist, to the first non-floating client in stack */ 00552 if (workspace->floating_hidden) 00553 SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) { 00554 if (client_is_floating(client)) 00555 continue; 00556 set_focus(conn, client, true); 00557 return; 00558 } 00559 00560 xcb_flush(conn); 00561 }