[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] Rule Names



Hello,

This patchset is finally cleaned up and should be
ready to go.

* Rule names are now refered to as 'ruleid's.
* Disallow rule id="" (empty ids)
* Default ruleids are now handled with glib
* Add command completion for unrule -> ids
* Include negated conditions in list_rules()
* Made list_rules more similar to list_keybinds:
    the parameter list the rule was called with
    is replicated. Printing of the id property is
    enforced (even if the id was defaulted). This
    should make it easy to restore rules in
    savestate/loadstate-like scripts

Thanks
Tylo
>From 3763df8f00d67cd2224af68d9e2447f609cbd5aa Mon Sep 17 00:00:00 2001
From: Tyler Thomas Hart <htyler _at_ pdx _dot_ edu>
Date: Sat, 15 Dec 2012 15:11:50 -0800
Subject: [PATCH 1/4] Add initial support for rule ids

New char* member in the HSRule struct is added for id. New
property for rule command 'id=' added, to add custom id. New
rules without an explicit id is given an integer id. Adds flag
printid which prints the id of a newly created rule to stdout.
---
 NEWS                 |  2 ++
 doc/herbstluftwm.txt | 23 ++++++++++++++++++-----
 src/rules.c          | 45 +++++++++++++++++++++++++++++++++++++++++++++
 src/rules.h          |  1 +
 4 files changed, 66 insertions(+), 5 deletions(-)

diff --git a/NEWS b/NEWS
index 3bfc9b6..7a5558c 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,8 @@ Changes:
     * disallow focus_follows_mouse if the focus change hides another window
       (e.g. an pseudotiled window in the max layout). In that case an extra
       click is required to change the focus.
+    * rule id: rules can be given ids with the 'id' property. The id
+      can be printed to stdout using the 'printid' flag to the rule command.
 
 Release 0.5.1 on 2013-01-05
 ---------------------------
diff --git a/doc/herbstluftwm.txt b/doc/herbstluftwm.txt
index 4345dda..479a556 100644
--- a/doc/herbstluftwm.txt
+++ b/doc/herbstluftwm.txt
@@ -649,7 +649,7 @@ floating [['TAG'] *on*|*off*|*toggle*|*status*]::
     is given, floating mode is toggled. If status is given, then *on* or *off*
     is printed, depending of the floating state on 'TAG'.
 
-rule [\[--]'FLAG'|\[--]'CONDITION'|\[--]'CONSEQUENCE' ...]::
+rule [\[--]'FLAG'|\[--]'ID'|\[--]'CONDITION'|\[--]'CONSEQUENCE' ...]::
     Defines a rule which will be applied to all new clients. Its behaviour is
     described in the <<RULES,*RULES section*>>.
 
@@ -870,11 +870,23 @@ appear. Each rule matches against a certain subset of all clients and defines a
 set of properties for them (called 'CONSEQUENCE'##s##). A rule can be defined
 with this command:
 
-+rule+ [\[--]'FLAG'|\[--]'CONDITION'|\[--]'CONSEQUENCE' ...]
++rule+ [\[--]'FLAG'|\[--]'ID'|\[--]'CONDITION'|\[--]'CONSEQUENCE' ...]
 
-Each rule consists of a list of 'FLAG'##s##, 'CONDITION'##s## and
-'CONSEQUENCE'##s## (each of them can be optionally prefixed with two dashes
-(+--+) to provide a more *iptables*(8)-like feeling).
+Each rule consists of a list of 'FLAG'##s##, 'CONDITION'##s## 'CONSEQUENCE'##s##
+and, optionally, an 'ID'. (each of them can be optionally prefixed with two
+dashes (+--+) to provide a more *iptables*(8)-like feeling).
+
+Each rule can be given a custom identifier by specifying the 'ID' property:
+
+    * +[--]id+='VALUE'
+
+If multiple id properties are specified, the last one in the list will be
+applied. If no ID is given, then the rule will be given an integer name that
+represents the index of the rule since the last 'unrule -F' or 'reload' command.
+
+TIP: Rule ID's default to an incremental index. These default IDs should be
+unique, unless you assign a different rule a custom integer ID. Defaulted IDs
+can be captured with the 'printid' flag.
 
 If a new client
 appears, herbstluftwm tries to apply each rule to this new client as follows:
@@ -969,6 +981,7 @@ A rule's behaviour can be configured by some special 'FLAGS':
     * +not+: negates the next 'CONDITION'.
     * +!+: same as +not+.
     * +once+: only apply this rule once (and delete it afterwards).
+    * +printid+: prints the id of the newly created rule to stdout.
 
 Examples:
 
diff --git a/src/rules.c b/src/rules.c
index eb2891b..413510b 100644
--- a/src/rules.c
+++ b/src/rules.c
@@ -77,6 +77,7 @@ static HSConditionType g_condition_types[] = {
 
 int     g_maxage_type; // index of "maxage"
 time_t  g_current_rule_birth_time; // data from rules_apply() to condition_maxage()
+unsigned long long g_rule_id_index; // incremental index of rule ID
 
 static HSConsequenceType g_consequence_types[] = {
     { "tag",            consequence_tag             },
@@ -97,11 +98,13 @@ GQueue g_rules = G_QUEUE_INIT; // a list of HSRule* elements
 // RULES //
 void rules_init() {
     g_maxage_type = find_condition_type("maxage");
+    g_rule_id_index = 0;
 }
 
 void rules_destroy() {
     g_queue_foreach(&g_rules, (GFunc)rule_destroy, NULL);
     g_queue_clear(&g_rules);
+    g_rule_id_index = 0;
 }
 
 // condition types //
@@ -230,12 +233,38 @@ void consequence_destroy(HSConsequence* cons) {
     g_free(cons);
 }
 
+bool rule_id_replace(HSRule* rule, char op, char* value, GString* output) {
+    switch (op) {
+        case '=':
+            if (*value == '\0') {
+                g_string_append_printf(output,
+                    "rule: Rule id cannot be empty");
+                return false;
+                break;
+            }
+            g_free(rule->id);
+            rule->id = g_strdup(value);
+            break;
+        default:
+            g_string_append_printf(output,
+                "rule: Unknown rule id operation \"%c\"\n", op);
+            return false;
+            break;
+    }
+    return true;
+}
+
 // rules parsing //
 
 HSRule* rule_create() {
     HSRule* rule = g_new0(HSRule, 1);
     rule->once = false;
     rule->birth_time = get_monotonic_timestamp();
+    // Add name. Defaults to index number.
+    GString* name = g_string_sized_new(20);
+    g_string_printf(name, "%llu", g_rule_id_index++);
+    rule->id = g_strdup(name->str);
+    g_string_free(name, true);
     return rule;
 }
 
@@ -250,6 +279,8 @@ void rule_destroy(HSRule* rule) {
         consequence_destroy(rule->consequences[i]);
     }
     g_free(rule->consequences);
+    // free id
+    g_free(rule->id);
     // free rule itself
     g_free(rule);
 }
@@ -304,6 +335,7 @@ int rule_add_command(int argc, char** argv, GString* output) {
     HSRule* rule = rule_create();
     HSCondition* cond;
     HSConsequence* cons;
+    bool printid = false;
     bool negated = false;
     struct {
         char* name;
@@ -312,6 +344,7 @@ int rule_add_command(int argc, char** argv, GString* output) {
         { "not",    &negated },
         { "!",      &negated },
         { "once",   &rule->once },
+        { "printid",&printid },
     };
 
     // parse rule incrementally. always maintain a correct rule in rule
@@ -358,6 +391,14 @@ int rule_add_command(int argc, char** argv, GString* output) {
             rule_add_consequence(rule, cons);
         }
 
+        // Check for a provided id, and replace default index if so
+        else if (consorcond && (!strcmp(name,"id"))) {
+            if (!rule_id_replace(rule, op, value, output)) {
+                rule_destroy(rule);
+                return HERBST_INVALID_ARGUMENT;
+            }
+        }
+
         else {
             // need to hardcode "rule:" here because args are shifted
             g_string_append_printf(output,
@@ -367,6 +408,10 @@ int rule_add_command(int argc, char** argv, GString* output) {
         }
     }
 
+    if (printid) {
+       g_string_append_printf(output, "ruleid\t%s\n", rule->id);
+    }
+
     g_queue_push_tail(&g_rules, rule);
     return 0;
 }
diff --git a/src/rules.h b/src/rules.h
index 9384711..bcdc638 100644
--- a/src/rules.h
+++ b/src/rules.h
@@ -43,6 +43,7 @@ typedef struct {
 } HSConsequence;
 
 typedef struct {
+    char*           id;
     HSCondition**   conditions;
     int             condition_count;
     HSConsequence** consequences;
-- 
1.8.1

>From 0ef6f2e75b311a903fd59c2a5c221580116f9746 Mon Sep 17 00:00:00 2001
From: Tyler Thomas Hart <htyler _at_ pdx _dot_ edu>
Date: Sat, 15 Dec 2012 15:49:14 -0800
Subject: [PATCH 2/4] Allow unrule command to remove by id

---
 NEWS                 |  1 +
 doc/herbstluftwm.txt |  6 +++---
 src/rules.c          | 35 ++++++++++++++++++++++++++++++++---
 3 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/NEWS b/NEWS
index 7a5558c..9e51f11 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,7 @@ Changes:
       click is required to change the focus.
     * rule id: rules can be given ids with the 'id' property. The id
       can be printed to stdout using the 'printid' flag to the rule command.
+      Unrule command accepts ids, and can remove rules individually.
 
 Release 0.5.1 on 2013-01-05
 ---------------------------
diff --git a/doc/herbstluftwm.txt b/doc/herbstluftwm.txt
index 479a556..2911532 100644
--- a/doc/herbstluftwm.txt
+++ b/doc/herbstluftwm.txt
@@ -653,9 +653,9 @@ rule [\[--]'FLAG'|\[--]'ID'|\[--]'CONDITION'|\[--]'CONSEQUENCE' ...]::
     Defines a rule which will be applied to all new clients. Its behaviour is
     described in the <<RULES,*RULES section*>>.
 
-unrule *--all*|*-F*::
-    If --all or -F is passed, then all rules are removed.
-    (It is not possible yet to remove certain rules)
+unrule 'ID'|*--all*|*-F*::
+    Removes all rules named 'ID'. If --all or -F is passed, then all rules are
+    removed.
 
 fullscreen [*on*|*off*|*toggle*]::
     Sets or toggles the fullscreen state of the focused client. If no argument
diff --git a/src/rules.c b/src/rules.c
index 413510b..06428e0 100644
--- a/src/rules.c
+++ b/src/rules.c
@@ -285,6 +285,31 @@ void rule_destroy(HSRule* rule) {
     g_free(rule);
 }
 
+// Compares the id of two rules.
+static gint rule_compare_id(const HSRule* a, const HSRule* b) {
+    return (!strcmp(a->id, b->id)) ? 0 : -1;
+}
+
+// Looks up rules of a given id and removes them from the queue
+bool rule_find_pop(char* id) {
+    GList* rule = { NULL };
+    bool status = false; // Will be returned as true if any is found
+    HSRule rule_find = { .id = id };
+    while ((rule = g_queue_find_custom(&g_rules, &rule_find,
+                        (GCompareFunc)rule_compare_id))) {
+        // Check if rule with id exists
+        if ( rule == NULL ) {
+            break;
+        }
+        status = true;
+        // If so, clear data
+        rule_destroy(rule->data);
+        // Remove and free empty link
+        g_queue_delete_link(&g_rules, rule);
+    }
+    return status;
+}
+
 // parses an arg like NAME=VALUE to res_name, res_operation and res_value
 bool tokenize_arg(char* condition,
                   char** res_name, char* res_operation, char** res_value) {
@@ -425,12 +450,16 @@ int rule_remove_command(int argc, char** argv, GString* output) {
         // remove all rules
         g_queue_foreach(&g_rules, (GFunc)rule_destroy, NULL);
         g_queue_clear(&g_rules);
+        g_rule_id_index = 0;
         return 0;
     }
 
-    g_string_append_printf(output,
-        "%s: This command must be used with --all/-F\n", argv[0]);
-    return HERBST_INVALID_ARGUMENT;
+    // Deletes rule with given id
+    if (!rule_find_pop(argv[1])) {
+        g_string_append_printf(output, "Couldn't find rule: \"%s\"", argv[1]);
+        return HERBST_INVALID_ARGUMENT;
+    }
+    return 0;
 }
 
 // rules applying //
-- 
1.8.1

>From 141bb173a1c5927b1540acfc921d428201695411 Mon Sep 17 00:00:00 2001
From: Tyler Thomas Hart <htyler _at_ pdx _dot_ edu>
Date: Sat, 15 Dec 2012 16:16:28 -0800
Subject: [PATCH 3/4] Add command list_rules

Regex in HSRule struct moved into substruct to add the original
regex pattern to the condition-union.
---
 NEWS                 |  1 +
 doc/herbstluftwm.txt |  4 ++++
 src/command.c        |  1 +
 src/main.c           |  1 +
 src/rules.c          | 51 +++++++++++++++++++++++++++++++++++++++++++++++----
 src/rules.h          |  6 +++++-
 6 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/NEWS b/NEWS
index 9e51f11..bca8bfc 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,7 @@ Changes:
     * rule id: rules can be given ids with the 'id' property. The id
       can be printed to stdout using the 'printid' flag to the rule command.
       Unrule command accepts ids, and can remove rules individually.
+    * new command: list_rules
 
 Release 0.5.1 on 2013-01-05
 ---------------------------
diff --git a/doc/herbstluftwm.txt b/doc/herbstluftwm.txt
index 2911532..53aa0b6 100644
--- a/doc/herbstluftwm.txt
+++ b/doc/herbstluftwm.txt
@@ -162,6 +162,10 @@ list_commands::
     List currently configured monitors with their index, area (as rectangle),
     name (if named) and currently viewed tag.
 
+list_rules::
+    Lists all active rules. Each line consists of all the parameters the rule
+    was called with, plus it's id, separated by tabs.
+
 list_keybinds::
     Lists all bound keys with their associated command. Each line consists of
     one key combination and the command with its parameters separated by tabs.
diff --git a/src/command.c b/src/command.c
index 62c54d3..49cff6b 100644
--- a/src/command.c
+++ b/src/command.c
@@ -58,6 +58,7 @@ struct {
     { "list_commands",  1,  no_completion },
     { "list_monitors",  1,  no_completion },
     { "list_keybinds",  1,  no_completion },
+    { "list_rules",     1,  no_completion },
     { "lock",           1,  no_completion },
     { "unlock",         1,  no_completion },
     { "keybind",        2,  keybind_parameter_expected },
diff --git a/src/main.c b/src/main.c
index a1dc7a8..6ad0460 100644
--- a/src/main.c
+++ b/src/main.c
@@ -145,6 +145,7 @@ CommandBinding g_commands[] = {
     CMD_BIND(             "raise",          raise_command),
     CMD_BIND(             "rule",           rule_add_command),
     CMD_BIND(             "unrule",         rule_remove_command),
+    CMD_BIND(             "list_rules",     rule_print_all_command),
     CMD_BIND(             "layout",         print_layout_command),
     CMD_BIND(             "stack",          print_stack_command),
     CMD_BIND(             "dump",           print_layout_command),
diff --git a/src/rules.c b/src/rules.c
index 06428e0..c6aa9ba 100644
--- a/src/rules.c
+++ b/src/rules.c
@@ -144,15 +144,16 @@ HSCondition* condition_create(int type, char op, char* value, GString* output) {
 
         case '~':
             cond.value_type = CONDITION_VALUE_TYPE_REGEX;
-            int status = regcomp(&cond.value.exp, value, REG_EXTENDED);
+            int status = regcomp(&cond.value.reg.exp, value, REG_EXTENDED);
             if (status != 0) {
                 char buf[ERROR_STRING_BUF_SIZE];
-                regerror(status, &cond.value.exp, buf, ERROR_STRING_BUF_SIZE);
+                regerror(status, &cond.value.reg.exp, buf, ERROR_STRING_BUF_SIZE);
                 g_string_append_printf(output,
                     "rule: Can not parse value \"%s\" from condition \"%s\": \"%s\"\n",
                     value, g_condition_types[type].name, buf);
                 return NULL;
             }
+            cond.value.reg.str = g_strdup(value);
             break;
 
         default:
@@ -179,7 +180,8 @@ void condition_destroy(HSCondition* cond) {
             free(cond->value.str);
             break;
         case CONDITION_VALUE_TYPE_REGEX:
-            regfree(&cond->value.exp);
+            regfree(&cond->value.reg.exp);
+            g_free(cond->value.reg.str);
             break;
         default:
             break;
@@ -310,6 +312,47 @@ bool rule_find_pop(char* id) {
     return status;
 }
 
+// List all rules in queue
+static void rule_print_append_output(HSRule* rule, GString* output) {
+    g_string_append_printf(output, "id=%s\t", rule->id);
+    // Append conditions
+    for (int i = 0; i < rule->condition_count; i++) {
+        if (rule->conditions[i]->negated) { // Include flag if negated
+            g_string_append_printf(output, "not\t");
+        }
+        g_string_append_printf(output, "%s=",
+            g_condition_types[rule->conditions[i]->condition_type].name);
+        switch (rule->conditions[i]->value_type) {
+            case CONDITION_VALUE_TYPE_STRING:
+                g_string_append_printf(output, "%s\t",
+                    rule->conditions[i]->value.str);
+                break;
+            case CONDITION_VALUE_TYPE_REGEX:
+                g_string_append_printf(output, "%s\t",
+                    rule->conditions[i]->value.reg.str);
+                break;
+            default: /* CONDITION_VALUE_TYPE_INTEGER: */
+                g_string_append_printf(output, "%i\t",
+                    rule->conditions[i]->value.integer);
+                break;
+        }
+    }
+    // Append consequences
+    for (int i = 0; i < rule->consequence_count; i++) {
+        g_string_append_printf(output, "%s=%s\t",
+            g_consequence_types[rule->consequences[i]->type].name,
+            rule->consequences[i]->value.str);
+    }
+    // Print new line
+    g_string_append_c(output, '\n');
+}
+
+int rule_print_all_command(int argc, char** argv, GString* output) {
+    // Print entry for each in the queue
+    g_queue_foreach(&g_rules, (GFunc)rule_print_append_output, output);
+    return 0;
+}
+
 // parses an arg like NAME=VALUE to res_name, res_operation and res_value
 bool tokenize_arg(char* condition,
                   char** res_name, char* res_operation, char** res_value) {
@@ -556,7 +599,7 @@ bool condition_string(HSCondition* rule, char* string) {
             return !strcmp(string, rule->value.str);
             break;
         case CONDITION_VALUE_TYPE_REGEX:
-            status = regexec(&rule->value.exp, string, 1, &match, 0);
+            status = regexec(&rule->value.reg.exp, string, 1, &match, 0);
             // only accept it, if it matches the entire string
             if (status == 0
                 && match.rm_so == 0
diff --git a/src/rules.h b/src/rules.h
index bcdc638..7d5ab69 100644
--- a/src/rules.h
+++ b/src/rules.h
@@ -29,7 +29,10 @@ typedef struct {
     bool negated;
     union {
         char*       str;
-        regex_t     exp;
+        struct {
+            regex_t     exp;
+            char*       str;
+        } reg;
         int         integer;
     } value;
 } HSCondition;
@@ -74,6 +77,7 @@ void rule_destroy(HSRule* rule);
 
 int rule_add_command(int argc, char** argv, GString* output);
 int rule_remove_command(int argc, char** argv, GString* output);
+int rule_print_all_command(int argc, char** argv, GString* output);
 
 #endif
 
-- 
1.8.1

>From eee3dc9612de29d28ba3d447986b9e9aa03f4d9d Mon Sep 17 00:00:00 2001
From: Tyler Thomas Hart <htyler _at_ pdx _dot_ edu>
Date: Wed, 9 Jan 2013 16:22:15 -0800
Subject: [PATCH 4/4] Add command completion for rule ids

---
 src/command.c | 22 ++++++++++++++++++++--
 src/command.h |  1 +
 src/rules.h   |  2 ++
 3 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/src/command.c b/src/command.c
index 49cff6b..acc3af2 100644
--- a/src/command.c
+++ b/src/command.c
@@ -11,6 +11,7 @@
 #include "key.h"
 #include "clientlist.h"
 #include "monitor.h"
+#include "rules.h"
 
 #include <glib.h>
 #include <string.h>
@@ -20,7 +21,7 @@
 
 static char* completion_directions[]    = { "left", "right", "down", "up",NULL};
 static char* completion_focus_args[]    = { "-i", "-e", NULL };
-static char* completion_unrule_args[]   = { "-F", "--all", NULL };
+static char* completion_unrule_flags[]   = { "-F", "--all", NULL };
 static char* completion_keyunbind_args[]= { "-F", "--all", NULL };
 static char* completion_flag_args[]     = { "on", "off", "toggle", NULL };
 static char* completion_status[]        = { "status", NULL };
@@ -197,7 +198,7 @@ struct {
     { "set_layout",     EQ, 1,  .list = g_layout_names },
     { "cycle_layout",   EQ, 1,  .list = completion_pm_one },
     { "cycle_layout",   GE, 2,  .list = g_layout_names },
-    { "unrule",         EQ, 1,  .list = completion_unrule_args },
+    { "unrule",         EQ, 1,  .function = complete_against_rule_names },
     { "use",            EQ, 1,  .function = complete_against_tags },
     { "use_index",      EQ, 1,  .list = completion_pm_one },
     { "use_index",      EQ, 2,  .list = completion_use_index_args },
@@ -316,6 +317,23 @@ void complete_against_tags(int argc, char** argv, int pos, GString* output) {
     }
 }
 
+void complete_against_rule_names(int argc, char** argv, int pos, GString* output) {
+    char* needle;
+    if (pos >= argc) {
+        needle = "";
+    } else {
+        needle = argv[pos];
+    }
+    // Complete flags
+    complete_against_list(needle, completion_unrule_flags, output);
+    // Complete ids
+    GList* cur_rule = g_queue_peek_head_link(&g_rules);
+    while (cur_rule != NULL) {
+        try_complete(needle, ((HSRule*)cur_rule->data)->id, output);
+        cur_rule = g_list_next(cur_rule);
+    }
+}
+
 void complete_negate(int argc, char** argv, int pos, GString* output) {
     if (pos <= 0) {
         return;
diff --git a/src/command.h b/src/command.h
index 6839770..b29ecb5 100644
--- a/src/command.h
+++ b/src/command.h
@@ -55,6 +55,7 @@ int complete_against_commands(int argc, char** argv, int position,
                               GString* output);
 void complete_against_keybind_command(int argc, char** argv, int position,
                                       GString* output);
+void complete_against_rule_names(int argc, char** argv, int pos, GString* output);
 void complete_chain(int argc, char** argv, int position, GString* output);
 
 int command_chain(char* separator, bool (*condition)(int laststatus),
diff --git a/src/rules.h b/src/rules.h
index 7d5ab69..41193d0 100644
--- a/src/rules.h
+++ b/src/rules.h
@@ -13,6 +13,8 @@
 struct HSClient;
 struct HSTag;
 
+extern GQueue g_rules;
+
 enum {
     CONDITION_VALUE_TYPE_STRING,
     CONDITION_VALUE_TYPE_REGEX,
-- 
1.8.1