]> git.lizzy.rs Git - bspwm.git/commitdiff
Asynchronously parse rule command output
authorBastien Dejean <nihilhill@gmail.com>
Fri, 8 Nov 2013 19:31:23 +0000 (20:31 +0100)
committerBastien Dejean <nihilhill@gmail.com>
Fri, 8 Nov 2013 19:31:23 +0000 (20:31 +0100)
19 files changed:
bspwm.c
bspwm.h
contrib/rule_scripts/README.asciidoc [new file with mode: 0644]
contrib/rule_scripts/rulc [new file with mode: 0755]
contrib/rule_scripts/ruld [new file with mode: 0755]
contrib/rule_scripts/rule_command [new file with mode: 0755]
contrib/rules/README.asciidoc [deleted file]
contrib/rules/rulc [deleted file]
contrib/rules/ruld [deleted file]
doc/bspwm.1
doc/bspwm.1.txt
events.c
examples/bspwmrc
rule.c
rule.h
settings.h
types.h
window.c
window.h

diff --git a/bspwm.c b/bspwm.c
index fa4d11e97d937293e62714c04c38fc18fe91534a..d9da173b626797c0a7a56878070642c0e2d61477 100644 (file)
--- a/bspwm.c
+++ b/bspwm.c
@@ -45,6 +45,7 @@
 #include "history.h"
 #include "stack.h"
 #include "ewmh.h"
+#include "rule.h"
 #include "bspwm.h"
 
 int main(int argc, char *argv[])
@@ -52,7 +53,7 @@ int main(int argc, char *argv[])
     fd_set descriptors;
     char socket_path[MAXLEN];
     config_path[0] = '\0';
-    int sock_fd, ret_fd, dpy_fd, sel, n;
+    int sock_fd, cli_fd, dpy_fd, max_fd, n;
     struct sockaddr_un sock_address;
     size_t rsp_len = 0;
     char msg[BUFSIZ] = {0};
@@ -111,8 +112,6 @@ int main(int argc, char *argv[])
     if (listen(sock_fd, SOMAXCONN) == -1)
         err("Couldn't listen to the socket.\n");
 
-    sel = MAX(sock_fd, dpy_fd) + 1;
-
     signal(SIGPIPE, SIG_IGN);
     load_settings();
     run_config();
@@ -125,12 +124,28 @@ int main(int argc, char *argv[])
         FD_ZERO(&descriptors);
         FD_SET(sock_fd, &descriptors);
         FD_SET(dpy_fd, &descriptors);
+        max_fd = MAX(sock_fd, dpy_fd);
+        for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) {
+            FD_SET(pr->fd, &descriptors);
+            if (pr->fd > max_fd)
+                max_fd = pr->fd;
+        }
+
+        if (select(max_fd + 1, &descriptors, NULL, NULL, NULL) > 0) {
 
-        if (select(sel, &descriptors, NULL, NULL, NULL) > 0) {
+            pending_rule_t *pr = pending_rule_head;
+            while (pr != NULL) {
+                pending_rule_t *next = pr->next;
+                if (FD_ISSET(pr->fd, &descriptors)) {
+                    manage_window(pr->win, pr->csq, pr->fd);
+                    remove_pending_rule(pr);
+                }
+                pr = next;
+            }
 
             if (FD_ISSET(sock_fd, &descriptors)) {
-                ret_fd = accept(sock_fd, NULL, 0);
-                if (ret_fd > 0 && (n = recv(ret_fd, msg, sizeof(msg), 0)) > 0) {
+                cli_fd = accept(sock_fd, NULL, 0);
+                if (cli_fd > 0 && (n = recv(cli_fd, msg, sizeof(msg), 0)) > 0) {
                     msg[n] = '\0';
                     if (handle_message(msg, n, rsp)) {
                         rsp_len = strlen(rsp);
@@ -139,10 +154,10 @@ int main(int argc, char *argv[])
                         rsp_len = 1;
                     }
                     if (rsp_len == 1 && rsp[0] == MESSAGE_SUBSCRIBE) {
-                        add_subscriber(ret_fd);
+                        add_subscriber(cli_fd);
                     } else {
-                        send(ret_fd, rsp, rsp_len, 0);
-                        close(ret_fd);
+                        send(cli_fd, rsp, rsp_len, 0);
+                        close(cli_fd);
                     }
                     rsp[0] = '\0';
                 }
@@ -154,7 +169,6 @@ int main(int argc, char *argv[])
                     free(event);
                 }
             }
-
         }
 
         if (xcb_connection_has_error(dpy))
@@ -179,6 +193,7 @@ void init(void)
     history_head = history_tail = history_needle = NULL;
     stack_head = stack_tail = NULL;
     subscribe_head = subscribe_tail = NULL;
+    pending_rule_head = pending_rule_tail = NULL;
     last_motion_time = last_motion_x = last_motion_y = 0;
     visible = auto_raise = sticky_still = record_history = true;
     randr_base = 0;
@@ -282,6 +297,8 @@ void cleanup(void)
         remove_stack(stack_head);
     while (subscribe_head != NULL)
         remove_subscriber(subscribe_head);
+    while (pending_rule_head != NULL)
+        remove_pending_rule(pending_rule_head);
     empty_history();
     free(frozen_pointer);
 }
diff --git a/bspwm.h b/bspwm.h
index 2e43c5177ae76eddaf2d80af99eaa24b2a3056ea..8278769e86683654b7491c538f04cdb122ab3e96 100644 (file)
--- a/bspwm.h
+++ b/bspwm.h
@@ -53,6 +53,8 @@ stacking_list_t *stack_head;
 stacking_list_t *stack_tail;
 subscriber_list_t *subscribe_head;
 subscriber_list_t *subscribe_tail;
+pending_rule_t *pending_rule_head;
+pending_rule_t *pending_rule_tail;
 
 pointer_state_t *frozen_pointer;
 xcb_window_t motion_recorder;
diff --git a/contrib/rule_scripts/README.asciidoc b/contrib/rule_scripts/README.asciidoc
new file mode 100644 (file)
index 0000000..17ae1cf
--- /dev/null
@@ -0,0 +1,42 @@
+Synopsis
+--------
+
+Server
+~~~~~~
+
+*ruld* [*-h*|*-p* 'PORT']
+
+Client
+~~~~~~
+
+*rulc* [*-h*|*-p* 'PORT'|*-a*|*-r*|*-l*|*-t*|*-q*] 'DATA' ...
+
+Options
+-------
+
+Shared
+~~~~~~
+
+*-h*::
+    Print the synopsis and exit.
+
+*-p* 'PORT'::
+    Set the socket port.
+
+Client
+~~~~~~
+
+*-a* 'HYPOT' 'CONSEQ' ['[duration=DURATION][,delay=DELAY]']::
+    Create a new rule. 'HYPOT' is a Lua expression that represent the rule hypothesis and involves the following strings: 'class', 'instance', 'title', 'type' and 'state'.
+
+*-r* 'STRING'|head|tail::
+    Remove the rules containing 'STRING' in their hypothesis or remove the first or last rule.
+
+*-l*::
+    List the rules.
+
+*-t* 'CLASS' 'INSTANCE' 'TITLE' 'TYPE' 'STATE'::
+    Test the rules for the given window informations.
+
+*-q*::
+    Kill the server.
diff --git a/contrib/rule_scripts/rulc b/contrib/rule_scripts/rulc
new file mode 100755 (executable)
index 0000000..f0cd633
--- /dev/null
@@ -0,0 +1,68 @@
+#! /usr/bin/env lua
+
+local p = require "posix"
+local port = 54321
+
+local short = "hp:arltq"
+local long = {
+    {"help", "none", 'h'},
+    {"port", "required", 'p'},
+    {"add", "none", 'a'},
+    {"remove", "none", 'r'},
+    {"list", "none", 'l'},
+    {"test", "none", 't'},
+    {"quit", "none", 'q'}
+}
+
+local cmd_assoc = {
+    a = "add",
+    r = "remove",
+    l = "list",
+    t = "test",
+    q = "quit"
+}
+
+local cmd
+local data_idx = 1
+for opt, optarg, optind, longind in p.getopt(arg, short, long) do
+    if opt == '?' then
+        print("Unrecognized option")
+        os.exit(1)
+    elseif opt == 'h' then
+        print("Usage: rulc [-h|-p PORT|-a|-r|-l|-t|-q] DATA ...")
+        os.exit(0)
+    elseif opt == 'p' then
+        port = optarg
+    else
+        cmd = cmd_assoc[opt]
+    end
+    data_idx = optind
+end
+
+if not cmd then
+    os.exit(1)
+end
+
+local msg = cmd
+if cmd == "test" then
+    msg = string.format("%s {class=%q, instance=%q, title=%q, type=%q, state=%q}", msg, arg[data_idx], arg[data_idx+1], arg[data_idx+2], arg[data_idx+3], arg[data_idx+4])
+elseif cmd == "add" then
+    msg = string.format("%s {%q, %q, %s}", msg, arg[data_idx], arg[data_idx+1], arg[data_idx+2] and string.format("{%s}", arg[data_idx+2]) or "")
+elseif cmd == "remove" then
+    msg = string.format("%s %s", msg, arg[data_idx])
+end
+
+local fd = p.socket(p.AF_INET, p.SOCK_STREAM, 0)
+local s = p.connect(fd, {family=p.AF_INET, addr="127.0.0.1", port=port})
+if not s then
+    p.close(fd)
+    os.exit(1)
+end
+p.send(fd, msg)
+rsp = p.recv(fd, p.BUFSIZ)
+
+if rsp and #rsp > 0 then
+    print(rsp)
+end
+
+p.close(fd)
diff --git a/contrib/rule_scripts/ruld b/contrib/rule_scripts/ruld
new file mode 100755 (executable)
index 0000000..d291aee
--- /dev/null
@@ -0,0 +1,134 @@
+#! /usr/bin/env lua
+
+local p = require("posix")
+local port = 54321
+
+local short = "hp:"
+local long = {
+    {"help", "none", 'h'},
+    {"port", "required", 'p'},
+}
+
+local rules = {}
+
+for opt, optarg, optind, longind in p.getopt(arg, short, long) do
+    if opt == '?' then
+        print("Unrecognized option")
+        os.exit(1)
+    elseif opt == 'h' then
+        print("Usage: ruld [-h|-p PORT]")
+        os.exit(0)
+    elseif opt == 'p' then
+        port = optarg
+    end
+end
+
+function eval(exp, env)
+    local f
+    local value = "return " .. exp
+    if env then
+        f = load(value, nil, nil, env)
+    else
+        f = load(value)
+    end
+    return f and f()
+end
+
+function test(env)
+    local rsp = ""
+    for index = #rules, 1, -1 do
+        local entry = rules[index]
+        if eval(entry[1], env) then
+            local delay, duration
+            if entry[3] then
+                delay = entry[3].delay
+                duration = entry[3].duration
+                if delay and delay > 0 then
+                    entry[3].delay = delay - 1
+                end
+                if ((not delay) or delay == 0) and duration and duration > 0 then
+                    entry[3].duration = duration - 1
+                end
+            end
+            if (((not delay) or delay == 0) and ((not duration) or duration > 0)) then
+                if #rsp > 0 then
+                    rsp = rsp .. " " .. entry[2]
+                else
+                    rsp = entry[2]
+                end
+            end
+            if duration and duration <= 1 then
+                table.remove(rules, index)
+            end
+        end
+    end
+    return rsp
+end
+
+local fd = p.socket(p.AF_INET, p.SOCK_STREAM, 0)
+p.bind(fd, {family=p.AF_INET, addr="127.0.0.1", port=port})
+p.listen(fd, p.SOMAXCONN)
+local running = true
+
+while running do
+    ret_fd = p.accept(fd)
+    if ret_fd then
+        local msg = p.recv(ret_fd, p.BUFSIZ)
+        if msg then
+            local cmd, data = nil
+            sep = msg:find(" ")
+            if sep then
+                cmd = msg:sub(1, sep - 1)
+                data = msg:sub(sep + 1)
+            else
+                cmd = msg
+            end
+            if cmd == "test" then
+                local env = eval(data)
+                if env then
+                    rsp = test(env)
+                    p.send(ret_fd, rsp)
+                end
+            elseif cmd == "add" then
+                local value = eval(data)
+                if value then
+                    table.insert(rules, value)
+                end
+            elseif cmd == "remove" then
+                if data == "tail" then
+                    table.remove(rules, #rules)
+                elseif data == "head" then
+                    table.remove(rules, 1)
+                else
+                    for index = #rules, 1, -1 do
+                        if rules[index][1]:find(data) then
+                            table.remove(rules, index)
+                        end
+                    end
+                end
+            elseif cmd == "quit" then
+                running = false
+            elseif cmd == "list" then
+                local rsp = ""
+                for index, entry in pairs(rules) do
+                    rsp = rsp .. string.format("%s => %s", entry[1], entry[2])
+                    if entry[3] then
+                        if entry[3].delay then
+                            rsp = rsp .. string.format(" @%i", entry[3].delay)
+                        end
+                        if entry[3].duration then
+                            rsp = rsp .. string.format(" +%i", entry[3].duration)
+                        end
+                    end
+                    if index < #rules then
+                        rsp = rsp .. "\n"
+                    end
+                end
+                p.send(ret_fd, rsp)
+            end
+        end
+        p.close(ret_fd)
+    end
+end
+
+p.close(fd)
diff --git a/contrib/rule_scripts/rule_command b/contrib/rule_scripts/rule_command
new file mode 100755 (executable)
index 0000000..642ffbc
--- /dev/null
@@ -0,0 +1,3 @@
+#! /bin/dash
+
+xwinfo -cints $1 | xargs -d '\n' rulc -t
diff --git a/contrib/rules/README.asciidoc b/contrib/rules/README.asciidoc
deleted file mode 100644 (file)
index 17ae1cf..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-Synopsis
---------
-
-Server
-~~~~~~
-
-*ruld* [*-h*|*-p* 'PORT']
-
-Client
-~~~~~~
-
-*rulc* [*-h*|*-p* 'PORT'|*-a*|*-r*|*-l*|*-t*|*-q*] 'DATA' ...
-
-Options
--------
-
-Shared
-~~~~~~
-
-*-h*::
-    Print the synopsis and exit.
-
-*-p* 'PORT'::
-    Set the socket port.
-
-Client
-~~~~~~
-
-*-a* 'HYPOT' 'CONSEQ' ['[duration=DURATION][,delay=DELAY]']::
-    Create a new rule. 'HYPOT' is a Lua expression that represent the rule hypothesis and involves the following strings: 'class', 'instance', 'title', 'type' and 'state'.
-
-*-r* 'STRING'|head|tail::
-    Remove the rules containing 'STRING' in their hypothesis or remove the first or last rule.
-
-*-l*::
-    List the rules.
-
-*-t* 'CLASS' 'INSTANCE' 'TITLE' 'TYPE' 'STATE'::
-    Test the rules for the given window informations.
-
-*-q*::
-    Kill the server.
diff --git a/contrib/rules/rulc b/contrib/rules/rulc
deleted file mode 100755 (executable)
index f0cd633..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#! /usr/bin/env lua
-
-local p = require "posix"
-local port = 54321
-
-local short = "hp:arltq"
-local long = {
-    {"help", "none", 'h'},
-    {"port", "required", 'p'},
-    {"add", "none", 'a'},
-    {"remove", "none", 'r'},
-    {"list", "none", 'l'},
-    {"test", "none", 't'},
-    {"quit", "none", 'q'}
-}
-
-local cmd_assoc = {
-    a = "add",
-    r = "remove",
-    l = "list",
-    t = "test",
-    q = "quit"
-}
-
-local cmd
-local data_idx = 1
-for opt, optarg, optind, longind in p.getopt(arg, short, long) do
-    if opt == '?' then
-        print("Unrecognized option")
-        os.exit(1)
-    elseif opt == 'h' then
-        print("Usage: rulc [-h|-p PORT|-a|-r|-l|-t|-q] DATA ...")
-        os.exit(0)
-    elseif opt == 'p' then
-        port = optarg
-    else
-        cmd = cmd_assoc[opt]
-    end
-    data_idx = optind
-end
-
-if not cmd then
-    os.exit(1)
-end
-
-local msg = cmd
-if cmd == "test" then
-    msg = string.format("%s {class=%q, instance=%q, title=%q, type=%q, state=%q}", msg, arg[data_idx], arg[data_idx+1], arg[data_idx+2], arg[data_idx+3], arg[data_idx+4])
-elseif cmd == "add" then
-    msg = string.format("%s {%q, %q, %s}", msg, arg[data_idx], arg[data_idx+1], arg[data_idx+2] and string.format("{%s}", arg[data_idx+2]) or "")
-elseif cmd == "remove" then
-    msg = string.format("%s %s", msg, arg[data_idx])
-end
-
-local fd = p.socket(p.AF_INET, p.SOCK_STREAM, 0)
-local s = p.connect(fd, {family=p.AF_INET, addr="127.0.0.1", port=port})
-if not s then
-    p.close(fd)
-    os.exit(1)
-end
-p.send(fd, msg)
-rsp = p.recv(fd, p.BUFSIZ)
-
-if rsp and #rsp > 0 then
-    print(rsp)
-end
-
-p.close(fd)
diff --git a/contrib/rules/ruld b/contrib/rules/ruld
deleted file mode 100755 (executable)
index d291aee..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-#! /usr/bin/env lua
-
-local p = require("posix")
-local port = 54321
-
-local short = "hp:"
-local long = {
-    {"help", "none", 'h'},
-    {"port", "required", 'p'},
-}
-
-local rules = {}
-
-for opt, optarg, optind, longind in p.getopt(arg, short, long) do
-    if opt == '?' then
-        print("Unrecognized option")
-        os.exit(1)
-    elseif opt == 'h' then
-        print("Usage: ruld [-h|-p PORT]")
-        os.exit(0)
-    elseif opt == 'p' then
-        port = optarg
-    end
-end
-
-function eval(exp, env)
-    local f
-    local value = "return " .. exp
-    if env then
-        f = load(value, nil, nil, env)
-    else
-        f = load(value)
-    end
-    return f and f()
-end
-
-function test(env)
-    local rsp = ""
-    for index = #rules, 1, -1 do
-        local entry = rules[index]
-        if eval(entry[1], env) then
-            local delay, duration
-            if entry[3] then
-                delay = entry[3].delay
-                duration = entry[3].duration
-                if delay and delay > 0 then
-                    entry[3].delay = delay - 1
-                end
-                if ((not delay) or delay == 0) and duration and duration > 0 then
-                    entry[3].duration = duration - 1
-                end
-            end
-            if (((not delay) or delay == 0) and ((not duration) or duration > 0)) then
-                if #rsp > 0 then
-                    rsp = rsp .. " " .. entry[2]
-                else
-                    rsp = entry[2]
-                end
-            end
-            if duration and duration <= 1 then
-                table.remove(rules, index)
-            end
-        end
-    end
-    return rsp
-end
-
-local fd = p.socket(p.AF_INET, p.SOCK_STREAM, 0)
-p.bind(fd, {family=p.AF_INET, addr="127.0.0.1", port=port})
-p.listen(fd, p.SOMAXCONN)
-local running = true
-
-while running do
-    ret_fd = p.accept(fd)
-    if ret_fd then
-        local msg = p.recv(ret_fd, p.BUFSIZ)
-        if msg then
-            local cmd, data = nil
-            sep = msg:find(" ")
-            if sep then
-                cmd = msg:sub(1, sep - 1)
-                data = msg:sub(sep + 1)
-            else
-                cmd = msg
-            end
-            if cmd == "test" then
-                local env = eval(data)
-                if env then
-                    rsp = test(env)
-                    p.send(ret_fd, rsp)
-                end
-            elseif cmd == "add" then
-                local value = eval(data)
-                if value then
-                    table.insert(rules, value)
-                end
-            elseif cmd == "remove" then
-                if data == "tail" then
-                    table.remove(rules, #rules)
-                elseif data == "head" then
-                    table.remove(rules, 1)
-                else
-                    for index = #rules, 1, -1 do
-                        if rules[index][1]:find(data) then
-                            table.remove(rules, index)
-                        end
-                    end
-                end
-            elseif cmd == "quit" then
-                running = false
-            elseif cmd == "list" then
-                local rsp = ""
-                for index, entry in pairs(rules) do
-                    rsp = rsp .. string.format("%s => %s", entry[1], entry[2])
-                    if entry[3] then
-                        if entry[3].delay then
-                            rsp = rsp .. string.format(" @%i", entry[3].delay)
-                        end
-                        if entry[3].duration then
-                            rsp = rsp .. string.format(" +%i", entry[3].duration)
-                        end
-                    end
-                    if index < #rules then
-                        rsp = rsp .. "\n"
-                    end
-                end
-                p.send(ret_fd, rsp)
-            end
-        end
-        p.close(ret_fd)
-    end
-end
-
-p.close(fd)
index 5f6226c7c1589639fb8a2e40e603ba195f30691d..a4a7198a53d096b99eba94def934401fe50a5c2b 100644 (file)
@@ -2,12 +2,12 @@
 .\"     Title: bspwm
 .\"    Author: [see the "Author" section]
 .\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\"      Date: 11/07/2013
+.\"      Date: 11/08/2013
 .\"    Manual: Bspwm Manual
 .\"    Source: Bspwm 0.8.6
 .\"  Language: English
 .\"
-.TH "BSPWM" "1" "11/07/2013" "Bspwm 0\&.8\&.6" "Bspwm Manual"
+.TH "BSPWM" "1" "11/08/2013" "Bspwm 0\&.8\&.6" "Bspwm Manual"
 .\" -----------------------------------------------------------------
 .\" * Define some portability stuff
 .\" -----------------------------------------------------------------
@@ -861,7 +861,7 @@ All the boolean settings are \fIfalse\fR by default\&.
 .PP
 \fIrule_command\fR
 .RS 4
-External command used to retrieve rule consequences\&. It must contain at least one integer format directive which will be replaced by the ID of the window being processed\&. The output of that command must have the following format:
+External command used to retrieve rule consequences\&. The command will receive the the ID of the window being processed as its first argument\&. The output of that command must have the following format:
 \fBkey1=value1 key2=value2 \&...\fR, where
 \fBkeyN\fR
 is one of
index aa379b465e1d2ca58f9bd1e6b92f6c8bbdb19dd5..9417cfe2d47746d1762a30590ee34cf05c53e7ac 100644 (file)
@@ -533,7 +533,7 @@ Global Settings
 ~~~~~~~~~~~~~~~
 
 'rule_command'::
-    External command used to retrieve rule consequences. It must contain at least one integer format directive which will be replaced by the ID of the window being processed. The output of that command must have the following format: *key1=value1 key2=value2 ...*, where *keyN* is one of 'floating', 'fullscreen', 'locked', 'sticky', 'private', 'frame', 'center', 'lower', 'follow', 'manage', 'focus', 'desktop' or 'monitor'.
+    External command used to retrieve rule consequences. The command will receive the the ID of the window being processed as its first argument. The output of that command must have the following format: *key1=value1 key2=value2 ...*, where *keyN* is one of 'floating', 'fullscreen', 'locked', 'sticky', 'private', 'frame', 'center', 'lower', 'follow', 'manage', 'focus', 'desktop' or 'monitor'.
 
 'status_prefix'::
     Prefix prepended to each of the status lines.
index 9106cdabf9286357fa264a90f4009e2427c8f9d1..c50cafe2d3400bddd37f299b96d8cecbc1ca7569 100644 (file)
--- a/events.c
+++ b/events.c
@@ -79,7 +79,7 @@ void map_request(xcb_generic_event_t *evt)
 
     PRINTF("map request %X\n", e->window);
 
-    manage_window(mon, mon->desk, e->window);
+    schedule_window(e->window);
 }
 
 void configure_request(xcb_generic_event_t *evt)
@@ -183,11 +183,7 @@ void destroy_notify(xcb_generic_event_t *evt)
 
     PRINTF("destroy notify %X\n", e->window);
 
-    coordinates_t loc;
-    if (locate_window(e->window, &loc)) {
-        remove_node(loc.monitor, loc.desktop, loc.node);
-        arrange(loc.monitor, loc.desktop);
-    }
+    unmanage_window(e->window);
 }
 
 void unmap_notify(xcb_generic_event_t *evt)
@@ -196,11 +192,7 @@ void unmap_notify(xcb_generic_event_t *evt)
 
     PRINTF("unmap notify %X\n", e->window);
 
-    coordinates_t loc;
-    if (locate_window(e->window, &loc)) {
-        remove_node(loc.monitor, loc.desktop, loc.node);
-        arrange(loc.monitor, loc.desktop);
-    }
+    unmanage_window(e->window);
 }
 
 void property_notify(xcb_generic_event_t *evt)
index 44faf47f0a993ec99adabb1234b2520c674478db..be754da008fbe6980fb4d38f5ff576a2aeb272e3 100755 (executable)
@@ -8,7 +8,7 @@ bspc config split_ratio         0.52
 bspc config borderless_monocle  true
 bspc config gapless_monocle     true
 
-bspc config rule_command "xwinfo -cints 0x%X | xargs -d '\n' rulc -t"
+bspc config rule_command "$(which rule_command)"
 
 rulc -a 'class=="Gimp"' 'desktop=^8 follow=on floating=on'
 rulc -a 'class=="Chromium"' 'desktop=^2'
diff --git a/rule.c b/rule.c
index 15c098e21aa82c90d8760acdc0b05905a7977804..428fba151ae908a31b5d8bb474f7665f3e2e1c24 100644 (file)
--- a/rule.c
+++ b/rule.c
@@ -24,7 +24,7 @@
 
 #include <stdio.h>
 #include <string.h>
-#include <ctype.h>
+#include <unistd.h>
 #include "bspwm.h"
 #include "ewmh.h"
 #include "window.h"
 
 rule_consequence_t *make_rule_conquence(void)
 {
-    rule_consequence_t *r = calloc(1, sizeof(rule_consequence_t));
-    r->manage = r->focus = true;
-    return r;
+    rule_consequence_t *rc = calloc(1, sizeof(rule_consequence_t));
+    rc->manage = rc->focus = true;
+    return rc;
 }
 
-void handle_rules(xcb_window_t win, monitor_t **m, desktop_t **d, rule_consequence_t *csq)
+pending_rule_t *make_pending_rule(int fd, xcb_window_t win, rule_consequence_t *csq)
 {
+    pending_rule_t *pr = malloc(sizeof(pending_rule_t));
+    pr->prev = pr->next = NULL;
+    pr->fd = fd;
+    pr->win = win;
+    pr->csq = csq;
+    return pr;
+}
+
+void add_pending_rule(pending_rule_t *pr)
+{
+    if (pr == NULL)
+        return;
+    PRINTF("add pending rule %i\n", pr->fd);
+    if (pending_rule_head == NULL) {
+        pending_rule_head = pending_rule_tail = pr;
+    } else {
+        pending_rule_tail->next = pr;
+        pr->prev = pending_rule_tail;
+        pending_rule_tail = pr;
+    }
+}
+
+void remove_pending_rule(pending_rule_t *pr)
+{
+    if (pr == NULL)
+        return;
+    PRINTF("remove pending rule %i\n", pr->fd);
+    pending_rule_t *a = pr->prev;
+    pending_rule_t *b = pr->next;
+    if (a != NULL)
+        a->next = b;
+    if (b != NULL)
+        b->prev = a;
+    if (pr == pending_rule_head)
+        pending_rule_head = b;
+    if (pr == pending_rule_tail)
+        pending_rule_tail = a;
+    close(pr->fd);
+    free(pr->csq);
+    free(pr);
+}
 
+void apply_rules(xcb_window_t win, rule_consequence_t *csq)
+{
     xcb_ewmh_get_atoms_reply_t win_type;
 
     if (xcb_ewmh_get_wm_window_type_reply(ewmh, xcb_ewmh_get_wm_window_type(ewmh, win), &win_type, NULL) == 1) {
@@ -87,23 +130,42 @@ void handle_rules(xcb_window_t win, monitor_t **m, desktop_t **d, rule_consequen
     xcb_icccm_get_wm_transient_for_reply(dpy, xcb_icccm_get_wm_transient_for(dpy, win), &transient_for, NULL);
     if (transient_for != XCB_NONE)
         csq->transient = csq->floating = true;
+}
 
-    char cmd[MAXLEN];
-    snprintf(cmd, sizeof(cmd), rule_command, win);
-    FILE *results = popen(cmd, "r");
-    if (results == NULL) {
-        warn("Failed to run rule command: '%s'.", cmd);
-        return;
+bool schedule_rules(xcb_window_t win, rule_consequence_t *csq)
+{
+    int fds[2];
+    if (pipe(fds) == -1)
+        return false;
+    pid_t pid = fork();
+    if (pid == 0) {
+        if (dpy != NULL)
+            close(xcb_get_file_descriptor(dpy));
+        dup2(fds[1], 1);
+        close(fds[0]);
+        char wid[SMALEN];
+        snprintf(wid, sizeof(wid), "%i", win);
+        execl(rule_command, rule_command, wid, NULL);
+        err("Couldn't spawn rule command.\n");
+    } else if (pid > 0) {
+        close(fds[1]);
+        pending_rule_t *pr = make_pending_rule(fds[0], win, csq);
+        add_pending_rule(pr);
     }
-    char line[MAXLEN];
+    return (pid != -1);
+}
+
+void parse_rule_consequence(int fd, rule_consequence_t *csq, monitor_t **m, desktop_t **d)
+{
+    if (fd == -1)
+        return;
+    char data[BUFSIZ];
+    int nb;
     bool v;
-    while (fgets(line, sizeof(line), results) != NULL) {
-        size_t i = strlen(line) - 1;
-        while (i > 0 && isspace(line[i])) {
-            line[i] = '\0';
-            i--;
-        }
-        char *key = strtok(line, CSQ_BLK);
+    while ((nb = read(fd, data, sizeof(data))) > 0) {
+        int end = MIN(nb, (int) sizeof(data) - 1);
+        data[end] = '\0';
+        char *key = strtok(data, CSQ_BLK);
         char *value = strtok(NULL, CSQ_BLK);
         while (key != NULL && value != NULL) {
             PRINTF("%s = %s\n", key, value);
@@ -144,7 +206,6 @@ void handle_rules(xcb_window_t win, monitor_t **m, desktop_t **d, rule_consequen
             value = strtok(NULL, CSQ_BLK);
         }
     }
-    pclose(results);
     if (csq->sticky) {
         *m = mon;
         *d = mon->desk;
diff --git a/rule.h b/rule.h
index ac717ebcb516fc278f4b88b13daa1bbb4871e686..fd2bfb2eadce4a55be88ab7fee21a42bebffb16f 100644 (file)
--- a/rule.h
+++ b/rule.h
 #ifndef BSPWM_RULE_H
 #define BSPWM_RULE_H
 
-#define CSQ_BLK  " =,"
+#define CSQ_BLK  " =,\n"
 
 rule_consequence_t *make_rule_conquence(void);
-void handle_rules(xcb_window_t win, monitor_t **m, desktop_t **d, rule_consequence_t *csq);
+pending_rule_t *make_pending_rule(int fd, xcb_window_t win, rule_consequence_t *csq);
+void add_pending_rule(pending_rule_t *pr);
+void remove_pending_rule(pending_rule_t *pr);
+void apply_rules(xcb_window_t win, rule_consequence_t *csq);
+bool schedule_rules(xcb_window_t win, rule_consequence_t *csq);
+void parse_rule_consequence(int fd, rule_consequence_t *csq, monitor_t **m, desktop_t **d);
 
 #endif
index 302a122531adec28fba506f24056a483bfa75818..ecf9a410f5851fe6e627dde1c602861f074f3361 100644 (file)
@@ -30,7 +30,7 @@
 #define WM_NAME             "bspwm"
 #define CONFIG_NAME         WM_NAME "rc"
 #define CONFIG_HOME_ENV     "XDG_CONFIG_HOME"
-#define RULE_COMMAND        "true"
+#define RULE_COMMAND        "/bin/true"
 #define STATUS_PREFIX       "W"
 
 #define FOCUSED_BORDER_COLOR          "#7E7F89"
diff --git a/types.h b/types.h
index 408b338e16bed9f6a5f3650fcda01906ec3189ba..e3a8bcfe3fe9ff12c13f1178e7c47765e2e1a632 100644 (file)
--- a/types.h
+++ b/types.h
@@ -264,6 +264,15 @@ typedef struct {
     bool focus;
 } rule_consequence_t;
 
+typedef struct pending_rule_t pending_rule_t;
+struct pending_rule_t {
+    int fd;
+    xcb_window_t win;
+    rule_consequence_t *csq;
+    pending_rule_t *prev;
+    pending_rule_t *next;
+};
+
 typedef struct {
     xcb_point_t position;
     pointer_action_t action;
index 49c155399a2fda472dae68c86ef58186665c54a3..509c99294504bb9850e99653c723e8890e498ca4 100644 (file)
--- a/window.c
+++ b/window.c
 #include "tree.h"
 #include "window.h"
 
-void manage_window(monitor_t *m, desktop_t *d, xcb_window_t win)
+void schedule_window(xcb_window_t win)
 {
     coordinates_t loc;
-    xcb_get_window_attributes_reply_t *wa = xcb_get_window_attributes_reply(dpy, xcb_get_window_attributes(dpy, win), NULL);
     uint8_t override_redirect = 0;
+    xcb_get_window_attributes_reply_t *wa = xcb_get_window_attributes_reply(dpy, xcb_get_window_attributes(dpy, win), NULL);
 
     if (wa != NULL) {
         override_redirect = wa->override_redirect;
@@ -49,7 +49,19 @@ void manage_window(monitor_t *m, desktop_t *d, xcb_window_t win)
         return;
 
     rule_consequence_t *csq = make_rule_conquence();
-    handle_rules(win, &m, &d, csq);
+    apply_rules(win, csq);
+    if (!schedule_rules(win, csq)) {
+        manage_window(win, csq, -1);
+        free(csq);
+    }
+}
+
+void manage_window(xcb_window_t win, rule_consequence_t *csq, int fd)
+{
+    monitor_t *m = mon;
+    desktop_t *d = mon->desk;
+
+    parse_rule_consequence(fd, csq, &m, &d);
 
     if (csq->lower)
         window_lower(win);
@@ -119,12 +131,28 @@ void manage_window(monitor_t *m, desktop_t *d, xcb_window_t win)
     if (give_focus)
         xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
 
-    free(csq);
     num_clients++;
     ewmh_set_wm_desktop(n, d);
     ewmh_update_client_list();
 }
 
+void unmanage_window(xcb_window_t win)
+{
+    coordinates_t loc;
+    if (locate_window(win, &loc)) {
+        PRINTF("unmanage %X\n", win);
+        remove_node(loc.monitor, loc.desktop, loc.node);
+        arrange(loc.monitor, loc.desktop);
+    } else {
+        for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) {
+            if (pr->win == win) {
+                remove_pending_rule(pr);
+                return;
+            }
+        }
+    }
+}
+
 void window_draw_border(node_t *n, bool focused_window, bool focused_monitor)
 {
     if (n == NULL || (n->client->border_width < 1 && !n->client->frame)) {
@@ -320,14 +348,10 @@ void adopt_orphans(void)
     for (int i = 0; i < len; i++) {
         uint32_t idx;
         xcb_window_t win = wins[i];
-        if (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), &idx, NULL) == 1) {
-            coordinates_t loc;
-            if (ewmh_locate_desktop(idx, &loc))
-                manage_window(loc.monitor, loc.desktop, win);
-            else
-                manage_window(mon, mon->desk, win);
-        }
+        if (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), &idx, NULL) == 1)
+            schedule_window(win);
     }
+
     free(qtr);
 }
 
index bcedecc8d595e962f45e112de5bffbf7da481d8e..d10be8095fd35d4363e80043af051b406a70a6a0 100644 (file)
--- a/window.h
+++ b/window.h
@@ -31,7 +31,9 @@
 #include <xcb/xcb_icccm.h>
 #include "types.h"
 
-void manage_window(monitor_t *m, desktop_t *d, xcb_window_t win);
+void schedule_window(xcb_window_t win);
+void manage_window(xcb_window_t win, rule_consequence_t *csq, int fd);
+void unmanage_window(xcb_window_t win);
 void window_draw_border(node_t *n, bool focused_window, bool focused_monitor);
 void draw_frame_background(node_t *n, bool focused_window, bool focused_monitor);
 pointer_state_t *make_pointer_state(void);