From 1a0817eb3912f25b75cfe61854920c52c78493d2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 Feb 2009 00:18:13 +0100 Subject: [PATCH] Correctly handle _NET_WM_WINDOW_TYPE == _NET_WM_WINDOW_TYPE_DOCK (for dzen2 -dock) --- include/data.h | 14 ++++++ include/handlers.h | 1 + include/i3.h | 2 +- include/xcb.h | 14 ++++-- src/handlers.c | 7 +++ src/layout.c | 115 ++++++++++++++++++++++++++++++++++----------- src/mainx.c | 45 ++++++++++++++++-- src/table.c | 1 + 8 files changed, 162 insertions(+), 37 deletions(-) diff --git a/include/data.h b/include/data.h index c1e10613..d5f9f27a 100644 --- a/include/data.h +++ b/include/data.h @@ -63,6 +63,9 @@ struct Workspace { Client *fullscreen_client; + /* Contains all clients with _NET_WM_WINDOW_TYPE == _NET_WM_WINDOW_TYPE_DOCK */ + SLIST_HEAD(dock_clients_head, Client) dock_clients; + /* Backpointer to the screen this workspace is on */ i3Screen *screen; @@ -125,6 +128,10 @@ struct Client { /* x, y, width, height */ Rect rect; + /* Height which was determined by reading the _NET_WM_STRUT_PARTIAL top/bottom of the screen + reservation */ + int desired_height; + /* Name */ char *name; int name_len; @@ -132,6 +139,12 @@ struct Client { /* fullscreen is pretty obvious */ bool fullscreen; + enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; + + /* If a client is set as a dock, it is placed at the very bottom of the screen and its + requested size is used */ + bool dock; + /* After leaving fullscreen mode, a client needs to be reconfigured (configuration = setting X, Y, width and height). By setting the force_reconfigure flag, render_layout() will reconfigure the client. */ @@ -148,6 +161,7 @@ struct Client { /* The following entry provides the necessary list pointers to use Client with LIST_* macros */ CIRCLEQ_ENTRY(Client) clients; + SLIST_ENTRY(Client) dock_clients; }; /* diff --git a/include/handlers.h b/include/handlers.h index bd0c9d78..0116d42b 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -20,5 +20,6 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event); int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event); +int window_type_handler(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); #endif diff --git a/include/i3.h b/include/i3.h index 46687113..53ebb69c 100644 --- a/include/i3.h +++ b/include/i3.h @@ -23,6 +23,6 @@ extern TAILQ_HEAD(bindings_head, Binding) bindings; extern xcb_event_handlers_t evenths; extern char *pattern; extern int num_screens; -extern xcb_atom_t atoms[6]; +extern xcb_atom_t atoms[9]; #endif diff --git a/include/xcb.h b/include/xcb.h index d234ac20..bc625629 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -16,11 +16,15 @@ #define _NET_WM_STATE_TOGGLE 2 enum { _NET_SUPPORTED = 0, - _NET_SUPPORTING_WM_CHECK = 1, - _NET_WM_NAME = 2, - _NET_WM_STATE_FULLSCREEN = 3, - _NET_WM_STATE = 4, - UTF8_STRING = 5 + _NET_SUPPORTING_WM_CHECK, + _NET_WM_NAME, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE, + _NET_WM_WINDOW_TYPE, + _NET_WM_WINDOW_TYPE_DOCK, + _NET_WM_DESKTOP, + _NET_WM_STRUT_PARTIAL, + UTF8_STRING }; uint32_t get_colorpixel(xcb_connection_t *conn, xcb_window_t window, char *hex); diff --git a/src/handlers.c b/src/handlers.c index f1a247ee..66205879 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -377,3 +377,10 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message return 1; } + +int window_type_handler(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t atom, xcb_get_property_reply_t *property) { + /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s + before changing this property. */ + printf("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n"); +} diff --git a/src/layout.c b/src/layout.c index 48fcaf36..bf867cca 100644 --- a/src/layout.c +++ b/src/layout.c @@ -128,18 +128,73 @@ void decorate_window(xcb_connection_t *conn, Client *client) { free(label); } +/* + * Pushes the client’s x and y coordinates to X11 + * + */ +static void reposition_client(xcb_connection_t *connection, Client *client) { + printf("frame needs to be pushed to %dx%d\n", 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 */ + xcb_configure_window(connection, client->frame, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); +} + +/* + * Pushes the client’s width/height to X11 and resizes the child window + * + */ +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); + xcb_configure_window(connection, client->frame, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + &(client->rect.width)); + + /* Adjust the position of the child inside its frame. + * The coordinates of the child are relative to its frame, we + * add a border of 2 pixel to each value */ + uint32_t mask = XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT; + Rect rect; + if (client->titlebar_position == TITLEBAR_OFF) { + rect.x = 0; + rect.y = 0; + rect.width = client->rect.width; + rect.height = client->rect.height; + } else { + rect.x = 2; + rect.y = font->height + 2 + 2; + rect.width = client->rect.width - (2 + 2); + rect.height = client->rect.height - ((font->height + 2 + 2) + 2); + } + + printf("child will be at %dx%d with size %dx%d\n", rect.x, rect.y, rect.width, rect.height); + + xcb_configure_window(connection, client->child, mask, &(rect.x)); +} + static void render_container(xcb_connection_t *connection, Container *container) { Client *client; - i3Font *font = load_font(connection, pattern); if (container->mode == MODE_DEFAULT) { int num_clients = 0; CIRCLEQ_FOREACH(client, &(container->clients), clients) - num_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) { + /* TODO: currently, clients are assigned to the current container. + Therefore, we need to skip them here. Does anything harmful happen + if clients *do not* have a container. Is this the more desired + situation? Let’s find out… */ + if (client->dock) + continue; /* TODO: at the moment, every column/row is screen / num_cols. This * needs to be changed to "percentage of the screen" by * default and adjustable by the user if necessary. @@ -151,38 +206,14 @@ static void render_container(xcb_connection_t *connection, Container *container) (client->rect.x != (client->rect.x = container->x)) | (client->rect.y != (client->rect.y = container->y + (container->height / num_clients) * current_client))) { - printf("frame needs to be pushed to %dx%d\n", 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 */ - xcb_configure_window(connection, client->frame, - XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); + reposition_client(connection, client); } /* TODO: vertical default layout */ if (client->force_reconfigure | (client->rect.width != (client->rect.width = container->width)) | (client->rect.height != (client->rect.height = container->height / num_clients))) { - printf("resizing client to %d x %d\n", client->rect.width, client->rect.height); - xcb_configure_window(connection, client->frame, - XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, - &(client->rect.width)); - - /* Adjust the position of the child inside its frame. - * The coordinates of the child are relative to its frame, we - * add a border of 2 pixel to each value */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; - uint32_t values[4] = {2, /* x */ - font->height + 2 + 2, /* y */ - client->rect.width - (2 + 2), /* width */ - client->rect.height - ((font->height + 2 + 2) + 2)}; /* height */ - - printf("child will be at %dx%d with size %dx%d\n", - values[0], values[1], values[2], values[3]); - - xcb_configure_window(connection, client->child, mask, values); + resize_client(connection, client); } if (client->force_reconfigure) @@ -195,6 +226,24 @@ static void render_container(xcb_connection_t *connection, Container *container) } } +static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int height) { + Client *client; + SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) { + if (client->force_reconfigure | + (client->rect.x != (client->rect.x = 0)) | + (client->rect.y != (client->rect.y = height))) + reposition_client(connection, client); + + if (client->force_reconfigure | + (client->rect.width != (client->rect.width = width)) | + (client->rect.height != (client->rect.height = client->desired_height))) + resize_client(connection, client); + + client->force_reconfigure = false; + height += client->desired_height; + } +} + void render_layout(xcb_connection_t *connection) { i3Screen *screen; @@ -206,11 +255,19 @@ void render_layout(xcb_connection_t *connection) { if (r_ws->fullscreen_client != NULL) /* This is easy: A client has entered fullscreen mode, so we don’t render at all */ continue; + int width = r_ws->rect.width; int height = r_ws->rect.height; int x = r_ws->rect.x; int y = r_ws->rect.y; + Client *client; + SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) { + printf("got dock client: %p\n", client); + printf("it wants to be this height: %d\n", client->desired_height); + height -= client->desired_height; + } + printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols); printf("each of them therefore is %d px width and %d px height\n", width / r_ws->cols, height / r_ws->rows); @@ -240,6 +297,8 @@ void render_layout(xcb_connection_t *connection) { x += container->width; } + + render_bars(connection, r_ws, width, height); } xcb_flush(connection); diff --git a/src/mainx.c b/src/mainx.c index 229e84e4..ca714ead 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -50,7 +50,7 @@ TAILQ_HEAD(bindings_head, Binding) bindings = TAILQ_HEAD_INITIALIZER(bindings); xcb_event_handlers_t evenths; xcb_window_t root_win; -xcb_atom_t atoms[6]; +xcb_atom_t atoms[9]; char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1"; @@ -122,6 +122,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, xcb_visualid_t visual, xcb_window_t root, uint8_t depth, int16_t x, int16_t y, uint16_t width, uint16_t height) { + xcb_get_property_cookie_t wm_type_cookie, strut_cookie; + + /* Place requests for propertys ASAP */ + wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); + strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); + Client *new = table_get(byChild, child); if (new == NULL) { /* TODO: When does this happen for existing clients? Is that a bug? */ @@ -206,6 +212,32 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Focus the new window */ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); + /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */ + xcb_atom_t *atom; + xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); + if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { + for (int i = 0; i < xcb_get_property_value_length(preply); i++) + if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { + printf("Window is a dock.\n"); + new->dock = true; + new->titlebar_position = TITLEBAR_OFF; + new->force_reconfigure = true; + SLIST_INSERT_HEAD(&(new->container->workspace->dock_clients), new, dock_clients); + } + } + + /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */ + uint32_t *strut; + preply = xcb_get_property_reply(conn, strut_cookie, NULL); + if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) { + /* We only use a subset of the provided values, namely the reserved space at the top/bottom + of the screen. This is because the only possibility for bars is at to be at the top/bottom + with maximum horizontal size. + TODO: bars at the top */ + new->desired_height = strut[3]; + printf("the client wants to be %d pixels height\n", new->desired_height); + } + render_layout(conn); } @@ -328,10 +360,17 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(_NET_WM_STATE_FULLSCREEN); GET_ATOM(_NET_SUPPORTING_WM_CHECK); GET_ATOM(_NET_WM_NAME); - GET_ATOM(UTF8_STRING); GET_ATOM(_NET_WM_STATE); + GET_ATOM(_NET_WM_WINDOW_TYPE); + GET_ATOM(_NET_WM_DESKTOP); + GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); + GET_ATOM(_NET_WM_STRUT_PARTIAL); + GET_ATOM(UTF8_STRING); + + xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL); + /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */ - check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 5, atoms), "Could not set _NET_SUPPORTED"); + check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED"); xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); diff --git a/src/table.c b/src/table.c index c0f75e16..6d8d1ca9 100644 --- a/src/table.c +++ b/src/table.c @@ -39,6 +39,7 @@ void init_table() { for (int i = 0; i < 10; i++) { workspaces[i].screen = NULL; + SLIST_INIT(&(workspaces[i].dock_clients)); expand_table_cols(&(workspaces[i])); expand_table_rows(&(workspaces[i])); } -- 2.39.5