From 5b8e2ecb1847743548ffa81da142f3524c866e5a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 23 May 2009 16:34:03 +0200 Subject: [PATCH] Implement floating (please test and find bugs) Details which are missing: A command to hide/show all floating clients, moving/resizing clients with your mouse holding Mod1 (click anywhere in the client, not just on its borders), resize/move by keyboard, select next/previous client by keyboard --- i3.config | 3 + include/data.h | 5 + include/floating.h | 37 +++++++ include/layout.h | 12 ++ include/xcb.h | 6 + src/commands.c | 96 ++++++++++++++-- src/floating.c | 267 +++++++++++++++++++++++++++++++++++++++++++++ src/handlers.c | 37 +++++-- src/layout.c | 23 ++-- src/manage.c | 13 +++ src/util.c | 81 ++++++++------ src/xcb.c | 9 ++ 12 files changed, 527 insertions(+), 62 deletions(-) create mode 100644 include/floating.h create mode 100644 src/floating.c diff --git a/i3.config b/i3.config index d2cf13a0..c1369c73 100644 --- a/i3.config +++ b/i3.config @@ -15,6 +15,9 @@ bind Mod1+43 s # Default (Mod1+e) bind Mod1+26 d +# Toggle tiling/floating of the current window +bind Mod1+Shift+65 t + # Focus (Mod1+j/k/l/;) bind Mod1+44 h bind Mod1+45 j diff --git a/include/data.h b/include/data.h index 8cb60b8f..7308f5c1 100644 --- a/include/data.h +++ b/include/data.h @@ -253,6 +253,8 @@ struct Client { /* x, y, width, height of the frame */ Rect rect; + /* Position in floating mode and in tiling mode are saved separately */ + Rect floating_rect; /* x, y, width, height of the child (relative to its frame) */ Rect child_rect; @@ -282,6 +284,9 @@ struct Client { /* fullscreen is pretty obvious */ bool fullscreen; + /* floating? (= not in tiling layout) */ + bool floating; + /* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */ enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; diff --git a/include/floating.h b/include/floating.h new file mode 100644 index 00000000..aa3c55b7 --- /dev/null +++ b/include/floating.h @@ -0,0 +1,37 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * (c) 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#ifndef _FLOATING_H +#define _FLOATING_H + +/** + * Enters floating mode for the given client. + * Correctly takes care of the position/size (separately stored for tiling/floating mode) + * and repositions/resizes/redecorates the client. + * + */ +void toggle_floating_mode(xcb_connection_t *conn, Client *client); + +/** + * Called whenever the user clicks on a border (not the titlebar!) of a floating window. + * Determines on which border the user clicked and launches the drag_pointer function + * with the resize_callback. + * + */ +int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); + +/** + * Called when the user clicked on the titlebar of a floating window. + * Calls the drag_pointer function with the drag_window callback + * + */ +void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); + +#endif diff --git a/include/layout.h b/include/layout.h index 19a40c5d..53dfbb9d 100644 --- a/include/layout.h +++ b/include/layout.h @@ -36,6 +36,18 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw */ void redecorate_window(xcb_connection_t *conn, Client *client); +/** + * Pushes the client’s x and y coordinates to X11 + * + */ +void reposition_client(xcb_connection_t *conn, Client *client); + +/** + * Pushes the client’s width/height to X11 and resizes the child window + * + */ +void resize_client(xcb_connection_t *conn, Client *client); + /** * Renders the given container. Is called by render_layout() or individually (for example * when focus changes in a stacking container) diff --git a/include/xcb.h b/include/xcb.h index c6bb70b2..bc335121 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -126,4 +126,10 @@ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client); */ void xcb_get_numlock_mask(xcb_connection_t *conn); +/** + * Raises the given window (typically client->frame) above all other windows + * + */ +void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); + #endif diff --git a/src/commands.c b/src/commands.c index e289e018..96f390f2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -24,6 +24,7 @@ #include "i3.h" #include "xinerama.h" #include "client.h" +#include "floating.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -418,6 +419,49 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction render_layout(conn); } +static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) { + /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ + Workspace *t_ws = &(workspaces[workspace-1]); + + LOG("moving floating\n"); + + if (t_ws->screen == NULL) { + LOG("initializing new workspace, setting num to %d\n", workspace-1); + t_ws->screen = c_ws->screen; + /* Copy the dimensions from the virtual screen */ + memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); + } else { + /* Check if there is already a fullscreen client on the destination workspace and + * stop moving if so. */ + if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { + LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); + return; + } + } + + /* Remove from focus stack */ + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + + if (client->workspace->fullscreen_client == client) + client->workspace->fullscreen_client = NULL; + + /* Insert into destination focus stack */ + client->workspace = t_ws; + SLIST_INSERT_HEAD(&(t_ws->focus_stack), client, focus_clients); + if (client->fullscreen) + t_ws->fullscreen_client = client; + + /* If we’re moving it to an invisible screen, we need to unmap it */ + if (t_ws->screen->current_workspace != t_ws->num) { + LOG("This workspace is not visible, unmapping\n"); + xcb_unmap_window(conn, client->frame); + } + + LOG("done\n"); + + render_layout(conn); +} + /* * Moves the currently selected window to the given workspace * @@ -463,7 +507,9 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa if (container->workspace->fullscreen_client == current_client) container->workspace->fullscreen_client = NULL; + /* TODO: insert it to the correct position */ CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); + SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); if (current_client->fullscreen) t_ws->fullscreen_client = current_client; @@ -564,6 +610,14 @@ void show_workspace(xcb_connection_t *conn, int workspace) { CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients) xcb_map_window(conn, client->frame); + /* Map all floating clients */ + SLIST_FOREACH(client, &(c_ws->focus_stack), focus_clients) { + if (!client->floating) + continue; + + xcb_map_window(conn, client->frame); + } + /* Map all stack windows, if any */ struct Stack_Window *stack_win; SLIST_FOREACH(stack_win, &stack_wins, stack_windows) @@ -683,6 +737,12 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { */ void parse_command(xcb_connection_t *conn, const char *command) { LOG("--- parsing command \"%s\" ---\n", command); + /* Get the first client from focus stack because floating clients are not + * in any container, therefore CUR_CELL is not appropriate. */ + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused == SLIST_END(&(c_ws->focus_stack))) + last_focused = NULL; + /* Hmm, just to be sure */ if (command[0] == '\0') return; @@ -708,17 +768,17 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (STARTS_WITH(command, "kill")) { - if (CUR_CELL->currently_focused == NULL) { + if (last_focused == NULL) { LOG("There is no window to kill\n"); return; } LOG("Killing current window\n"); - client_kill(conn, CUR_CELL->currently_focused); + client_kill(conn, last_focused); return; } - /* Is it a jump to a specified workspae, row, col? */ + /* Is it a jump to a specified workspace, row, col? */ if (STARTS_WITH(command, "jump ")) { const char *arguments = command + strlen("jump "); if (arguments[0] == '"') @@ -736,19 +796,34 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it 'f' for fullscreen? */ if (command[0] == 'f') { - if (CUR_CELL->currently_focused == NULL) + if (last_focused == NULL) return; - toggle_fullscreen(conn, CUR_CELL->currently_focused); + toggle_fullscreen(conn, last_focused); return; } /* Is it just 's' for stacking or 'd' for default? */ if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) { + if (last_focused->floating) { + LOG("not switching, this is a floating client\n"); + return; + } LOG("Switching mode for current container\n"); switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT)); return; } + /* Is it 't' for toggle tiling/floating? */ + if (command[0] == 't') { + if (last_focused == NULL) { + LOG("Cannot toggle tiling/floating: workspace empty\n"); + return; + } + + toggle_floating_mode(conn, last_focused); + return; + } + enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW; /* Is it a ? */ @@ -764,7 +839,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } } - /* It's a normal */ + /* It’s a normal */ char *rest = NULL; enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; direction_t direction; @@ -793,7 +868,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (*rest == '\0') { - move_current_window_to_workspace(conn, workspace); + if (last_focused->floating) + move_floating_window_to_workspace(conn, last_focused, workspace); + else move_current_window_to_workspace(conn, workspace); + return; + } + + if (last_focused->floating) { + LOG("Not performing because this is a floating window\n"); return; } diff --git a/src/floating.c b/src/floating.c new file mode 100644 index 00000000..3e8c1c07 --- /dev/null +++ b/src/floating.c @@ -0,0 +1,267 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * src/floating.c: contains all functions for handling floating clients + * + */ +#include +#include +#include + +#include +#include + +#include "i3.h" +#include "data.h" +#include "util.h" +#include "xcb.h" +#include "debug.h" +#include "layout.h" + +/* On which border was the dragging initiated? */ +typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; +/* Callback for dragging */ +typedef void(*callback_t)(xcb_connection_t*, Client*, border_t, Rect*, xcb_button_press_event_t*, uint32_t, uint32_t); + +/* Forward definitions */ +static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, + border_t border, callback_t callback); + +/* + * Toggles floating mode for the given client. + * Correctly takes care of the position/size (separately stored for tiling/floating mode) + * and repositions/resizes/redecorates the client. + * + */ +void toggle_floating_mode(xcb_connection_t *conn, Client *client) { + Container *con = client->container; + + if (con == NULL) { + LOG("This client is already in floating (container == NULL), re-inserting\n"); + Client *next_tiling; + SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients) + if (!next_tiling->floating) + break; + /* If there are no tiling clients on this workspace, there can only be one + * container: the first one */ + if (next_tiling == SLIST_END(&(client->workspace->focus_stack))) + con = client->workspace->table[0][0]; + else con = next_tiling->container; + + LOG("destination container = %p\n", con); + Client *old_focused = con->currently_focused; + /* Preserve position/size */ + memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); + + client->floating = false; + client->container = con; + + if (old_focused != NULL && !old_focused->dock) + CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients); + else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients); + + LOG("Re-inserted the client into the matrix.\n"); + con->currently_focused = client; + + render_container(conn, con); + xcb_flush(conn); + + return; + } + + LOG("Entering floating for client %08x\n", client->child); + + /* Remove the client of its container */ + CIRCLEQ_REMOVE(&(con->clients), client, clients); + client->container = NULL; + + if (con->currently_focused == client) { + LOG("Need to re-adjust currently_focused\n"); + /* Get the next client in the focus stack for this particular container */ + con->currently_focused = get_last_focused_client(conn, con, NULL); + } + + client->floating = true; + + /* Initialize the floating position from the position in tiling mode, if this + * client never was floating (width == 0) */ + if (client->floating_rect.width == 0) { + memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); + LOG("(%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + client->floating_rect.width, client->floating_rect.height); + } else { + /* If the client was already in floating before we restore the old position / size */ + memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); + } + + /* Raise the client */ + xcb_raise_window(conn, client->frame); + reposition_client(conn, client); + resize_client(conn, client); + /* redecorate_window flushes */ + redecorate_window(conn, client); + + /* Re-render the tiling layout of this container */ + render_container(conn, con); + xcb_flush(conn); +} + +/* + * Callback for resizing windows + * + */ +static void resize_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect, + xcb_button_press_event_t *event, uint32_t new_x, uint32_t new_y) { + switch (border) { + case BORDER_RIGHT: + client->rect.width = old_rect->width + (new_x - event->root_x); + break; + + case BORDER_BOTTOM: + client->rect.height = old_rect->height + (new_y - event->root_y); + break; + + case BORDER_TOP: + client->rect.y = old_rect->y + (new_y - event->root_y); + client->rect.height = old_rect->height + (event->root_y - new_y); + break; + + case BORDER_LEFT: + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.width = old_rect->width + (event->root_x - new_x); + break; + } + + /* Push the new position/size to X11 */ + reposition_client(conn, client); + resize_client(conn, client); + xcb_flush(conn); +} + +/* + * Called whenever the user clicks on a border (not the titlebar!) of a floating window. + * Determines on which border the user clicked and launches the drag_pointer function + * with the resize_callback. + * + */ +int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { + + LOG("floating border click\n"); + + border_t border; + if (event->event_y < 2) + border = BORDER_TOP; + else if (event->event_y >= (client->rect.height - 2)) + border = BORDER_BOTTOM; + else if (event->event_x <= 2) + border = BORDER_LEFT; + else if (event->event_x > 2) + border = BORDER_RIGHT; + else { + LOG("Not on any border, not doing anything.\n"); + return 1; + } + + LOG("border = %d\n", border); + + drag_pointer(conn, client, event, border, resize_callback); + + return 1; +} + +static void drag_window_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect, + xcb_button_press_event_t *event, uint32_t new_x, uint32_t new_y) { + /* Reposition the client correctly while moving */ + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.y = old_rect->y + (new_y - event->root_y); + reposition_client(conn, client); + xcb_flush(conn); +} + +/* + * Called when the user clicked on the titlebar of a floating window. + * Calls the drag_pointer function with the drag_window callback + * + */ +void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { + LOG("floating_drag_window\n"); + + drag_pointer(conn, client, event, BORDER_TOP /* irrelevant */, drag_window_callback); +} + +/* + * This function grabs your pointer and lets you drag stuff around (borders). + * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received + * and the given callback will be called with the parameters specified (client, + * border on which the click originally was), the original rect of the client, + * the event and the new coordinates (x, y). + * + */ +static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, + border_t border, callback_t callback) { + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + uint32_t new_x, new_y; + Rect old_rect; + memcpy(&old_rect, &(client->rect), sizeof(Rect)); + + /* Grab the pointer */ + /* TODO: returncode */ + xcb_grab_pointer(conn, + false, /* get all pointer events specified by the following mask */ + root, /* grab the root window */ + XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ + XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ + XCB_GRAB_MODE_ASYNC, /* keyboard mode */ + XCB_NONE, /* confine_to = in which window should the cursor stay */ + XCB_NONE, /* don’t display a special cursor */ + XCB_CURRENT_TIME); + + /* Go into our own event loop */ + xcb_flush(conn); + + xcb_generic_event_t *inside_event; + /* I’ve always wanted to have my own eventhandler… */ + while ((inside_event = xcb_wait_for_event(conn))) { + /* Same as get_event_handler in xcb */ + int nr = inside_event->response_type; + if (nr == 0) { + /* An error occured */ + handle_event(NULL, conn, inside_event); + free(inside_event); + continue; + } + assert(nr < 256); + nr &= XCB_EVENT_RESPONSE_TYPE_MASK; + assert(nr >= 2); + + /* Check if we need to escape this loop */ + if (nr == XCB_BUTTON_RELEASE) + break; + + switch (nr) { + case XCB_MOTION_NOTIFY: + new_x = ((xcb_motion_notify_event_t*)inside_event)->root_x; + new_y = ((xcb_motion_notify_event_t*)inside_event)->root_y; + + callback(conn, client, border, &old_rect, event, new_x, new_y); + + break; + default: + LOG("Passing to original handler\n"); + /* Use original handler */ + xcb_event_handle(&evenths, inside_event); + break; + } + free(inside_event); + } + + xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + xcb_flush(conn); +} + diff --git a/src/handlers.c b/src/handlers.c index da556459..10dd16fb 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -34,6 +34,7 @@ #include "resize.h" #include "client.h" #include "manage.h" +#include "floating.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -322,7 +323,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ Container *con = client->container; int first, second; - if (con == NULL) { + if (client->dock) { LOG("dock. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); @@ -334,6 +335,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ if (!border_click) { LOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + /* Floating clients should be raised on click */ + if (client->floating) + xcb_raise_window(conn, client->frame); xcb_flush(conn); return 1; } @@ -342,9 +346,22 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ i3Font *font = load_font(conn, config.font); if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { LOG("click on titlebar\n"); + + /* Floating clients can be dragged by grabbing their titlebar */ + if (client->floating) { + /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ + uint32_t values[] = { XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + xcb_flush(conn); + + floating_drag_window(conn, client, event); + } return 1; } + if (client->floating) + return floating_border_click(conn, client, event); + if (event->event_y < 2) { /* This was a press on the top border */ if (con->row == 0) @@ -508,6 +525,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if (client->name != NULL) free(client->name); + /* Clients without a container are either floating or dock windows */ if (client->container != NULL) { Container *con = client->container; @@ -524,6 +542,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti /* Only if this is the active container, we need to really change focus */ if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) set_focus(conn, con->currently_focused, true); + } else if (client->floating) { + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } if (client->dock) { @@ -543,13 +563,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti } /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */ - bool workspace_empty = true; - FOR_TABLE(client->workspace) - if (!CIRCLEQ_EMPTY(&(client->workspace->table[cols][rows]->clients))) { - workspace_empty = false; - break; - } + bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack)); + Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL); + /* If this workspace is currently active, we don’t delete it */ i3Screen *screen; TAILQ_FOREACH(screen, virtual_screens, screens) if (screen->current_workspace == client->workspace->num) { @@ -564,6 +581,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti render_layout(conn); + /* Ensure the focus is set to the next client in the focus stack */ + if (to_focus != NULL) + set_focus(conn, to_focus, true); + return 1; } @@ -755,7 +776,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * return 1; } - if (client->container->mode != MODE_STACK) + if (client->container == NULL || client->container->mode != MODE_STACK) decorate_window(conn, client, client->frame, client->titlegc, 0); else { uint32_t background_color; diff --git a/src/layout.c b/src/layout.c index ea9ff4f2..1f0a6d87 100644 --- a/src/layout.c +++ b/src/layout.c @@ -83,7 +83,7 @@ int get_unoccupied_y(Workspace *workspace, int col) { * */ void redecorate_window(xcb_connection_t *conn, Client *client) { - if (client->container->mode == MODE_STACK) { + if (client->container != NULL && client->container->mode == MODE_STACK) { render_container(conn, client->container); /* We clear the frame to generate exposure events, because the color used in drawing may be different */ @@ -105,12 +105,13 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw border_color; /* Clients without a container (docks) won’t get decorated */ - if (client->container == NULL) + if (client->dock) return; - if (client->container->currently_focused == client) { + LOG("redecorating child %08x\n", client->child); + if (client->floating || client->container->currently_focused == client) { /* Distinguish if the window is currently focused… */ - if (CUR_CELL->currently_focused == client) + if (client->floating || CUR_CELL->currently_focused == client) background_color = get_colorpixel(conn, "#285577"); /* …or if it is the focused window in a not focused container */ else background_color = get_colorpixel(conn, "#555555"); @@ -133,14 +134,14 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color); /* In stacking mode, we only render the rect for this specific decoration */ - if (client->container->mode == MODE_STACK) { - xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height }; - xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); - } else { + if (client->container != NULL && client->container->mode == MODE_STACK) { /* We need to use the container’s width because it is the more recent value - when in stacking mode, clients get reconfigured only on demand (the not active client is not reconfigured), so the client’s rect.width would be wrong */ - xcb_rectangle_t rect = {0, 0, client->container->width, client->rect.height}; + xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height }; + xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); + } else { + xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height}; xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); /* Draw the inner background to have a black frame around clients (such as mplayer) @@ -179,7 +180,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw * Pushes the client’s x and y coordinates to X11 * */ -static void reposition_client(xcb_connection_t *conn, Client *client) { +void reposition_client(xcb_connection_t *conn, Client *client) { LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); /* Note: We can use a pointer to client->x like an array of uint32_ts because it is followed by client->y by definition */ @@ -190,7 +191,7 @@ static void reposition_client(xcb_connection_t *conn, Client *client) { * Pushes the client’s width/height to X11 and resizes the child window * */ -static void resize_client(xcb_connection_t *conn, Client *client) { +void resize_client(xcb_connection_t *conn, Client *client) { i3Font *font = load_font(conn, config.font); LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); diff --git a/src/manage.c b/src/manage.c index d330d88f..e139b6c1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -27,6 +27,7 @@ #include "handlers.h" #include "layout.h" #include "manage.h" +#include "floating.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -333,6 +334,18 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); + + /* Ensure that it is below all floating clients */ + Client *first_floating; + SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients) + if (first_floating->floating) + break; + + if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) { + LOG("Setting below floating\n"); + uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } } /* Check if the window already got the fullscreen hint set */ diff --git a/src/util.c b/src/util.c index a1146251..44f25fb8 100644 --- a/src/util.c +++ b/src/util.c @@ -262,6 +262,15 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { unmapped_clients++; } + /* To find floating clients, we traverse the focus stack */ + SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { + if (!client->floating) + continue; + + xcb_unmap_window(conn, client->frame); + unmapped_clients++; + } + /* If we did not unmap any clients, the workspace is empty and we can destroy it */ if (unmapped_clients == 0) { /* Re-assign the workspace of all dock clients which use this workspace */ @@ -296,7 +305,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { return; /* Store the old client */ - Client *old_client = CUR_CELL->currently_focused; + Client *old_client = SLIST_FIRST(&(c_ws->focus_stack)); /* Check if the focus needs to be changed at all */ if (!set_anyways && (old_client == client)) { @@ -307,47 +316,51 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { /* Store current_row/current_col */ c_ws->current_row = current_row; c_ws->current_col = current_col; - c_ws = client->container->workspace; + c_ws = client->workspace; /* Update container */ - client->container->currently_focused = client; + if (client->container != NULL) { + client->container->currently_focused = client; - current_col = client->container->col; - current_row = client->container->row; + current_col = client->container->col; + current_row = client->container->row; + } LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name); /* Set focus to the entered window, and flush xcb buffer immediately */ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME); //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10); - /* Get the client which was last focused in this particular container, it may be a different - one than old_client */ - Client *last_focused = get_last_focused_client(conn, client->container, NULL); - - /* In stacking containers, raise the client in respect to the one which was focused before */ - if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) { - /* We need to get the client again, this time excluding the current client, because - * we might have just gone into stacking mode and need to raise */ - Client *last_focused = get_last_focused_client(conn, client->container, client); - - if (last_focused != NULL) { - LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); - uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + if (client->container != NULL) { + /* Get the client which was last focused in this particular container, it may be a different + one than old_client */ + Client *last_focused = get_last_focused_client(conn, client->container, NULL); + + /* In stacking containers, raise the client in respect to the one which was focused before */ + if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) { + /* We need to get the client again, this time excluding the current client, because + * we might have just gone into stacking mode and need to raise */ + Client *last_focused = get_last_focused_client(conn, client->container, client); + + if (last_focused != NULL) { + LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); + uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } } - } - /* If it is the same one as old_client, we save us the unnecessary redecorate */ - if ((last_focused != NULL) && (last_focused != old_client)) - redecorate_window(conn, last_focused); + /* If it is the same one as old_client, we save us the unnecessary redecorate */ + if ((last_focused != NULL) && (last_focused != old_client)) + redecorate_window(conn, last_focused); + } /* If we’re in stacking mode, this renders the container to update changes in the title bars and to raise the focused client */ if ((old_client != NULL) && (old_client != client) && !old_client->dock) redecorate_window(conn, old_client); - SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients); - SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients); + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); /* redecorate_window flushes, so we don’t need to */ redecorate_window(conn, client); @@ -521,18 +534,14 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl if (workspaces[workspace].screen == NULL) continue; - FOR_TABLE(&(workspaces[workspace])) { - Container *con = workspaces[workspace].table[cols][rows]; - Client *client; - - CIRCLEQ_FOREACH(client, &(con->clients), clients) { - LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); - if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) - continue; + Client *client; + SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) { + LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); + if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) + continue; - matching = client; - goto done; - } + matching = client; + goto done; } } diff --git a/src/xcb.c b/src/xcb.c index 10608ac4..cefb9c33 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -278,3 +278,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) { xcb_key_symbols_free(keysyms); free(reply); } + +/* + * Raises the given window (typically client->frame) above all other windows + * + */ +void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { + uint32_t values[] = { XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); +} -- 2.39.5