]> git.sur5r.net Git - i3/i3/commitdiff
Implement stacking
authorMichael Stapelberg <michael+git@stapelberg.de>
Mon, 23 Feb 2009 23:30:04 +0000 (00:30 +0100)
committerMichael Stapelberg <michael+git@stapelberg.de>
Mon, 23 Feb 2009 23:30:04 +0000 (00:30 +0100)
12 files changed:
TODO
include/data.h
include/i3.h
include/layout.h
include/util.h
include/xcb.h
src/commands.c
src/handlers.c
src/layout.c
src/mainx.c
src/util.c
src/xcb.c

diff --git a/TODO b/TODO
index ff02d9f1a418cd1a5a4293b7023702861c5dcc15..70023cfe06e644cf0b0e9a8a1b8be24d74f5a61e 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,5 @@
 TODO list, in order of importance:
 
- * freely resizable (e.g. using your mouse, for now) percentage of rows/cols
  * document stuff!
  * more documentation!
  * debian package
index d5f9f27a449e2cbbbe0e7d9880ea0f308d323cd8..b4db800a01a3064a1add912540f67479baa44fbc 100644 (file)
@@ -164,6 +164,21 @@ struct Client {
         SLIST_ENTRY(Client) dock_clients;
 };
 
+/*
+ * Contains data for the windows needed to draw the titlebars on in stacking mode
+ *
+ */
+struct Stack_Window {
+        xcb_window_t window;
+        xcb_gcontext_t gc;
+        uint32_t width, height;
+
+        /* Backpointer to the container this stack window is in */
+        Container *container;
+
+        SLIST_ENTRY(Stack_Window) stack_windows;
+};
+
 /*
  * A container is either in default or stacking mode. It sits inside the table.
  *
@@ -186,6 +201,9 @@ struct Container {
         float width_factor;
         float height_factor;
 
+        /* When in stacking mode, we draw the titlebars of each client onto a separate window */
+        struct Stack_Window stack_win;
+
         /* Backpointer to the workspace this container is in */
         Workspace *workspace;
 
index 53ebb69cbe4a84cfed39e2c1e372ea1de59a54ce..2861e01c311122796ccc7dd40f6ada03591af0a6 100644 (file)
@@ -20,6 +20,7 @@
 
 extern Display *xkbdpy;
 extern TAILQ_HEAD(bindings_head, Binding) bindings;
+extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
 extern xcb_event_handlers_t evenths;
 extern char *pattern;
 extern int num_screens;
index 4d7f0d9ce0a94b87e4a0ab16ee61a56059dc87b1..fe2c2d9a11b6e8e207cbd3be87c54abaab80bc22 100644 (file)
@@ -14,7 +14,8 @@
 #define _LAYOUT_H
 
 Rect get_unoccupied_space(Workspace *workspace);
-void decorate_window(xcb_connection_t *conn, Client *client);
+void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset);
+void render_container(xcb_connection_t *connection, Container *container);
 void render_layout(xcb_connection_t *conn);
 
 #endif
index b25112878086c58d711472c4267af026683b44ac..e1728c557ca17eaf133544c60c8d68b6d037386e 100644 (file)
@@ -30,6 +30,7 @@ char *sstrdup(const char *str);
 void start_application(const char *command);
 void check_error(xcb_connection_t *connection, xcb_void_cookie_t cookie, char *err_message);
 void set_focus(xcb_connection_t *conn, Client *client);
+void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
 void warp_pointer_into(xcb_connection_t *connection, Client *client);
 void toggle_fullscreen(xcb_connection_t *conn, Client *client);
 
index bc625629137c88b96597487d2e44d08bc3f5de62..1f372c36624146c2e523aa200d009cb6c89d2ad4 100644 (file)
@@ -29,5 +29,8 @@ enum { _NET_SUPPORTED = 0,
 
 uint32_t get_colorpixel(xcb_connection_t *conn, xcb_window_t window, char *hex);
 xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class, uint32_t mask, uint32_t *values);
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
+void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
+                   uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y);
 
 #endif
index 7266a32735d0b087d0a2c54f003da7bca081858f..930696eeeba85e2634526bb02094251a0526cf61 100644 (file)
@@ -343,6 +343,13 @@ void parse_command(xcb_connection_t *conn, const char *command) {
                 return;
         }
 
+        /* Is it just 's' for stacking or 'd' for default? */
+        if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
+                printf("Switching mode for current container\n");
+                switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
+                return;
+        }
+
         /* Is it a <with>? */
         if (command[0] == 'w') {
                 /* TODO: implement */
index 17693f82dd9171cea8b55bce754f89402bb16493..f31e23bd46a5839ccb6571f6e732abfa8af1bf8f 100644 (file)
@@ -89,7 +89,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
  *
  */
 int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
-        printf("enter_notify\n");
+        printf("enter_notify for %08x\n", event->event);
 
         /* This was either a focus for a client’s parent (= titlebar)… */
         Client *client = table_get(byParent, event->event);
@@ -110,6 +110,10 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
                 return 1;
         }
 
+        /* When in stacking, enter notifications are ignored. Focus will be changed via keyboard only. */
+        if (client->container->mode == MODE_STACK)
+                return 1;
+
         set_focus(conn, client);
 
         return 1;
@@ -354,7 +358,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
         strncpy(client->name, xcb_get_property_value(prop), client->name_len);
         printf("rename to \"%.*s\".\n", client->name_len, client->name);
 
-        decorate_window(conn, client);
+        decorate_window(conn, client, client->frame, client->titlegc, 0);
         xcb_flush(conn);
 
         return 1;
@@ -365,11 +369,28 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
  *
  */
 int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *e) {
+        printf("got expose_event\n");
+        /* e->count is the number of minimum remaining expose events for this window, so we
+           skip all events but the last one */
+        if (e->count != 0)
+                return 1;
+
         Client *client = table_get(byParent, e->window);
-        if(!client || e->count != 0)
+        if (client == NULL) {
+                /* There was no client in the table, so this is probably an expose event for
+                   one of our stack_windows. */
+                struct Stack_Window *stack_win;
+                SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
+                        if (stack_win->window == e->window) {
+                                render_container(conn, stack_win->container);
+                                return 1;
+                        }
                 return 1;
+        }
+
         printf("handle_expose_event()\n");
-        decorate_window(conn, client);
+        if (client->container->mode != MODE_STACK)
+                decorate_window(conn, client, client->frame, client->titlegc, 0);
         return 1;
 }
 
index fb11b77982c05d1555f25aeb4c0ce7d2bd8916ca..0530388482502defc502d6cd8a4a7f0043505df8 100644 (file)
@@ -14,6 +14,7 @@
 #include <string.h>
 #include <stdlib.h>
 #include <xcb/xcb.h>
+#include <assert.h>
 
 #include "font.h"
 #include "i3.h"
@@ -81,9 +82,7 @@ int get_unoccupied_y(Workspace *workspace, int col) {
  * (Re-)draws window decorations for a given Client
  *
  */
-void decorate_window(xcb_connection_t *conn, Client *client) {
-        uint32_t mask = 0;
-        uint32_t values[3];
+void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
         i3Font *font = load_font(conn, pattern);
         uint32_t background_color,
                  text_color,
@@ -107,45 +106,31 @@ void decorate_window(xcb_connection_t *conn, Client *client) {
            - Draw a rect around the whole client in background_color
            - Draw two lines in a lighter color
            - Draw the window’s title
-
-           Note that xcb_image_text apparently adds 1xp border around the font? Can anyone confirm this?
          */
 
         /* Draw a green rectangle around the window */
-        mask = XCB_GC_FOREGROUND;
-        values[0] = background_color;
-        xcb_change_gc(conn, client->titlegc, mask, values);
+        xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
+        printf("drawing at offset %d\n", offset);
 
-        xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
-        xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &rect);
+        xcb_rectangle_t rect = {0, offset, client->rect.width, offset + client->rect.height};
+        xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
 
         /* Draw the lines */
-        /* TODO: this needs to be more beautiful somewhen. maybe stdarg + change_gc(gc, ...) ? */
-#define DRAW_LINE(colorpixel, x, y, to_x, to_y) { \
-                uint32_t draw_values[1]; \
-                draw_values[0] = colorpixel; \
-                xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND, draw_values); \
-                xcb_point_t points[] = {{x, y}, {to_x, to_y}}; \
-                xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 2, points); \
-        }
-
-        DRAW_LINE(border_color, 2, 0, client->rect.width, 0);
-        DRAW_LINE(border_color, 2, font->height + 3, 2 + client->rect.width, font->height + 3);
+        xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
+        xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
+                      2 + client->rect.width, offset + font->height + 3);
 
         /* Draw the font */
-        mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
-
-        values[0] = text_color;
-        values[1] = background_color;
-        values[2] = font->id;
-
-        xcb_change_gc(conn, client->titlegc, mask, values);
+        uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
+        uint32_t values[] = { text_color, background_color, font->id };
+        xcb_change_gc(conn, gc, mask, values);
 
         /* TODO: utf8? */
         char *label;
         asprintf(&label, "(%08x) %.*s", client->frame, client->name_len, client->name);
-        xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), client->frame,
-                                        client->titlegc, 3 /* X */, font->height /* Y = baseline of font */, label);
+        printf("label is %s\n", label);
+        xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), drawable,
+                                        gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
         check_error(conn, text_cookie, "Could not draw client's title");
         free(label);
 }
@@ -169,7 +154,7 @@ static void reposition_client(xcb_connection_t *connection, Client *client) {
 static void resize_client(xcb_connection_t *connection, Client *client) {
         i3Font *font = load_font(connection, pattern);
 
-        printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
+        printf("resizing client \"%s\" to %d x %d\n", client->name, client->rect.width, client->rect.height);
         xcb_configure_window(connection, client->frame,
                         XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
                         &(client->rect.width));
@@ -182,7 +167,8 @@ static void resize_client(xcb_connection_t *connection, Client *client) {
                         XCB_CONFIG_WINDOW_WIDTH |
                         XCB_CONFIG_WINDOW_HEIGHT;
         Rect rect;
-        if (client->titlebar_position == TITLEBAR_OFF) {
+        if (client->titlebar_position == TITLEBAR_OFF ||
+            client->container->mode == MODE_STACK) {
                 rect.x = 0;
                 rect.y = 0;
                 rect.width = client->rect.width;
@@ -199,17 +185,23 @@ static void resize_client(xcb_connection_t *connection, Client *client) {
         xcb_configure_window(connection, client->child, mask, &(rect.x));
 }
 
-static void render_container(xcb_connection_t *connection, Container *container) {
+/*
+ * Renders the given container. Is called by render_layout() or individually (for example
+ * when focus changes in a stacking container)
+ *
+ */
+void render_container(xcb_connection_t *connection, Container *container) {
         Client *client;
+        int num_clients = 0, current_client = 0;
+
+        if (container->currently_focused == NULL)
+                return;
+
+        CIRCLEQ_FOREACH(client, &(container->clients), clients)
+                num_clients++;
 
         if (container->mode == MODE_DEFAULT) {
-                int num_clients = 0;
-                CIRCLEQ_FOREACH(client, &(container->clients), clients)
-                        if (!client->dock)
-                                num_clients++;
                 printf("got %d clients in this default container.\n", num_clients);
-
-                int current_client = 0;
                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
                         /* Check if we changed client->x or client->y by updating it.
                          * Note the bitwise OR instead of logical OR to force evaluation of both statements */
@@ -233,7 +225,42 @@ static void render_container(xcb_connection_t *connection, Container *container)
                         current_client++;
                 }
         } else {
-                /* TODO: Implement stacking */
+                i3Font *font = load_font(connection, pattern);
+                int decoration_height = (font->height + 2 + 2);
+                struct Stack_Window *stack_win = &(container->stack_win);
+
+                /* Check if we need to reconfigure our stack title window */
+                if ((stack_win->width != (stack_win->width = container->width)) |
+                    (stack_win->height != (stack_win->height = decoration_height * num_clients)))
+                        xcb_configure_window(connection, stack_win->window,
+                                XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &(stack_win->width));
+
+                /* All clients are repositioned */
+                CIRCLEQ_FOREACH(client, &(container->clients), clients) {
+                        /* Check if we changed client->x or client->y by updating it.
+                         * Note the bitwise OR instead of logical OR to force evaluation of both statements */
+                        if (client->force_reconfigure |
+                            (client->rect.x != (client->rect.x = container->x)) |
+                            (client->rect.y != (client->rect.y = container->y + (decoration_height * num_clients))))
+                                reposition_client(connection, client);
+
+                        if (client->force_reconfigure |
+                            (client->rect.width != (client->rect.width = container->width)) |
+                            (client->rect.height !=
+                             (client->rect.height = container->height - (decoration_height * num_clients))))
+                                resize_client(connection, client);
+
+                        client->force_reconfigure = false;
+
+                        decorate_window(connection, client, stack_win->window, stack_win->gc,
+                                        current_client * decoration_height);
+                        current_client++;
+                }
+
+                /* Raise the focused window */
+                uint32_t values[] = { XCB_STACK_MODE_ABOVE };
+                xcb_configure_window(connection, container->currently_focused->frame,
+                                     XCB_CONFIG_WINDOW_STACK_MODE, values);
         }
 }
 
@@ -308,7 +335,7 @@ void render_layout(xcb_connection_t *connection) {
                                 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
                                 container->height *= container->rowspan;
 
-                                /* Render it */
+                                /* Render the container if it is not empty */
                                 render_container(connection, container);
 
                                 xoffset[rows] += container->width;
index baaaa2906121e00647ab42fc260acb6186a9bb48..e49362a0bcd42334e3025346a77a2dde0db131db 100644 (file)
 Display *xkbdpy;
 
 TAILQ_HEAD(bindings_head, Binding) bindings = TAILQ_HEAD_INITIALIZER(bindings);
+SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
 xcb_event_handlers_t evenths;
 
 xcb_window_t root_win;
 xcb_atom_t atoms[9];
 
-
 char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
 int num_screens = 0;
 
@@ -393,6 +393,9 @@ int main(int argc, char *argv[], char *env[]) {
 
         BIND(41, BIND_MOD_1, "f");
 
+        BIND(43, BIND_MOD_1, "s");
+        BIND(26, BIND_MOD_1, "d");
+
         BIND(44, BIND_MOD_1, "h");
         BIND(45, BIND_MOD_1, "j");
         BIND(46, BIND_MOD_1, "k");
index 63e98ff90fa027cf722e66e266145a3f0d194fad..25a968f93a3ed0b30687bbbd5a85f2456159ba69 100644 (file)
@@ -23,6 +23,7 @@
 #include "table.h"
 #include "layout.h"
 #include "util.h"
+#include "xcb.h"
 
 int min(int a, int b) {
         return (a < b ? a : b);
@@ -138,11 +139,74 @@ void set_focus(xcb_connection_t *conn, Client *client) {
         //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
         /* Update last/current client’s titlebar */
         if (old_client != NULL)
-                decorate_window(conn, old_client);
-        decorate_window(conn, client);
+                decorate_window(conn, old_client, old_client->frame, old_client->titlegc, 0);
+        decorate_window(conn, client, client->frame, client->titlegc, 0);
+
+        /* If we’re in stacking mode, we render the container to update changes in the title
+           bars and to raise the focused client */
+        if (client->container->mode == MODE_STACK)
+                render_container(conn, client->container);
+
         xcb_flush(conn);
 }
 
+/*
+ * Switches the layout of the given container taking care of the necessary house-keeping
+ *
+ */
+void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) {
+        if (mode == MODE_STACK) {
+                /* When entering stacking mode, we need to open a window on which we can draw the
+                   title bars of the clients */
+                Rect rect = {container->x, container->y, container->width, 15 /* TODO: exact */ };
+
+                /* Don’t generate events for our new window, it should *not* be managed */
+                uint32_t mask = 0;
+                uint32_t values[2];
+
+                mask |= XCB_CW_OVERRIDE_REDIRECT;
+                values[0] = 1;
+
+                /* We want to know when… */
+                mask |= XCB_CW_EVENT_MASK;
+                values[1] =     XCB_EVENT_MASK_BUTTON_PRESS |   /* …mouse is pressed */
+                                XCB_EVENT_MASK_EXPOSURE;        /* …our window needs to be redrawn */
+
+                struct Stack_Window *stack_win = &(container->stack_win);
+                stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, mask, values);
+
+                /* Generate a graphics context for the titlebar */
+                stack_win->gc = xcb_generate_id(conn);
+                xcb_create_gc(conn, stack_win->gc, stack_win->window, 0, 0);
+
+                stack_win->container = container;
+
+                SLIST_INSERT_HEAD(&stack_wins, stack_win, stack_windows);
+        } else {
+                if (container->mode == MODE_STACK) {
+                        /* When going out of stacking mode, we need to close the window */
+                        struct Stack_Window *stack_win = &(container->stack_win);
+
+                        SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows);
+
+                        xcb_free_gc(conn, stack_win->gc);
+                        xcb_destroy_window(conn, stack_win->window);
+
+                        stack_win->width = -1;
+                        stack_win->height = -1;
+                }
+        }
+        container->mode = mode;
+
+        /* Force reconfiguration of each client */
+        Client *client;
+
+        CIRCLEQ_FOREACH(client, &(container->clients), clients)
+                client->force_reconfigure = true;
+
+        render_layout(conn);
+}
+
 /*
  * Warps the pointer into the given client (in the middle of it, to be specific), therefore
  * selecting it
index 176f26925c60f09330e9245dba45b262a890a0fa..00f59fa119893849577e84e3abd5c051b56bbb38 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -82,3 +82,22 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
 
         return result;
 }
+
+/*
+ * Changes a single value in the graphic context (so one doesn’t have to define an array of values)
+ *
+ */
+void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
+        xcb_change_gc(conn, gc, mask, &value);
+}
+
+/*
+ * Draws a line from x,y to to_x,to_y using the given color
+ *
+ */
+void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
+                   uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) {
+        xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, colorpixel);
+        xcb_point_t points[] = {{x, y}, {to_x, to_y}};
+        xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, points);
+}