]> git.sur5r.net Git - i3/i3/commitdiff
Implement putting clients onto specific workspaces ("assign" in the configfile)
authorMichael Stapelberg <michael@stapelberg.de>
Sat, 16 May 2009 15:32:36 +0000 (17:32 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 16 May 2009 15:32:36 +0000 (17:32 +0200)
This closes ticket #39

12 files changed:
docs/Makefile
docs/userguide [new file with mode: 0644]
include/client.h [new file with mode: 0644]
include/data.h
include/i3.h
include/util.h
src/client.c [new file with mode: 0644]
src/commands.c
src/config.c
src/handlers.c
src/mainx.c
src/util.c

index e69aefc56ddb513a1da8bccd077754f548f13026..123f839f2a7682f05da9bf00010a170b4b3f6c62 100644 (file)
@@ -1,5 +1,5 @@
 
-all: hacking-howto.html debugging.html
+all: hacking-howto.html debugging.html userguide.html
 
 hacking-howto.html: hacking-howto
        asciidoc -a toc -n $<
@@ -7,6 +7,8 @@ hacking-howto.html: hacking-howto
 debugging.html: debugging
        asciidoc -n $<
 
+userguide.html: userguide
+       asciidoc -a toc -n $<
 
 clean:
        rm -f */*.{aux,log,toc,bm,pdf,dvi}
diff --git a/docs/userguide b/docs/userguide
new file mode 100644 (file)
index 0000000..36c2af4
--- /dev/null
@@ -0,0 +1,59 @@
+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
+----------------------
diff --git a/include/client.h b/include/client.h
new file mode 100644 (file)
index 0000000..a88f8d0
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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
index af6b3ef5a0fd27530e0d4767c4af97490b29e79c..8cb60b8fbb56f44dbeb6abd992879ce0e6b06a46 100644 (file)
@@ -48,7 +48,6 @@ typedef struct Font i3Font;
 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;
@@ -208,6 +207,17 @@ struct Autostart {
         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)
index 1c9e1b35877681a033d556e8150ed219496e8721..06f423a057b6eb6ce5499188d13b552acd833558 100644 (file)
@@ -26,6 +26,7 @@ extern char **start_argv;
 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;
index 889dcf122f175b53d2b10815b23d425ee81cbaa2..709e48a471b204039bf31aa7892665a950629498 100644 (file)
@@ -123,13 +123,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
  */
 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.
@@ -169,13 +162,6 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container);
  */
 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
@@ -185,9 +171,12 @@ void warp_pointer_into(xcb_connection_t *conn, Client *client);
 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
diff --git a/src/client.c b/src/client.c
new file mode 100644 (file)
index 0000000..f5409d0
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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;
+}
index 553d347d3083e8dec1416cc9b0cb9f5fc3d909e2..b84cb858d00f111e50486b21a4a87e90dd743e01 100644 (file)
@@ -23,6 +23,7 @@
 #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 */
@@ -240,7 +241,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
         }
 
         /* 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);
@@ -458,7 +459,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
 
         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;
 
@@ -538,7 +539,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
                 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);
                         }
                 }
@@ -574,7 +575,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
         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);
@@ -582,35 +583,6 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
         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
@@ -619,44 +591,22 @@ static bool client_matches_class_name(Client *client, char *to_class, char *to_t
  *
  */
 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);
 }
 
 /*
@@ -763,7 +713,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
                 }
 
                 LOG("Killing current window\n");
-                kill_window(conn, CUR_CELL->currently_focused);
+                client_kill(conn, CUR_CELL->currently_focused);
                 return;
         }
 
index ca84282a41292535a8ce7454dce594ed443e2133..562b2e928bb3a02afc72dd1b7be42f56a233f78a 100644 (file)
@@ -87,7 +87,7 @@ void load_configuration(const char *override_configpath) {
 
                 /* 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;
@@ -133,6 +133,44 @@ void load_configuration(const char *override_configpath) {
                         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);
         }
index 7652f07b9f6010936855da77667e0463250e67b4..c7c11246833bf9881707174f7351abb8c48dd2ef 100644 (file)
@@ -32,6 +32,7 @@
 #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
@@ -501,7 +502,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
                         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);
index da79ee3ab5b1dfabff5ef56d644cfe0743d3b501..dbce26a159695e51e886f826d1e3504a97a28dd8 100644 (file)
@@ -55,6 +55,9 @@ struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
 /* 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);
 
@@ -102,17 +105,21 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
         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:
@@ -131,7 +138,8 @@ 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;
+        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];
 
@@ -147,6 +155,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
         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);
 
@@ -191,6 +202,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t 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);
 
@@ -227,15 +240,16 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
         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) {
@@ -257,18 +271,65 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
                         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 */
@@ -276,8 +337,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
                 /* 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);
         }
@@ -287,14 +348,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
         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);
 }
@@ -531,7 +593,7 @@ int main(int argc, char *argv[], char *env[]) {
         }
 
         /* Autostarting exec-lines */
-        Autostart *exec;
+        struct Autostart *exec;
         TAILQ_FOREACH(exec, &autostarts, autostarts) {
                 LOG("auto-starting %s\n", exec->command);
                 start_application(exec->command);
index c0e74c75b216c7d199dc393de168234c66cdb787..5b51bd5b39bb8ca70a2bd14d15f68d5c16f0c4cd 100644 (file)
@@ -27,6 +27,7 @@
 #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);
@@ -224,25 +225,6 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
        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.
@@ -435,17 +417,6 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
                 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
@@ -508,52 +479,55 @@ void toggle_fullscreen(xcb_connection_t *conn, Client *client) {
 }
 
 /*
- * 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;
 }