]> git.sur5r.net Git - i3/i3/commitdiff
Implement floating (please test and find bugs)
authorMichael Stapelberg <michael@stapelberg.de>
Sat, 23 May 2009 14:34:03 +0000 (16:34 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 23 May 2009 14:34:03 +0000 (16:34 +0200)
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

12 files changed:
i3.config
include/data.h
include/floating.h [new file with mode: 0644]
include/layout.h
include/xcb.h
src/commands.c
src/floating.c [new file with mode: 0644]
src/handlers.c
src/layout.c
src/manage.c
src/util.c
src/xcb.c

index d2cf13a09e8eca695b8c37e94f1b480a24bdb553..c1369c731eccf49cf13de7cb1df6757fa748edff 100644 (file)
--- 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
index 8cb60b8fbb56f44dbeb6abd992879ce0e6b06a46..7308f5c1201d93758fbc3be9209f2a914c2e5a47 100644 (file)
@@ -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 (file)
index 0000000..aa3c55b
--- /dev/null
@@ -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
index 19a40c5d157de4af771a23959b5647cb2f391b87..53dfbb9dead58fd1c7ef2115e257e13b2e6fd6d5 100644 (file)
@@ -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)
index c6bb70b2de4b7f2201e39f10b525ccfbd780f045..bc335121f4b2e14b861cc99165ecb61c05c42775 100644 (file)
@@ -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
index e289e018f163e71e5e7432cbb00fc8dd69ffa6fd..96f390f2293819db00f9f600e13cf2bc469e5504 100644 (file)
@@ -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 <with>? */
@@ -764,7 +839,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
                 }
         }
 
-        /* It's a normal <cmd> */
+        /* Its a normal <cmd> */
         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 (file)
index 0000000..3e8c1c0
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_event.h>
+
+#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);
+}
+
index da5564596a023dbca1a3d28e6c42060ecde5742c..10dd16fba342caf7a3de30f6f1342462e287c688 100644 (file)
@@ -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;
index ea9ff4f260a8e097b998d8f20c0b4261233c633c..1f0a6d87331a5ab7b96dc83a51331cb20a17914b 100644 (file)
@@ -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);
index d330d88f462f8c09e60f129db73fab801c1c6d79..e139b6c1fc7c42aced272b8b53741c9ee6301167 100644 (file)
@@ -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 */
index a1146251ddbb45d23e906e73c4dec06ae7361e06..44f25fb80ccaa016104673125014455bc0c6f939 100644 (file)
@@ -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;
                 }
         }
 
index 10608ac4fc733e5f367512d54d26830afcfb31b2..cefb9c33accb7639f733fa1dd4c06cc82d2e3022 100644 (file)
--- 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);
+}