-all: hacking-howto.html debugging.html
+all: hacking-howto.html debugging.html userguide.html
hacking-howto.html: hacking-howto
asciidoc -a toc -n $<
debugging.html: debugging
asciidoc -n $<
+userguide.html: userguide
+ asciidoc -a toc -n $<
clean:
rm -f */*.{aux,log,toc,bm,pdf,dvi}
--- /dev/null
+i3 User’s Guide
+===============
+Michael Stapelberg <michael+i3@stapelberg.de>
+May 2009
+
+This document contains all information you need to configuring and using the i3 window
+manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out.
+
+== Configuring i3
+
+TODO: document the other options, implement variables before
+
+terminal::
+ Specifies the terminal emulator program you prefer. It will be started by default when
+ you press Mod1+Enter, but you can overwrite this. Refer to it as +$terminal+ to keep things
+ modular.
+font::
+ Specifies the default font you want i3 to use. Use an X core font descriptor here, like
+ +-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can use +xfontsel(1)+
+ to pick one.
+
+=== Keyboard bindings
+
+TODO
+
+*Syntax*:
+--------------------------------
+bind [Modifiers+]keycode command
+--------------------------------
+
+*Examples*:
+--------------------------------
+# Fullscreen
+bind Mod1+41 f
+
+# Restart
+bind Mod1+Shift+27 restart
+--------------------------------
+
+=== Automatically putting clients on specific workspaces
+
+It is recommended that you match on window classes whereever possible because some applications
+first create their window and then care about setting the correct title. Firefox with Vimperator
+comes to mind, as the window starts up being named Firefox and only when Vimperator is loaded,
+the title changes. As i3 will get the title as soon as the application maps the window (mapping
+means actually displaying it on the screen), you’d need to have to match on Firefox in this case.
+
+*Syntax*:
+----------------------------------------------------
+assign ["]window class[/window title]["] [→] workspace
+----------------------------------------------------
+
+*Examples*:
+----------------------
+assign urxvt 2
+assign urxvt → 2
+assign "urxvt" → 2
+assign "urxvt/VIM" → 3
+----------------------
--- /dev/null
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * (c) 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ */
+#include <xcb/xcb.h>
+
+#include "data.h"
+
+#ifndef _CLIENT_H
+#define _CLIENT_H
+
+/**
+ * Removes the given client from the container, either because it will be inserted into another
+ * one or because it was unmapped
+ *
+ */
+void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container);
+
+/**
+ * Warps the pointer into the given client (in the middle of it, to be specific), therefore
+ * selecting it
+ *
+ */
+void client_warp_pointer_into(xcb_connection_t *conn, Client *client);
+
+/**
+ * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
+ *
+ */
+void client_kill(xcb_connection_t *conn, Client *window);
+
+/**
+ * Checks if the given window class and title match the given client
+ * Window title is passed as "normal" string and as UCS-2 converted string for
+ * matching _NET_WM_NAME capable clients as well as those using legacy hints.
+ *
+ */
+bool client_matches_class_name(Client *client, char *to_class, char *to_title,
+ char *to_title_ucs, int to_title_ucs_len);
+
+#endif
typedef struct Container Container;
typedef struct Client Client;
typedef struct Binding Binding;
-typedef struct Autostart Autostart;
typedef struct Workspace Workspace;
typedef struct Rect Rect;
typedef struct Screen i3Screen;
TAILQ_ENTRY(Autostart) autostarts;
};
+/*
+ * Holds an assignment for a given window class/title to a specific workspace
+ * (see src/config.c)
+ *
+ */
+struct Assignment {
+ char *windowclass_title;
+ int workspace;
+ TAILQ_ENTRY(Assignment) assignments;
+};
+
/*
* Data structure for cached font information:
* - font id in X11 (load it once)
extern Display *xkbdpy;
extern TAILQ_HEAD(bindings_head, Binding) bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
+extern TAILQ_HEAD(assignments_head, Assignment) assignments;
extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
extern xcb_event_handlers_t evenths;
extern int num_screens;
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-/**
- * Removes the given client from the container, either because it will be inserted into another
- * one or because it was unmapped
- *
- */
-void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container);
-
/**
* Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client.
*/
void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
-/**
- * Warps the pointer into the given client (in the middle of it, to be specific), therefore
- * selecting it
- *
- */
-void warp_pointer_into(xcb_connection_t *conn, Client *client);
-
/**
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
void toggle_fullscreen(xcb_connection_t *conn, Client *client);
/**
- * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
+ * Gets the first matching client for the given window class/window title.
+ * If the paramater specific is set to a specific client, only this one
+ * will be checked.
*
*/
-void kill_window(xcb_connection_t *conn, Client *window);
+Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
+ Client *specific);
#endif
--- /dev/null
+/*
+ * vim:ts=8:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ *
+ * © 2009 Michael Stapelberg and contributors
+ *
+ * See file LICENSE for license information.
+ *
+ * client.c: holds all client-specific functions
+ *
+ */
+#include <stdlib.h>
+#include <string.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+
+#include "data.h"
+#include "i3.h"
+#include "xcb.h"
+#include "util.h"
+#include "queue.h"
+
+/*
+ * Removes the given client from the container, either because it will be inserted into another
+ * one or because it was unmapped
+ *
+ */
+void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container) {
+ CIRCLEQ_REMOVE(&(container->clients), client, clients);
+
+ SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
+
+ /* If the container will be empty now and is in stacking mode, we need to
+ unmap the stack_win */
+ if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
+ struct Stack_Window *stack_win = &(container->stack_win);
+ stack_win->rect.height = 0;
+ xcb_unmap_window(conn, stack_win->window);
+ }
+}
+
+/*
+ * Warps the pointer into the given client (in the middle of it, to be specific), therefore
+ * selecting it
+ *
+ */
+void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
+ int mid_x = client->rect.width / 2,
+ mid_y = client->rect.height / 2;
+ xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
+}
+
+/*
+ * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
+ *
+ */
+static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
+ xcb_get_property_cookie_t cookie;
+ xcb_get_wm_protocols_reply_t protocols;
+ bool result = false;
+
+ cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
+ if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
+ return false;
+
+ /* Check if the client’s protocols have the requested atom set */
+ for (uint32_t i = 0; i < protocols.atoms_len; i++)
+ if (protocols.atoms[i] == atom)
+ result = true;
+
+ xcb_get_wm_protocols_reply_wipe(&protocols);
+
+ return result;
+}
+
+/*
+ * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
+ *
+ */
+void client_kill(xcb_connection_t *conn, Client *window) {
+ /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
+ if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
+ LOG("Killing window the hard way\n");
+ xcb_kill_client(conn, window->child);
+ return;
+ }
+
+ xcb_client_message_event_t ev;
+
+ memset(&ev, 0, sizeof(xcb_client_message_event_t));
+
+ ev.response_type = XCB_CLIENT_MESSAGE;
+ ev.window = window->child;
+ ev.type = atoms[WM_PROTOCOLS];
+ ev.format = 32;
+ ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
+ ev.data.data32[1] = XCB_CURRENT_TIME;
+
+ LOG("Sending WM_DELETE to the client\n");
+ xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
+ xcb_flush(conn);
+}
+
+/*
+ * Checks if the given window class and title match the given client
+ * Window title is passed as "normal" string and as UCS-2 converted string for
+ * matching _NET_WM_NAME capable clients as well as those using legacy hints.
+ *
+ */
+bool client_matches_class_name(Client *client, char *to_class, char *to_title,
+ char *to_title_ucs, int to_title_ucs_len) {
+ /* Check if the given class is part of the window class */
+ if (strcasestr(client->window_class, to_class) == NULL)
+ return false;
+
+ /* If no title was given, we’re done */
+ if (to_title == NULL)
+ return true;
+
+ if (client->name_len > -1) {
+ /* UCS-2 converted window titles */
+ if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
+ return false;
+ } else {
+ /* Legacy hints */
+ if (strcasestr(client->name, to_title) == NULL)
+ return false;
+ }
+
+ return true;
+}
#include "layout.h"
#include "i3.h"
#include "xinerama.h"
+#include "client.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, we’re done */
}
/* Remove it from the old container and put it into the new one */
- remove_client_from_container(conn, current_client, container);
+ client_remove_from_container(conn, current_client, container);
if (new->currently_focused != NULL)
CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
assert(to_container != NULL);
- remove_client_from_container(conn, current_client, container);
+ client_remove_from_container(conn, current_client, container);
if (container->workspace->fullscreen_client == current_client)
container->workspace->fullscreen_client = NULL;
if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true);
if (need_warp) {
- warp_pointer_into(conn, CUR_CELL->currently_focused);
+ client_warp_pointer_into(conn, CUR_CELL->currently_focused);
xcb_flush(conn);
}
}
if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true);
if (need_warp) {
- warp_pointer_into(conn, CUR_CELL->currently_focused);
+ client_warp_pointer_into(conn, CUR_CELL->currently_focused);
xcb_flush(conn);
}
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
render_layout(conn);
}
-/*
- * Checks if the given window class and title match the given client
- * Window title is passed as "normal" string and as UCS-2 converted string for
- * matching _NET_WM_NAME capable clients as well as those using legacy hints.
- *
- */
-static bool client_matches_class_name(Client *client, char *to_class, char *to_title,
- char *to_title_ucs, int to_title_ucs_len) {
- /* Check if the given class is part of the window class */
- if (strcasestr(client->window_class, to_class) == NULL)
- return false;
-
- /* If no title was given, we’re done */
- if (to_title == NULL)
- return true;
-
- if (client->name_len > -1) {
- /* UCS-2 converted window titles */
- if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
- return false;
- } else {
- /* Legacy hints */
- if (strcasestr(client->name, to_title) == NULL)
- return false;
- }
-
- return true;
-}
-
/*
* Jumps to the given window class / title.
* Title is matched using strstr, that is, matches if it appears anywhere
*
*/
static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
- char *to_class, *to_title, *to_title_ucs = NULL;
- int to_title_ucs_len;
+ char *classtitle;
+ Client *client;
/* The first character is a quote, this was checked before */
- to_class = sstrdup(arguments+1);
+ classtitle = sstrdup(arguments+1);
/* The last character is a quote, we just set it to NULL */
- to_class[strlen(to_class)-1] = '\0';
-
- /* If a title was specified, split both strings at the slash */
- if ((to_title = strstr(to_class, "/")) != NULL) {
- *(to_title++) = '\0';
- /* Convert to UCS-2 */
- to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
- }
-
- LOG("Should jump to class \"%s\" / title \"%s\"\n", to_class, to_title);
- for (int workspace = 0; workspace < 10; workspace++) {
- if (workspaces[workspace].screen == NULL)
- continue;
-
- FOR_TABLE(&(workspaces[workspace])) {
- Container *con = workspaces[workspace].table[cols][rows];
- Client *client;
+ classtitle[strlen(classtitle)-1] = '\0';
- 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;
-
- set_focus(conn, client, true);
- goto done;
- }
- }
+ if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
+ free(classtitle);
+ LOG("No matching client found.\n");
+ return;
}
-done:
- free(to_class);
- FREE(to_title_ucs);
+ free(classtitle);
+ set_focus(conn, client, true);
}
/*
}
LOG("Killing current window\n");
- kill_window(conn, CUR_CELL->currently_focused);
+ client_kill(conn, CUR_CELL->currently_focused);
return;
}
/* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) {
- Autostart *new = smalloc(sizeof(Autostart));
+ struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(value);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
continue;
continue;
}
+ /* assign window class[/window title] → workspace */
+ if (strcasecmp(key, "assign") == 0) {
+ LOG("assign: \"%s\"\n", value);
+ char *class_title = sstrdup(value);
+ char *target;
+
+ /* If the window class/title is quoted we skip quotes */
+ if (class_title[0] == '"') {
+ class_title++;
+ char *end = strchr(class_title, '"');
+ if (end == NULL)
+ die("Malformatted assignment, couldn't find finishing quote\n");
+ *end = '\0';
+ } else {
+ /* If it is not quoted, we terminate it at the first space */
+ char *end = strchr(class_title, ' ');
+ if (end == NULL)
+ die("Malformed assignment, couldn't find terminating space\n");
+ *end = '\0';
+ }
+
+ /* The target is the last argument separated by a space */
+ if ((target = strrchr(value, ' ')) == NULL)
+ die("Malformed assignment, couldn't find target\n");
+ target++;
+
+ if (atoi(target) < 1 || atoi(target) > 10)
+ die("Malformed assignment, invalid workspace number\n");
+
+ LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
+
+ struct Assignment *new = smalloc(sizeof(struct Assignment));
+ new->windowclass_title = class_title;
+ new->workspace = atoi(target);
+ TAILQ_INSERT_TAIL(&assignments, new, assignments);
+ continue;
+ }
+
fprintf(stderr, "Unknown configfile option: %s\n", key);
exit(1);
}
#include "config.h"
#include "queue.h"
#include "resize.h"
+#include "client.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
con->workspace->fullscreen_client = NULL;
/* Remove the client from the list of clients */
- remove_client_from_container(conn, client, con);
+ client_remove_from_container(conn, client, con);
/* Set focus to the last focused client in this container */
con->currently_focused = get_last_focused_client(conn, con, NULL);
/* The list of exec-lines */
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
+/* The list of assignments */
+struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
+
/* This is a list of Stack_Windows, global, for easier/faster access on expose events */
struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
if (!attr) {
wa.tag = TAG_COOKIE;
wa.u.cookie = xcb_get_window_attributes(conn, window);
- attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
- }
- geom = xcb_get_geometry_reply(conn, geomc, 0);
- if (attr && geom) {
- reparent_window(conn, window, attr->visual, geom->root, geom->depth,
- geom->x, geom->y, geom->width, geom->height);
- 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]);
+ 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:
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;
+ 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];
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);
/* 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);
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]) {
- 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);
- }
+ 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) {
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;
+ }
}
- /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
- if (CUR_CELL->workspace->fullscreen_client == NULL) {
- if (!new->dock) {
- CUR_CELL->currently_focused = new;
- xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
+ 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);
}
- } else {
- /* 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);
}
/* Insert into the currently active container, if it’s not a dock window */
/* 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(&(CUR_CELL->clients), old_focused, new, clients);
- else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
+ 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);
}
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]) {
- /* 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;
- }
+ 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);
}
}
/* Autostarting exec-lines */
- Autostart *exec;
+ struct Autostart *exec;
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
#include "layout.h"
#include "util.h"
#include "xcb.h"
+#include "client.h"
static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
return buffer;
}
-/*
- * Removes the given client from the container, either because it will be inserted into another
- * one or because it was unmapped
- *
- */
-void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) {
- CIRCLEQ_REMOVE(&(container->clients), client, clients);
-
- SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
-
- /* If the container will be empty now and is in stacking mode, we need to
- unmap the stack_win */
- if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
- struct Stack_Window *stack_win = &(container->stack_win);
- stack_win->rect.height = 0;
- xcb_unmap_window(conn, stack_win->window);
- }
-}
-
/*
* Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client.
set_focus(conn, container->currently_focused, true);
}
-/*
- * Warps the pointer into the given client (in the middle of it, to be specific), therefore
- * selecting it
- *
- */
-void warp_pointer_into(xcb_connection_t *conn, Client *client) {
- int mid_x = client->rect.width / 2,
- mid_y = client->rect.height / 2;
- xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
-}
-
/*
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
}
/*
- * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
+ * Gets the first matching client for the given window class/window title.
+ * If the paramater specific is set to a specific client, only this one
+ * will be checked.
*
*/
-static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
- xcb_get_property_cookie_t cookie;
- xcb_get_wm_protocols_reply_t protocols;
- bool result = false;
+Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
+ Client *specific) {
+ char *to_class, *to_title, *to_title_ucs = NULL;
+ int to_title_ucs_len;
+ Client *matching = NULL;
+
+ to_class = sstrdup(window_classtitle);
+
+ /* If a title was specified, split both strings at the slash */
+ if ((to_title = strstr(to_class, "/")) != NULL) {
+ *(to_title++) = '\0';
+ /* Convert to UCS-2 */
+ to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
+ }
- cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
- if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
- return false;
+ /* If we were given a specific client we only check if that one matches */
+ if (specific != NULL) {
+ if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
+ matching = specific;
+ goto done;
+ }
- /* Check if the client’s protocols have the requested atom set */
- for (uint32_t i = 0; i < protocols.atoms_len; i++)
- if (protocols.atoms[i] == atom)
- result = true;
+ LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
+ for (int workspace = 0; workspace < 10; workspace++) {
+ if (workspaces[workspace].screen == NULL)
+ continue;
- xcb_get_wm_protocols_reply_wipe(&protocols);
+ FOR_TABLE(&(workspaces[workspace])) {
+ Container *con = workspaces[workspace].table[cols][rows];
+ Client *client;
- return result;
-}
+ 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;
-/*
- * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
- *
- */
-void kill_window(xcb_connection_t *conn, Client *window) {
- /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
- if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
- LOG("Killing window the hard way\n");
- xcb_kill_client(conn, window->child);
- return;
+ matching = client;
+ goto done;
+ }
+ }
}
- xcb_client_message_event_t ev;
-
- memset(&ev, 0, sizeof(xcb_client_message_event_t));
-
- ev.response_type = XCB_CLIENT_MESSAGE;
- ev.window = window->child;
- ev.type = atoms[WM_PROTOCOLS];
- ev.format = 32;
- ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
- ev.data.data32[1] = XCB_CURRENT_TIME;
-
- LOG("Sending WM_DELETE to the client\n");
- xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
- xcb_flush(conn);
+done:
+ free(to_class);
+ FREE(to_title_ucs);
+ return matching;
}