]> git.sur5r.net Git - i3/i3/commitdiff
Time Lord technology: for_window config directive to run arbitrary cmds
authorMichael Stapelberg <michael@stapelberg.de>
Sun, 15 May 2011 18:10:25 +0000 (20:10 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Sun, 15 May 2011 18:10:25 +0000 (20:10 +0200)
An example to set all XTerms floating:
    for_window [class="XTerm"] mode floating

To make all urxvts use a 1-pixel border:
    for_window [class="urxvt"] border 1pixel

A less useful, but rather funny example:
    for_window [title="x200: ~/work"] mode floating

The commands are not completely arbitrary. The commands above were tested,
others may need some fixing. Internally, windows are compared against your
criteria (class, title, …) when they are initially managed and whenever one of
the relevant values change. Then, the specified command is run *once* (per
window). It gets prefixed with a criteria to make it match only the specific
window that triggered it. So, if you configure "mode floating", i3 runs
something like '[id="8393923"] mode floating'.

15 files changed:
Makefile
include/all.h
include/assignments.h [new file with mode: 0644]
include/data.h
include/i3.h
include/window.h
src/assignments.c [new file with mode: 0644]
src/cfgparse.l
src/cfgparse.y
src/cmdparse.y
src/handlers.c
src/main.c
src/manage.c
src/match.c
src/window.c

index c93591425f06a38ae94ed04af7fdcf797f9d7785..6e032f4180d18c10373ba241c9e13c3268755da2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk
 
 # Depend on the object files of all source-files in src/*.c and on all header files
 AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
-FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c
+FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c src/output.c src/ewmh.c src/assignments.c
 FILES:=$(FILES:.c=.o)
 HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
 
index f9f12a70afc322d3137039953e37bfe5e89197c2..ba582a80e32445bf8a89ccaa6de08ebf9320389a 100644 (file)
@@ -62,5 +62,6 @@
 #include "move.h"
 #include "output.h"
 #include "ewmh.h"
+#include "assignments.h"
 
 #endif
diff --git a/include/assignments.h b/include/assignments.h
new file mode 100644 (file)
index 0000000..ecd8b80
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ */
+#ifndef _ASSIGNMENTS_H
+#define _ASSIGNMENTS_H
+
+/**
+ * Checks the list of assignments for the given window and runs all matching
+ * ones (unless they have already been run for this specific window).
+ *
+ */
+void run_assignments(i3Window *window);
+
+#endif
index c408d3f6fed3c8283806b8991ebb65f81ad5d438..832200ee85c3ae947488fff128b15d14fab84878 100644 (file)
@@ -33,6 +33,7 @@ typedef struct Rect Rect;
 typedef struct xoutput Output;
 typedef struct Con Con;
 typedef struct Match Match;
+typedef struct Assignment Assignment;
 typedef struct Window i3Window;
 
 
@@ -274,11 +275,14 @@ struct Window {
 
     /** Pixels the window reserves. left/right/top/bottom */
     struct reservedpx reserved;
+
+    /** Pointers to the Assignments which were already ran for this Window
+     * (assignments run only once) */
+    uint32_t nr_assignments;
+    Assignment **ran_assignments;
 };
 
 struct Match {
-    enum { M_WINDOW, M_CON } what;
-
     char *title;
     int title_len;
     char *application;
@@ -296,10 +300,6 @@ struct Match {
     Con *con_id;
     enum { M_ANY = 0, M_TILING, M_FLOATING } floating;
 
-    enum { M_GLOBAL = 0, M_OUTPUT, M_WORKSPACE } levels;
-
-    enum { M_USER = 0, M_RESTART } source;
-
     char *target_ws;
 
     /* Where the window looking for a match should be inserted:
@@ -317,6 +317,29 @@ struct Match {
     TAILQ_ENTRY(Match) assignments;
 };
 
+struct Assignment {
+    /** type of this assignment:
+     *
+     * A_COMMAND = run the specified command for the matching window
+     * A_TO_WORKSPACE = assign the matching window to the specified workspace
+     * A_TO_OUTPUT = assign the matching window to the specified output
+     *
+     */
+    enum { A_COMMAND = 0, A_TO_WORKSPACE = 1, A_TO_OUTPUT = 2 } type;
+
+    /** the criteria to check if a window matches */
+    Match match;
+
+    /** destination workspace/output/command, depending on the type */
+    union {
+        char *command;
+        char *workspace;
+        char *output;
+    } dest;
+
+    TAILQ_ENTRY(Assignment) real_assignments;
+};
+
 struct Con {
     bool mapped;
     enum {
index a18cd6bc8c5a2d53d684496bc9e4b84f54396019..6aeea847fb2755109f62415fa6f764a7d8e53e53 100644 (file)
@@ -28,6 +28,7 @@ extern TAILQ_HEAD(bindings_head, Binding) *bindings;
 extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
 extern TAILQ_HEAD(assignments_head, Match) assignments;
 extern TAILQ_HEAD(ws_assignments_head, Workspace_Assignment) ws_assignments;
+extern TAILQ_HEAD(real_assignments_head, Assignment) real_assignments;
 extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
 extern uint8_t root_depth;
 extern bool xcursor_supported, xkb_supported;
index 1c48c012e0c0626187bffdd31fd6320fa12828e5..fe282aa08208f2bc8d9e55888b3b1575fb041852 100644 (file)
@@ -6,14 +6,14 @@
  * given window.
  *
  */
-void window_update_class(i3Window *win, xcb_get_property_reply_t *prop);
+void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
 
 /**
  * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
  * window. Further updates using window_update_name_legacy will be ignored.
  *
  */
-void window_update_name(i3Window *win, xcb_get_property_reply_t *prop);
+void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
 
 /**
  * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
@@ -22,7 +22,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop);
  * window_update_name()).
  *
  */
-void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop);
+void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt);
 
 /**
  * Updates the CLIENT_LEADER (logical parent window).
diff --git a/src/assignments.c b/src/assignments.c
new file mode 100644 (file)
index 0000000..f41877f
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include "all.h"
+
+/*
+ * Checks the list of assignments for the given window and runs all matching
+ * ones (unless they have already been run for this specific window).
+ *
+ */
+void run_assignments(i3Window *window) {
+    DLOG("Checking assignments...\n");
+
+    /* Check if any assignments match */
+    Assignment *current;
+    TAILQ_FOREACH(current, &real_assignments, real_assignments) {
+        if (!match_matches_window(&(current->match), window))
+            continue;
+
+        bool skip = false;
+        for (int c = 0; c < window->nr_assignments; c++) {
+            if (window->ran_assignments[c] != current)
+                continue;
+
+            DLOG("This assignment already ran for the given window, not executing it again.\n");
+            skip = true;
+            break;
+        }
+
+        if (skip)
+            continue;
+
+        DLOG("matching assignment, would do:\n");
+        if (current->type == A_COMMAND) {
+            DLOG("execute command %s\n", current->dest.command);
+            char *full_command;
+            asprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
+            parse_cmd(full_command);
+        }
+
+        /* Store that we ran this assignment to not execute it again */
+        window->nr_assignments++;
+        window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment*) * window->nr_assignments);
+        window->ran_assignments[window->nr_assignments-1] = current;
+    }
+}
index 1615288e93f2ec22415369757b8182ee66ec9c72..09cf9c44b1944eb0c113ef4e1314c4516ed0a0bb 100644 (file)
@@ -43,6 +43,9 @@ EOL   (\r?\n)
 %s COLOR_COND
 %s OUTPUT_COND
 %s OUTPUT_AWS_COND
+%s WANT_QSTRING
+%s FOR_WINDOW_COND
+%s REQUIRE_WS
 %x BUFFER_LINE
 
 %%
@@ -68,6 +71,16 @@ EOL  (\r?\n)
 }
 
 
+<FOR_WINDOW_COND>"]"             { yy_pop_state(); return ']'; }
+<REQUIRE_WS>[ \t]*               { yy_pop_state(); return WHITESPACE; }
+<WANT_QSTRING>\"[^\"]+\"         {
+                                  yy_pop_state();
+                                  /* strip quotes */
+                                  char *copy = sstrdup(yytext+1);
+                                  copy[strlen(copy)-1] = '\0';
+                                  yylval.string = copy;
+                                  return STR;
+                                 }
 <BIND_A2WS_COND>[^\n]+          { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
 <OUTPUT_AWS_COND>[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; }
 ^[ \t]*#[^\n]*                  { return TOKCOMMENT; }
@@ -108,6 +121,18 @@ workspace_bar                   { return TOKWORKSPACEBAR; }
 popup_during_fullscreen         { return TOK_POPUP_DURING_FULLSCREEN; }
 ignore                          { return TOK_IGNORE; }
 leave_fullscreen                { return TOK_LEAVE_FULLSCREEN; }
+for_window                      {
+                                  /* Example: for_window [class="urxvt"] border none
+                                   *
+                                   * First, we wait for the ']' that finishes a match (FOR_WINDOW_COND)
+                                   * Then, we require a whitespace (REQUIRE_WS)
+                                   * And the rest of the line is parsed as a string
+                                   */
+                                  yy_push_state(BIND_A2WS_COND);
+                                  yy_push_state(REQUIRE_WS);
+                                  yy_push_state(FOR_WINDOW_COND);
+                                  return TOK_FOR_WINDOW;
+                                }
 default                         { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; }
 stacking                        { /* yylval.number = MODE_STACK; */return TOK_STACKING; }
 stacked                         { return TOK_STACKING; }
@@ -134,6 +159,13 @@ control                         { return TOKCONTROL; }
 ctrl                            { return TOKCONTROL; }
 shift                           { return TOKSHIFT; }
 →                               { return TOKARROW; }
+
+class                           { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
+id                              { yy_push_state(WANT_QSTRING); return TOK_ID; }
+con_id                          { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
+con_mark                        { yy_push_state(WANT_QSTRING); return TOK_MARK; }
+title                           { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
+
 {EOL}                           {
                                   FREE(context->line_copy);
                                   context->line_number++;
index a5e0f0baca70f3860a78e388a3f4d31fbe974e56..00190dbc3b26e3d597b77ba3b45b65848d1b3784 100644 (file)
@@ -7,9 +7,12 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <limits.h>
 
 #include "all.h"
 
+static Match current_match;
+
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
 extern int yylex(struct context *context);
 extern int yyparse(void);
@@ -242,6 +245,13 @@ void parse_file(const char *f) {
 %token                  TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
 %token                  TOK_IGNORE                  "ignore"
 %token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
+%token                  TOK_FOR_WINDOW              "for_window"
+
+%token              TOK_MARK            "mark"
+%token              TOK_CLASS           "class"
+%token              TOK_ID              "id"
+%token              TOK_CON_ID          "con_id"
+%token              TOK_TITLE           "title"
 
 %type   <binding>       binding
 %type   <binding>       bindcode
@@ -272,6 +282,7 @@ lines: /* empty */
 
 line:
     bindline
+    | for_window
     | mode
     | floating_modifier
     | orientation
@@ -292,6 +303,10 @@ line:
     | popup_during_fullscreen
     ;
 
+optwhitespace:
+    | WHITESPACE
+    ;
+
 comment:
     TOKCOMMENT
     ;
@@ -340,6 +355,90 @@ bindsym:
     }
     ;
 
+for_window:
+    TOK_FOR_WINDOW WHITESPACE match WHITESPACE command
+    {
+        printf("\t should execute command %s for the criteria mentioned above\n", $5);
+        Assignment *assignment = scalloc(sizeof(Assignment));
+        assignment->type = A_COMMAND;
+        assignment->match = current_match;
+        assignment->dest.command = $5;
+        TAILQ_INSERT_TAIL(&real_assignments, assignment, real_assignments);
+    }
+    ;
+
+match:
+    | matchstart optwhitespace criteria optwhitespace matchend
+    {
+        printf("match parsed\n");
+    }
+    ;
+
+matchstart:
+    '['
+    {
+        printf("start\n");
+        match_init(&current_match);
+    }
+    ;
+
+matchend:
+    ']'
+    {
+        printf("match specification finished\n");
+    }
+    ;
+
+criteria:
+    TOK_CLASS '=' STR
+    {
+        printf("criteria: class = %s\n", $3);
+        current_match.class = $3;
+    }
+    | TOK_CON_ID '=' STR
+    {
+        printf("criteria: id = %s\n", $3);
+        char *end;
+        long parsed = strtol($3, &end, 10);
+        if (parsed == LONG_MIN ||
+            parsed == LONG_MAX ||
+            parsed < 0 ||
+            (end && *end != '\0')) {
+            ELOG("Could not parse con id \"%s\"\n", $3);
+        } else {
+            current_match.con_id = (Con*)parsed;
+            printf("id as int = %p\n", current_match.con_id);
+        }
+    }
+    | TOK_ID '=' STR
+    {
+        printf("criteria: window id = %s\n", $3);
+        char *end;
+        long parsed = strtol($3, &end, 10);
+        if (parsed == LONG_MIN ||
+            parsed == LONG_MAX ||
+            parsed < 0 ||
+            (end && *end != '\0')) {
+            ELOG("Could not parse window id \"%s\"\n", $3);
+        } else {
+            current_match.id = parsed;
+            printf("window id as int = %d\n", current_match.id);
+        }
+    }
+    | TOK_MARK '=' STR
+    {
+        printf("criteria: mark = %s\n", $3);
+        current_match.mark = $3;
+    }
+    | TOK_TITLE '=' STR
+    {
+        printf("criteria: title = %s\n", $3);
+        current_match.title = $3;
+    }
+    ;
+
+
+
 word_or_number:
     WORD
     | NUMBER
index eb41c8207794e700ecfc1c86483e3d1be982dfa2..bc4858f27c5e4e6f4f143e78ac1069e2cf345c31 100644 (file)
@@ -3,7 +3,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
  *
@@ -80,6 +80,7 @@ int cmdyywrap() {
 }
 
 char *parse_cmd(const char *new) {
+    LOG("COMMAND: *%s*\n", new);
     cmdyy_scan_string(new);
 
     match_init(&current_match);
@@ -512,15 +513,21 @@ direction:
 mode:
     TOK_MODE WHITESPACE window_mode
     {
-        if ($3 == TOK_TOGGLE) {
-            printf("should toggle mode\n");
-            toggle_floating_mode(focused, false);
-        } else {
-            printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling"));
-            if ($3 == TOK_FLOATING) {
-                floating_enable(focused, false);
+        HANDLE_EMPTY_MATCH;
+
+        owindow *current;
+        TAILQ_FOREACH(current, &owindows, owindows) {
+            printf("matching: %p / %s\n", current->con, current->con->name);
+            if ($3 == TOK_TOGGLE) {
+                printf("should toggle mode\n");
+                toggle_floating_mode(current->con, false);
             } else {
-                floating_disable(focused, false);
+                printf("should switch mode to %s\n", ($3 == TOK_FLOATING ? "floating" : "tiling"));
+                if ($3 == TOK_FLOATING) {
+                    floating_enable(current->con, false);
+                } else {
+                    floating_disable(current->con, false);
+                }
             }
         }
     }
@@ -608,11 +615,14 @@ layout:
         printf("changing layout to %d\n", $3);
         owindow *current;
 
-        HANDLE_EMPTY_MATCH;
-
-        TAILQ_FOREACH(current, &owindows, owindows) {
-            printf("matching: %p / %s\n", current->con, current->con->name);
-            con_set_layout(current->con, $3);
+        /* check if the match is empty, not if the result is empty */
+        if (match_is_empty(&current_match))
+            con_set_layout(focused->parent, $3);
+        else {
+            TAILQ_FOREACH(current, &owindows, owindows) {
+                printf("matching: %p / %s\n", current->con, current->con->name);
+                con_set_layout(current->con, $3);
+            }
         }
     }
     ;
index f1e1c4b02704c6d3a828a47c88b858f70def6913..b87bb4181590b0c199a4c8944a385079bc379230 100644 (file)
@@ -541,7 +541,7 @@ static int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
         return 1;
 
-    window_update_name(con->window, prop);
+    window_update_name(con->window, prop, false);
 
     x_push_changes(croot);
 
@@ -559,7 +559,7 @@ static int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, u
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
         return 1;
 
-    window_update_name_legacy(con->window, prop);
+    window_update_name_legacy(con->window, prop, false);
 
     x_push_changes(croot);
 
@@ -576,7 +576,7 @@ static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
         return 1;
 
-    window_update_class(con->window, prop);
+    window_update_class(con->window, prop, false);
 
     return 0;
 }
index c079d8dce8cd1fbbeeadbdb7a5e95a4a84d8ee97..39702223b6904027399f364d9f80cc5e73646e88 100644 (file)
@@ -37,6 +37,8 @@ struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
  * output) */
 struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignments);
 
+struct real_assignments_head real_assignments = TAILQ_HEAD_INITIALIZER(real_assignments);
+
 /* We hope that those are supported and set them to true */
 bool xcursor_supported = true;
 bool xkb_supported = true;
index b511189c0a1b36d4207949ef0770d6ac6724e52a..7d240f4234b7afecf519864eb43d839ee9b24317 100644 (file)
@@ -158,9 +158,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
 
     /* update as much information as possible so far (some replies may be NULL) */
-    window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
-    window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
-    window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
+    window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
+    window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true);
+    window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL), true);
     window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
     window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
     window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
@@ -330,6 +330,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
      * cleanup) */
     xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
 
+    /* Check if any assignments match */
+    run_assignments(cwindow);
+
     tree_render();
 
     free(geom);
index 9ed4d434f14e7c1e86983401531d3f2db8f39068..fd024d59e3ffbdb0937d05df1c75cb9bef8035af 100644 (file)
@@ -66,6 +66,12 @@ bool match_matches_window(Match *match, i3Window *window) {
         return true;
     }
 
+    /* TODO: pcre match */
+    if (match->title != NULL && window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) {
+        LOG("match made by title (%s)\n", window->name_json);
+        return true;
+    }
+
     LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock);
     if (match->dock != -1 &&
         ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) ||
index cd96890cb8baf972b62ab30424be15f66efb8671..b6aaae9e8f65e70539d3d5ebed1a5c3e3d694630 100644 (file)
@@ -2,7 +2,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
  *
  */
 #include "all.h"
@@ -12,7 +12,7 @@
  * given window.
  *
  */
-void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
+void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
         DLOG("empty property, not updating\n");
         return;
@@ -32,6 +32,11 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
     else win->class_class = NULL;
     LOG("WM_CLASS changed to %s (instance), %s (class)\n",
         win->class_instance, win->class_class);
+
+    if (before_mgmt)
+        return;
+
+    run_assignments(win);
 }
 
 /*
@@ -39,7 +44,7 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) {
  * window. Further updates using window_update_name_legacy will be ignored.
  *
  */
-void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
+void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
         DLOG("_NET_WM_NAME not specified, not changing\n");
         return;
@@ -70,6 +75,11 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
     LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
 
     win->uses_net_wm_name = true;
+
+    if (before_mgmt)
+        return;
+
+    run_assignments(win);
 }
 
 /*
@@ -79,7 +89,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) {
  * window_update_name()).
  *
  */
-void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) {
+void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
         DLOG("prop == NULL\n");
         return;
@@ -106,6 +116,11 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) {
     win->name_json = sstrdup(new_name);
     win->name_len = strlen(new_name);
     win->name_x_changed = true;
+
+    if (before_mgmt)
+        return;
+
+    run_assignments(win);
 }
 
 /*