diff --git a/include/i3.h b/include/i3.h index 06f423a0..4f285b7c 100644 --- a/include/i3.h +++ b/include/i3.h @@ -32,9 +32,4 @@ extern xcb_event_handlers_t evenths; extern int num_screens; extern xcb_atom_t atoms[NUM_ATOMS]; -void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa); -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); - #endif diff --git a/include/manage.h b/include/manage.h new file mode 100644 index 00000000..52816e37 --- /dev/null +++ b/include/manage.h @@ -0,0 +1,42 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * (c) 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include + +#include "data.h" + +#ifndef _MANAGE_H +#define _MANAGE_H + +/** + * Go through all existing windows (if the window manager is restarted) and manage them + * + */ +void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root); + +/** + * Do some sanity checks and then reparent the window. + * + */ +void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, + xcb_window_t window, window_attributes_t wa); + +/** + * reparent_window() gets called when a new window was opened and becomes a child of the root + * window, or it gets called by us when we manage the already existing windows at startup. + * + * Essentially, this is the point where we take over control. + * + */ +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); + +#endif diff --git a/src/handlers.c b/src/handlers.c index 7dae1149..8316fc34 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -33,6 +33,7 @@ #include "queue.h" #include "resize.h" #include "client.h" +#include "manage.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 diff --git a/src/mainx.c b/src/mainx.c index dbce26a1..60cd6a0f 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -42,6 +42,7 @@ #include "util.h" #include "xcb.h" #include "xinerama.h" +#include "manage.h" /* This is the path to i3, copied from argv[0] when starting up */ char **start_argv; @@ -68,331 +69,6 @@ xcb_atom_t atoms[NUM_ATOMS]; int num_screens = 0; -/* - * Do some sanity checks and then reparent the window. - * - */ -void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) { - LOG("managing window.\n"); - xcb_drawable_t d = { window }; - xcb_get_geometry_cookie_t geomc; - xcb_get_geometry_reply_t *geom; - xcb_get_window_attributes_reply_t *attr = 0; - - if (wa.tag == TAG_COOKIE) { - /* Check if the window is mapped (it could be not mapped when intializing and - calling manage_window() for every window) */ - if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) - return; - - if (attr->map_state != XCB_MAP_STATE_VIEWABLE) - goto out; - - wa.tag = TAG_VALUE; - wa.u.override_redirect = attr->override_redirect; - } - - /* Don’t manage clients with the override_redirect flag */ - if (wa.u.override_redirect) - goto out; - - /* Check if the window is already managed */ - if (table_get(&by_child, window)) - goto out; - - /* Get the initial geometry (position, size, …) */ - geomc = xcb_get_geometry(conn, d); - if (!attr) { - wa.tag = TAG_COOKIE; - wa.u.cookie = xcb_get_window_attributes(conn, window); - if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) - return; - } - if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) - goto out; - - /* Reparent the window and add it to our list of managed windows */ - reparent_window(conn, window, attr->visual, geom->root, geom->depth, - geom->x, geom->y, geom->width, geom->height); - - /* Generate callback events for every property we watch */ - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); - - free(geom); -out: - free(attr); - return; -} - -/* - * reparent_window() gets called when a new window was opened and becomes a child of the root - * window, or it gets called by us when we manage the already existing windows at startup. - * - * Essentially, this is the point where we take over control. - * - */ -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, state_cookie, - utf8_title_cookie, title_cookie, class_cookie; - uint32_t mask = 0; - uint32_t values[3]; - - /* We are interested in property changes */ - mask = XCB_CW_EVENT_MASK; - values[0] = CHILD_EVENT_MASK; - xcb_change_window_attributes(conn, child, mask, values); - - /* Map the window first to avoid flickering */ - xcb_map_window(conn, child); - - /* Place requests for properties 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); - state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); - utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); - title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); - class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); - - Client *new = table_get(&by_child, child); - - /* Events for already managed windows should already be filtered in manage_window() */ - assert(new == NULL); - - LOG("reparenting new client\n"); - new = calloc(sizeof(Client), 1); - new->force_reconfigure = true; - - /* Update the data structures */ - Client *old_focused = CUR_CELL->currently_focused; - - new->container = CUR_CELL; - new->workspace = new->container->workspace; - - new->frame = xcb_generate_id(conn); - new->child = child; - new->rect.width = width; - new->rect.height = height; - - mask = 0; - - /* Don’t generate events for our new window, it should *not* be managed */ - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* We want to know when… */ - mask |= XCB_CW_EVENT_MASK; - values[1] = FRAME_EVENT_MASK; - - LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame); - - i3Font *font = load_font(conn, config.font); - width = min(width, c_ws->rect.x + c_ws->rect.width); - height = min(height, c_ws->rect.y + c_ws->rect.height); - - Rect framerect = {x, y, - width + 2 + 2, /* 2 px border at each side */ - height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ - - /* Yo dawg, I heard you like windows, so I create a window around your window… */ - new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); - - /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t. - * Also, xprop(1) needs that to work. */ - long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); - - /* Put the client inside the save set. Upon termination (whether killed or normal exit - does not matter) of the window manager, these clients will be correctly reparented - to their most closest living ancestor (= cleanup) */ - xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child); - - /* Generate a graphics context for the titlebar */ - new->titlegc = xcb_generate_id(conn); - xcb_create_gc(conn, new->titlegc, new->frame, 0, 0); - - /* Moves the original window into the new frame we've created for it */ - new->awaiting_useless_unmap = true; - xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); - if (xcb_request_check(conn, cookie) != NULL) { - LOG("Could not reparent the window, aborting\n"); - xcb_destroy_window(conn, new->frame); - free(new); - return; - } - - /* Put our data structure (Client) into the table */ - table_put(&by_parent, new->frame, new); - table_put(&by_child, child, new); - - /* We need to grab the mouse buttons for click to focus */ - xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - 1 /* left mouse button */, - XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); - - /* 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]) - continue; - LOG("Window is a dock.\n"); - new->dock = true; - new->titlebar_position = TITLEBAR_OFF; - new->force_reconfigure = true; - new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); - } - } - - if (new->dock) { - /* 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]; - if (new->desired_height == 0) { - LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height); - new->desired_height = height; - } - LOG("the client wants to be %d pixels high\n", new->desired_height); - } else { - LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height); - new->desired_height = height; - } - } else { - /* If it’s not a dock, we can check on which workspace we should put it. */ - - /* Firstly, we need to get the window’s class / title. We asked for the properties at the - * top of this function, get them now and pass them to our callback function for window class / title - * changes. It is important that the client was already inserted into the by_child table, - * because the callbacks won’t work otherwise. */ - preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL); - handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply); - - preply = xcb_get_property_reply(conn, title_cookie, NULL); - handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply); - - preply = xcb_get_property_reply(conn, class_cookie, NULL); - handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); - - LOG("DEBUG: should have all infos now\n"); - struct Assignment *assign; - TAILQ_FOREACH(assign, &assignments, assignments) { - if (get_matching_client(conn, assign->windowclass_title, new) == NULL) - continue; - - LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", - assign->windowclass_title, assign->workspace); - - if (c_ws->screen->current_workspace == (assign->workspace-1)) { - LOG("We are already there, no need to do anything\n"); - break; - } - - LOG("Changin container/workspace and unmapping the client\n"); - Workspace *t_ws = &(workspaces[assign->workspace-1]); - if (t_ws->screen == NULL) { - LOG("initializing new workspace, setting num to %d\n", assign->workspace); - t_ws->screen = c_ws->screen; - /* Copy the dimensions from the virtual screen */ - memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); - } - - new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; - new->workspace = t_ws; - xcb_unmap_window(conn, new->frame); - break; - } - } - - if (CUR_CELL->workspace->fullscreen_client != NULL) { - if (new->container == CUR_CELL) { - /* If we are in fullscreen, we should lower the window to not be annoying */ - uint32_t values[] = { XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - } - } else if (!new->dock) { - /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ - if (new->container->workspace->fullscreen_client == NULL) { - new->container->currently_focused = new; - if (new->container == CUR_CELL) - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); - } - } - - /* Insert into the currently active container, if it’s not a dock window */ - if (!new->dock) { - /* Insert after the old active client, if existing. If it does not exist, the - container is empty and it does not matter, where we insert it */ - if (old_focused != NULL && !old_focused->dock) - CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); - else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); - - SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); - } - - /* Check if the window already got the fullscreen hint set */ - xcb_atom_t *state; - if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && - (state = xcb_get_property_value(preply)) != NULL) - /* Check all set _NET_WM_STATEs */ - for (int i = 0; i < xcb_get_property_value_length(preply); i++) { - if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) - continue; - /* If the window got the fullscreen state, we just toggle fullscreen - and don’t event bother to redraw the layout – that would not change - anything anyways */ - toggle_fullscreen(conn, new); - return; - } - - render_layout(conn); -} - -/* - * Go through all existing windows (if the window manager is restarted) and manage them - * - */ -void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) { - xcb_query_tree_reply_t *reply; - int i, len; - xcb_window_t *children; - xcb_get_window_attributes_cookie_t *cookies; - - /* Get the tree of windows whose parent is the root window (= all) */ - if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) - return; - - len = xcb_query_tree_children_length(reply); - cookies = smalloc(len * sizeof(*cookies)); - - /* Request the window attributes for every window */ - children = xcb_query_tree_children(reply); - for(i = 0; i < len; ++i) - cookies[i] = xcb_get_window_attributes(conn, children[i]); - - /* Call manage_window with the attributes for every window */ - for(i = 0; i < len; ++i) { - window_attributes_t wa = { TAG_COOKIE, { cookies[i] } }; - manage_window(prophs, conn, children[i], wa); - } - - free(reply); - free(cookies); -} - int main(int argc, char *argv[], char *env[]) { int i, screens, opt; char *override_configpath = NULL; diff --git a/src/manage.c b/src/manage.c new file mode 100644 index 00000000..d330d88f --- /dev/null +++ b/src/manage.c @@ -0,0 +1,354 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * src/manage.c: Contains all functions for initially managing new windows + * (or existing ones on restart). + * + */ +#include +#include +#include + +#include +#include + +#include "xcb.h" +#include "data.h" +#include "util.h" +#include "i3.h" +#include "table.h" +#include "config.h" +#include "handlers.h" +#include "layout.h" +#include "manage.h" + +/* + * Go through all existing windows (if the window manager is restarted) and manage them + * + */ +void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) { + xcb_query_tree_reply_t *reply; + int i, len; + xcb_window_t *children; + xcb_get_window_attributes_cookie_t *cookies; + + /* Get the tree of windows whose parent is the root window (= all) */ + if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) + return; + + len = xcb_query_tree_children_length(reply); + cookies = smalloc(len * sizeof(*cookies)); + + /* Request the window attributes for every window */ + children = xcb_query_tree_children(reply); + for(i = 0; i < len; ++i) + cookies[i] = xcb_get_window_attributes(conn, children[i]); + + /* Call manage_window with the attributes for every window */ + for(i = 0; i < len; ++i) { + window_attributes_t wa = { TAG_COOKIE, { cookies[i] } }; + manage_window(prophs, conn, children[i], wa); + } + + free(reply); + free(cookies); +} + +/* + * Do some sanity checks and then reparent the window. + * + */ +void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) { + LOG("managing window.\n"); + xcb_drawable_t d = { window }; + xcb_get_geometry_cookie_t geomc; + xcb_get_geometry_reply_t *geom; + xcb_get_window_attributes_reply_t *attr = 0; + + if (wa.tag == TAG_COOKIE) { + /* Check if the window is mapped (it could be not mapped when intializing and + calling manage_window() for every window) */ + if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) + return; + + if (attr->map_state != XCB_MAP_STATE_VIEWABLE) + goto out; + + wa.tag = TAG_VALUE; + wa.u.override_redirect = attr->override_redirect; + } + + /* Don’t manage clients with the override_redirect flag */ + if (wa.u.override_redirect) + goto out; + + /* Check if the window is already managed */ + if (table_get(&by_child, window)) + goto out; + + /* Get the initial geometry (position, size, …) */ + geomc = xcb_get_geometry(conn, d); + if (!attr) { + wa.tag = TAG_COOKIE; + wa.u.cookie = xcb_get_window_attributes(conn, window); + if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) + return; + } + if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) + goto out; + + /* Reparent the window and add it to our list of managed windows */ + reparent_window(conn, window, attr->visual, geom->root, geom->depth, + geom->x, geom->y, geom->width, geom->height); + + /* Generate callback events for every property we watch */ + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); + + free(geom); +out: + free(attr); + return; +} + +/* + * reparent_window() gets called when a new window was opened and becomes a child of the root + * window, or it gets called by us when we manage the already existing windows at startup. + * + * Essentially, this is the point where we take over control. + * + */ +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, state_cookie, + utf8_title_cookie, title_cookie, class_cookie; + uint32_t mask = 0; + uint32_t values[3]; + + /* We are interested in property changes */ + mask = XCB_CW_EVENT_MASK; + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, child, mask, values); + + /* Map the window first to avoid flickering */ + xcb_map_window(conn, child); + + /* Place requests for properties 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); + state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); + utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); + title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); + class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); + + Client *new = table_get(&by_child, child); + + /* Events for already managed windows should already be filtered in manage_window() */ + assert(new == NULL); + + LOG("reparenting new client\n"); + new = calloc(sizeof(Client), 1); + new->force_reconfigure = true; + + /* Update the data structures */ + Client *old_focused = CUR_CELL->currently_focused; + + new->container = CUR_CELL; + new->workspace = new->container->workspace; + + new->frame = xcb_generate_id(conn); + new->child = child; + new->rect.width = width; + new->rect.height = height; + + mask = 0; + + /* Don’t generate events for our new window, it should *not* be managed */ + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[0] = 1; + + /* We want to know when… */ + mask |= XCB_CW_EVENT_MASK; + values[1] = FRAME_EVENT_MASK; + + LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame); + + i3Font *font = load_font(conn, config.font); + width = min(width, c_ws->rect.x + c_ws->rect.width); + height = min(height, c_ws->rect.y + c_ws->rect.height); + + Rect framerect = {x, y, + width + 2 + 2, /* 2 px border at each side */ + height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ + + /* Yo dawg, I heard you like windows, so I create a window around your window… */ + new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + + /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t. + * Also, xprop(1) needs that to work. */ + long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + + /* Put the client inside the save set. Upon termination (whether killed or normal exit + does not matter) of the window manager, these clients will be correctly reparented + to their most closest living ancestor (= cleanup) */ + xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child); + + /* Generate a graphics context for the titlebar */ + new->titlegc = xcb_generate_id(conn); + xcb_create_gc(conn, new->titlegc, new->frame, 0, 0); + + /* Moves the original window into the new frame we've created for it */ + new->awaiting_useless_unmap = true; + xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); + if (xcb_request_check(conn, cookie) != NULL) { + LOG("Could not reparent the window, aborting\n"); + xcb_destroy_window(conn, new->frame); + free(new); + return; + } + + /* Put our data structure (Client) into the table */ + table_put(&by_parent, new->frame, new); + table_put(&by_child, child, new); + + /* We need to grab the mouse buttons for click to focus */ + xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, + 1 /* left mouse button */, + XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + + /* 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]) + continue; + LOG("Window is a dock.\n"); + new->dock = true; + new->titlebar_position = TITLEBAR_OFF; + new->force_reconfigure = true; + new->container = NULL; + SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); + } + } + + if (new->dock) { + /* 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]; + if (new->desired_height == 0) { + LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height); + new->desired_height = height; + } + LOG("the client wants to be %d pixels high\n", new->desired_height); + } else { + LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height); + new->desired_height = height; + } + } else { + /* If it’s not a dock, we can check on which workspace we should put it. */ + + /* Firstly, we need to get the window’s class / title. We asked for the properties at the + * top of this function, get them now and pass them to our callback function for window class / title + * changes. It is important that the client was already inserted into the by_child table, + * because the callbacks won’t work otherwise. */ + preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL); + handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply); + + preply = xcb_get_property_reply(conn, title_cookie, NULL); + handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply); + + preply = xcb_get_property_reply(conn, class_cookie, NULL); + handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); + + LOG("DEBUG: should have all infos now\n"); + struct Assignment *assign; + TAILQ_FOREACH(assign, &assignments, assignments) { + if (get_matching_client(conn, assign->windowclass_title, new) == NULL) + continue; + + LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", + assign->windowclass_title, assign->workspace); + + if (c_ws->screen->current_workspace == (assign->workspace-1)) { + LOG("We are already there, no need to do anything\n"); + break; + } + + LOG("Changin container/workspace and unmapping the client\n"); + Workspace *t_ws = &(workspaces[assign->workspace-1]); + if (t_ws->screen == NULL) { + LOG("initializing new workspace, setting num to %d\n", assign->workspace); + t_ws->screen = c_ws->screen; + /* Copy the dimensions from the virtual screen */ + memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); + } + + new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; + new->workspace = t_ws; + xcb_unmap_window(conn, new->frame); + break; + } + } + + if (CUR_CELL->workspace->fullscreen_client != NULL) { + if (new->container == CUR_CELL) { + /* If we are in fullscreen, we should lower the window to not be annoying */ + uint32_t values[] = { XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + } + } else if (!new->dock) { + /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ + if (new->container->workspace->fullscreen_client == NULL) { + new->container->currently_focused = new; + if (new->container == CUR_CELL) + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); + } + } + + /* Insert into the currently active container, if it’s not a dock window */ + if (!new->dock) { + /* Insert after the old active client, if existing. If it does not exist, the + container is empty and it does not matter, where we insert it */ + if (old_focused != NULL && !old_focused->dock) + CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); + else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); + + SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); + } + + /* Check if the window already got the fullscreen hint set */ + xcb_atom_t *state; + if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && + (state = xcb_get_property_value(preply)) != NULL) + /* Check all set _NET_WM_STATEs */ + for (int i = 0; i < xcb_get_property_value_length(preply); i++) { + if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) + continue; + /* If the window got the fullscreen state, we just toggle fullscreen + and don’t event bother to redraw the layout – that would not change + anything anyways */ + toggle_fullscreen(conn, new); + return; + } + + render_layout(conn); +}