diff --git a/TODO b/TODO index ff02d9f1..70023cfe 100644 --- 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 diff --git a/include/data.h b/include/data.h index d5f9f27a..b4db800a 100644 --- a/include/data.h +++ b/include/data.h @@ -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; diff --git a/include/i3.h b/include/i3.h index 53ebb69c..2861e01c 100644 --- a/include/i3.h +++ b/include/i3.h @@ -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; diff --git a/include/layout.h b/include/layout.h index 4d7f0d9c..fe2c2d9a 100644 --- a/include/layout.h +++ b/include/layout.h @@ -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 diff --git a/include/util.h b/include/util.h index b2511287..e1728c55 100644 --- a/include/util.h +++ b/include/util.h @@ -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); diff --git a/include/xcb.h b/include/xcb.h index bc625629..1f372c36 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -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 diff --git a/src/commands.c b/src/commands.c index 7266a327..930696ee 100644 --- a/src/commands.c +++ b/src/commands.c @@ -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 ? */ if (command[0] == 'w') { /* TODO: implement */ diff --git a/src/handlers.c b/src/handlers.c index 17693f82..f31e23bd 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -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) { - Client *client = table_get(byParent, e->window); - if(!client || e->count != 0) + 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 == 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; } diff --git a/src/layout.c b/src/layout.c index fb11b779..05303884 100644 --- a/src/layout.c +++ b/src/layout.c @@ -14,6 +14,7 @@ #include #include #include +#include #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; diff --git a/src/mainx.c b/src/mainx.c index baaaa290..e49362a0 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -47,12 +47,12 @@ 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"); diff --git a/src/util.c b/src/util.c index 63e98ff9..25a968f9 100644 --- a/src/util.c +++ b/src/util.c @@ -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 diff --git a/src/xcb.c b/src/xcb.c index 176f2692..00f59fa1 100644 --- 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); +}