Add queries to If actions
authorDana Jansens <danakj@orodu.net>
Sun, 18 Aug 2013 23:29:05 +0000 (19:29 -0400)
committerDana Jansens <danakj@orodu.net>
Mon, 19 Aug 2013 00:04:16 +0000 (20:04 -0400)
This allows the If action to run queries against a client other than
the target of the actions being run, for example to check state on
the focused window while performing actions on another window during
focus cycling.

The syntax looks like

<action name="If">
  <query target="default">
    <title>FooBar</title>
    <maximized>yes</maximized>
  </query>
  <query target="focus">
    <desktop>3</desktop>
  </query>
  <then>
   <action name="NextDesktop"/>
  </then>
</action>

The above checks the client window that the actions will run on to
verify that its title is "FooBar" and that it is maximized. If that
is true, it also checks that the currently focused client window is
on desktop 3. If that is true also, then it runs the NextDesktop
action.

The target="" option can be set to "default" which uses the client
window that the actions will run on, or it can be "focus" which uses
the client window that is currently focused.

The <query> tag is optional, and the conditions inside the query can
be placed directly inside the If <action> tag, as they were before
this change. In that case, a default <query> tag is assumed with
target="default" which matches the previous behaviour.

Multiple <query> tags can be present, and they must all be true in
order to run the actions in <then>. If any one is false, the actions
in <else> will be run instead.

openbox/actions/if.c

index 411e95c..8a31c9a 100644 (file)
@@ -1,3 +1,22 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   if.c for the Openbox window manager
+   Copyright (c) 2007        Mikael Magnusson
+   Copyright (c) 2007        Dana Jansens
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
 #include "openbox/actions.h"
 #include "openbox/misc.h"
 #include "openbox/client.h"
@@ -6,7 +25,13 @@
 #include "openbox/focus.h"
 #include <glib.h>
 
+typedef enum {
+    QUERY_TARGET_IS_ACTION_TARGET,
+    QUERY_TARGET_IS_FOCUS_TARGET,
+} QueryTarget;
+
 typedef struct {
+    QueryTarget target;
     gboolean shaded_on;
     gboolean shaded_off;
     gboolean maxvert_on;
@@ -33,6 +58,10 @@ typedef struct {
     GPatternSpec *matchtitle;
     GRegex *regextitle;
     gchar  *exacttitle;
+} Query;
+
+typedef struct {
+    GArray* queries;
     GSList *thenacts;
     GSList *elseacts;
 } Options;
@@ -61,37 +90,37 @@ static inline void set_bool(xmlNodePtr node,
     }
 }
 
-static gpointer setup_func(xmlNodePtr node)
-{
-    xmlNodePtr n;
-    Options *o;
+static void setup_query(Options* o, xmlNodePtr node, QueryTarget target) {
+    Query *q = g_slice_new0(Query);
+    g_array_append_val(o->queries, q);
 
-    o = g_slice_new0(Options);
+    q->target = target;
 
-    set_bool(node, "shaded", &o->shaded_on, &o->shaded_off);
-    set_bool(node, "maximized", &o->maxfull_on, &o->maxfull_off);
-    set_bool(node, "maximizedhorizontal", &o->maxhorz_on, &o->maxhorz_off);
-    set_bool(node, "maximizedvertical", &o->maxvert_on, &o->maxvert_off);
-    set_bool(node, "iconified", &o->iconic_on, &o->iconic_off);
-    set_bool(node, "focused", &o->focused, &o->unfocused);
-    set_bool(node, "urgent", &o->urgent_on, &o->urgent_off);
-    set_bool(node, "undecorated", &o->decor_off, &o->decor_on);
-    set_bool(node, "omnipresent", &o->omnipresent_on, &o->omnipresent_off);
+    set_bool(node, "shaded", &q->shaded_on, &q->shaded_off);
+    set_bool(node, "maximized", &q->maxfull_on, &q->maxfull_off);
+    set_bool(node, "maximizedhorizontal", &q->maxhorz_on, &q->maxhorz_off);
+    set_bool(node, "maximizedvertical", &q->maxvert_on, &q->maxvert_off);
+    set_bool(node, "iconified", &q->iconic_on, &q->iconic_off);
+    set_bool(node, "focused", &q->focused, &q->unfocused);
+    set_bool(node, "urgent", &q->urgent_on, &q->urgent_off);
+    set_bool(node, "undecorated", &q->decor_off, &q->decor_on);
+    set_bool(node, "omnipresent", &q->omnipresent_on, &q->omnipresent_off);
 
+    xmlNodePtr n;
     if ((n = obt_xml_find_node(node, "desktop"))) {
         gchar *s;
         if ((s = obt_xml_node_string(n))) {
             if (!g_ascii_strcasecmp(s, "current"))
-                o->desktop_current = TRUE;
+                q->desktop_current = TRUE;
             if (!g_ascii_strcasecmp(s, "other"))
-                o->desktop_other = TRUE;
+                q->desktop_other = TRUE;
             else
-                o->desktop_number = atoi(s);
+                q->desktop_number = atoi(s);
             g_free(s);
         }
     }
     if ((n = obt_xml_find_node(node, "activedesktop"))) {
-        o->screendesktop_number = obt_xml_node_int(n);
+        q->screendesktop_number = obt_xml_node_int(n);
     }
     if ((n = obt_xml_find_node(node, "title"))) {
         gchar *s, *type = NULL;
@@ -99,19 +128,31 @@ static gpointer setup_func(xmlNodePtr node)
             if (!obt_xml_attr_string(n, "type", &type) ||
                 !g_ascii_strcasecmp(type, "pattern"))
             {
-                o->matchtitle = g_pattern_spec_new(s);
+                q->matchtitle = g_pattern_spec_new(s);
             } else if (type && !g_ascii_strcasecmp(type, "regex")) {
-                o->regextitle = g_regex_new(s, 0, 0, NULL);
+                q->regextitle = g_regex_new(s, 0, 0, NULL);
             } else if (type && !g_ascii_strcasecmp(type, "exact")) {
-                o->exacttitle = g_strdup(s);
+                q->exacttitle = g_strdup(s);
             }
             g_free(s);
         }
     }
     if ((n = obt_xml_find_node(node, "monitor"))) {
-        o->client_monitor = obt_xml_node_int(n);
+        q->client_monitor = obt_xml_node_int(n);
     }
+}
 
+static gpointer setup_func(xmlNodePtr node)
+{
+    Options *o = g_slice_new0(Options);
+
+    gboolean zero_terminated = FALSE;
+    gboolean clear_to_zero_on_alloc = FALSE;
+    o->queries = g_array_new(zero_terminated,
+                             clear_to_zero_on_alloc,
+                             sizeof(Query*));
+
+    xmlNodePtr n;
     if ((n = obt_xml_find_node(node, "then"))) {
         xmlNodePtr m;
 
@@ -133,6 +174,25 @@ static gpointer setup_func(xmlNodePtr node)
         }
     }
 
+    xmlNodePtr query_node = obt_xml_find_node(node, "query");
+    if (!query_node) {
+        /* The default query if none is specified. It uses the conditions
+           found in the action's node. */
+        setup_query(o,
+                    node,
+                    QUERY_TARGET_IS_ACTION_TARGET);
+    } else {
+        while (query_node) {
+            QueryTarget query_target = QUERY_TARGET_IS_ACTION_TARGET;
+            if (obt_xml_attr_contains(query_node, "target", "focus"))
+                query_target = QUERY_TARGET_IS_FOCUS_TARGET;
+
+            setup_query(o, query_node->children, query_target);
+
+            query_node = obt_xml_find_node(query_node->next, "query");
+        }
+    }
+
     return o;
 }
 
@@ -140,6 +200,20 @@ static void free_func(gpointer options)
 {
     Options *o = options;
 
+    guint i;
+    for (i = 0; i < o->queries->len; ++i) {
+        Query *q = g_array_index(o->queries, Query*, i);
+
+        if (q->matchtitle)
+            g_pattern_spec_free(q->matchtitle);
+        if (q->regextitle)
+            g_regex_unref(q->regextitle);
+        if (q->exacttitle)
+            g_free(q->exacttitle);
+
+        g_slice_free(Query, q);
+    }
+
     while (o->thenacts) {
         actions_act_unref(o->thenacts->data);
         o->thenacts = g_slist_delete_link(o->thenacts, o->thenacts);
@@ -148,13 +222,8 @@ static void free_func(gpointer options)
         actions_act_unref(o->elseacts->data);
         o->elseacts = g_slist_delete_link(o->elseacts, o->elseacts);
     }
-    if (o->matchtitle)
-        g_pattern_spec_free(o->matchtitle);
-    if (o->regextitle)
-        g_regex_unref(o->regextitle);
-    if (o->exacttitle)
-        g_free(o->exacttitle);
 
+    g_array_unref(o->queries);
     g_slice_free(Options, o);
 }
 
@@ -162,52 +231,123 @@ static void free_func(gpointer options)
 static gboolean run_func(ObActionsData *data, gpointer options)
 {
     Options *o = options;
+    ObClient *action_target = data->client;
+    gboolean is_true = TRUE;
+
+    guint i;
+    for (i = 0; i < o->queries->len; ++i) {
+        Query *q = g_array_index(o->queries, Query*, i);
+        ObClient *query_target = NULL;
+
+        switch (q->target) {
+        case QUERY_TARGET_IS_ACTION_TARGET:
+            query_target = data->client;
+            break;
+        case QUERY_TARGET_IS_FOCUS_TARGET:
+            query_target = focus_client;
+            break;
+        }
+
+        /* If there's no client to query, then false. */
+        is_true &= query_target != NULL;
+
+        if (q->shaded_on)
+            is_true &= query_target->shaded;
+        if (q->shaded_off)
+            is_true &= !query_target->shaded;
+
+        if (q->iconic_on)
+            is_true &= query_target->iconic;
+        if (q->iconic_off)
+            is_true &= !query_target->iconic;
+
+        if (q->maxhorz_on)
+            is_true &= query_target->max_horz;
+        if (q->maxhorz_off)
+            is_true &= !query_target->max_horz;
+
+        if (q->maxvert_on)
+            is_true &= query_target->max_vert;
+        if (q->maxvert_off)
+            is_true &= !query_target->max_vert;
+
+        gboolean is_max_full =
+            query_target->max_vert && query_target->max_horz;
+        if (q->maxfull_on)
+            is_true &= is_max_full;
+        if (q->maxfull_off)
+            is_true &= !is_max_full;
+
+        if (q->focused)
+            is_true &= query_target == focus_client;
+        if (q->unfocused)
+            is_true &= query_target != focus_client;
+
+        gboolean is_urgent =
+            query_target->urgent || query_target->demands_attention;
+        if (q->urgent_on)
+            is_true &= is_urgent;
+        if (q->urgent_off)
+            is_true &= !is_urgent;
+
+        gboolean has_visible_title_bar =
+            !query_target->undecorated &&
+            (query_target->decorations & OB_FRAME_DECOR_TITLEBAR);
+        if (q->decor_on)
+            is_true &= has_visible_title_bar;
+        if (q->decor_off)
+            is_true &= !has_visible_title_bar;
+
+        if (q->omnipresent_on)
+            is_true &= query_target->desktop == DESKTOP_ALL;
+        if (q->omnipresent_off)
+            is_true &= query_target->desktop != DESKTOP_ALL;
+
+        gboolean is_on_current_desktop =
+            query_target->desktop == screen_desktop ||
+            query_target->desktop == DESKTOP_ALL;
+        if (q->desktop_current)
+            is_true &= is_on_current_desktop;
+        if (q->desktop_other)
+            is_true &= !is_on_current_desktop;
+
+        if (q->desktop_number) {
+            gboolean is_on_desktop =
+                query_target->desktop == q->desktop_number - 1 ||
+                query_target->desktop == DESKTOP_ALL;
+            is_true &= is_on_desktop;
+        }
+
+        if (q->screendesktop_number)
+            is_true &= screen_desktop == q->screendesktop_number - 1;
+
+        if (q->matchtitle) {
+            is_true &= g_pattern_match_string(q->matchtitle,
+                                              query_target->original_title);
+        }
+        if (q->regextitle) {
+            is_true &= g_regex_match(q->regextitle,
+                                     query_target->original_title,
+                                     0,
+                                     NULL);
+        }
+        if (q->exacttitle)
+            is_true &= !strcmp(q->exacttitle, query_target->original_title);
+
+        if (q->client_monitor)
+            is_true &= client_monitor(query_target) == q->client_monitor - 1;
+
+    }
+
     GSList *acts;
-    ObClient *c = data->client;
-
-    if (c &&
-        (!o->shaded_on   ||  c->shaded) &&
-        (!o->shaded_off  || !c->shaded) &&
-        (!o->iconic_on   ||  c->iconic) &&
-        (!o->iconic_off  || !c->iconic) &&
-        (!o->maxhorz_on  ||  c->max_horz) &&
-        (!o->maxhorz_off || !c->max_horz) &&
-        (!o->maxvert_on  ||  c->max_vert) &&
-        (!o->maxvert_off || !c->max_vert) &&
-        (!o->maxfull_on  ||  (c->max_vert && c->max_horz)) &&
-        (!o->maxfull_off || !(c->max_vert && c->max_horz)) &&
-        (!o->focused     ||  (c == focus_client)) &&
-        (!o->unfocused   || !(c == focus_client)) &&
-        (!o->urgent_on   ||  (c->urgent || c->demands_attention)) &&
-        (!o->urgent_off  || !(c->urgent || c->demands_attention)) &&
-        (!o->decor_off   ||  (c->undecorated || !(c->decorations & OB_FRAME_DECOR_TITLEBAR))) &&
-        (!o->decor_on    ||  (!c->undecorated && (c->decorations & OB_FRAME_DECOR_TITLEBAR))) &&
-        (!o->omnipresent_on  || (c->desktop == DESKTOP_ALL)) &&
-        (!o->omnipresent_off || (c->desktop != DESKTOP_ALL)) &&
-        (!o->desktop_current || ((c->desktop == screen_desktop) ||
-                                 (c->desktop == DESKTOP_ALL))) &&
-        (!o->desktop_other   || ((c->desktop != screen_desktop) &&
-                                 (c->desktop != DESKTOP_ALL))) &&
-        (!o->desktop_number  || ((c->desktop == o->desktop_number - 1) ||
-                                 (c->desktop == DESKTOP_ALL))) &&
-        (!o->screendesktop_number || screen_desktop == o->screendesktop_number - 1) &&
-        (!o->matchtitle ||
-         (g_pattern_match_string(o->matchtitle, c->original_title))) &&
-        (!o->regextitle ||
-         (g_regex_match(o->regextitle, c->original_title, 0, NULL))) &&
-        (!o->exacttitle ||
-         (!strcmp(o->exacttitle, c->original_title))) &&
-        (!o->client_monitor ||
-         (o->client_monitor == client_monitor(c) + 1)))
-    {
+    if (is_true)
         acts = o->thenacts;
-    }
     else
         acts = o->elseacts;
 
     actions_run_acts(acts, data->uact, data->state,
                      data->x, data->y, data->button,
-                     data->context, data->client);
+                     data->context, action_target);
 
     return FALSE;
 }