Don't segfault when there's no target in If
[dana/openbox.git] / openbox / actions / if.c
index 8a31c9a..67d067e 100644 (file)
@@ -30,6 +30,22 @@ typedef enum {
     QUERY_TARGET_IS_FOCUS_TARGET,
 } QueryTarget;
 
+typedef enum {
+    MATCH_TYPE_NONE = 0,
+    MATCH_TYPE_PATTERN,
+    MATCH_TYPE_REGEX,
+    MATCH_TYPE_EXACT,
+} MatchType;
+
+typedef struct {
+    MatchType type;
+    union m {
+        GPatternSpec *pattern;
+        GRegex *regex;
+        gchar *exact;
+    } m;
+} TypedMatch;
+
 typedef struct {
     QueryTarget target;
     gboolean shaded_on;
@@ -55,24 +71,33 @@ typedef struct {
     guint    desktop_number;
     guint    screendesktop_number;
     guint    client_monitor;
-    GPatternSpec *matchtitle;
-    GRegex *regextitle;
-    gchar  *exacttitle;
+    TypedMatch title;
+    TypedMatch class;
+    TypedMatch name;
+    TypedMatch role;
+    TypedMatch type;
 } Query;
 
 typedef struct {
     GArray* queries;
     GSList *thenacts;
     GSList *elseacts;
+    gboolean stop;
 } Options;
 
 static gpointer setup_func(xmlNodePtr node);
 static void     free_func(gpointer options);
-static gboolean run_func(ObActionsData *data, gpointer options);
+static gboolean run_func_if(ObActionsData *data, gpointer options);
+static gboolean run_func_stop(ObActionsData *data, gpointer options);
+static gboolean run_func_foreach(ObActionsData *data, gpointer options);
 
 void action_if_startup(void)
 {
-    actions_register("If", setup_func, free_func, run_func);
+    actions_register("If", setup_func, free_func, run_func_if);
+    actions_register("Stop", NULL, NULL, run_func_stop);
+    actions_register("ForEach", setup_func, free_func, run_func_foreach);
+
+    actions_set_can_stop("Stop", TRUE);
 }
 
 static inline void set_bool(xmlNodePtr node,
@@ -90,6 +115,60 @@ static inline void set_bool(xmlNodePtr node,
     }
 }
 
+static void setup_typed_match(TypedMatch *tm, xmlNodePtr n)
+{
+    gchar *s;
+    if ((s = obt_xml_node_string(n))) {
+        gchar *type = NULL;
+        if (!obt_xml_attr_string(n, "type", &type) ||
+            !g_ascii_strcasecmp(type, "pattern"))
+        {
+            tm->type = MATCH_TYPE_PATTERN;
+            tm->m.pattern = g_pattern_spec_new(s);
+        } else if (type && !g_ascii_strcasecmp(type, "regex")) {
+            tm->type = MATCH_TYPE_REGEX;
+            tm->m.regex = g_regex_new(s, 0, 0, NULL);
+        } else if (type && !g_ascii_strcasecmp(type, "exact")) {
+            tm->type = MATCH_TYPE_EXACT;
+            tm->m.exact = g_strdup(s);
+        }
+        g_free(s);
+        g_free(type);
+    }
+}
+
+static void free_typed_match(TypedMatch *tm)
+{
+    switch (tm->type) {
+    case MATCH_TYPE_PATTERN:
+        g_pattern_spec_free(tm->m.pattern);
+        break;
+    case MATCH_TYPE_REGEX:
+        g_regex_unref(tm->m.regex);
+        break;
+    case MATCH_TYPE_EXACT:
+        g_free(tm->m.exact);
+        break;
+    case MATCH_TYPE_NONE:
+        break;
+    }
+}
+
+static gboolean check_typed_match(TypedMatch *tm, const gchar *s)
+{
+    switch (tm->type) {
+    case MATCH_TYPE_PATTERN:
+        return g_pattern_match_string(tm->m.pattern, s);
+    case MATCH_TYPE_REGEX:
+        return g_regex_match(tm->m.regex, s, 0, NULL);
+    case MATCH_TYPE_EXACT:
+        return !strcmp(tm->m.exact, s);
+    case MATCH_TYPE_NONE:
+        return TRUE;
+    }
+    g_assert_not_reached();
+}
+
 static void setup_query(Options* o, xmlNodePtr node, QueryTarget target) {
     Query *q = g_slice_new0(Query);
     g_array_append_val(o->queries, q);
@@ -123,19 +202,19 @@ static void setup_query(Options* o, xmlNodePtr node, QueryTarget target) {
         q->screendesktop_number = obt_xml_node_int(n);
     }
     if ((n = obt_xml_find_node(node, "title"))) {
-        gchar *s, *type = NULL;
-        if ((s = obt_xml_node_string(n))) {
-            if (!obt_xml_attr_string(n, "type", &type) ||
-                !g_ascii_strcasecmp(type, "pattern"))
-            {
-                q->matchtitle = g_pattern_spec_new(s);
-            } else if (type && !g_ascii_strcasecmp(type, "regex")) {
-                q->regextitle = g_regex_new(s, 0, 0, NULL);
-            } else if (type && !g_ascii_strcasecmp(type, "exact")) {
-                q->exacttitle = g_strdup(s);
-            }
-            g_free(s);
-        }
+        setup_typed_match(&q->title, n);
+    }
+    if ((n = obt_xml_find_node(node, "class"))) {
+        setup_typed_match(&q->class, n);
+    }
+    if ((n = obt_xml_find_node(node, "name"))) {
+        setup_typed_match(&q->name, n);
+    }
+    if ((n = obt_xml_find_node(node, "role"))) {
+        setup_typed_match(&q->role, n);
+    }
+    if ((n = obt_xml_find_node(node, "type"))) {
+        setup_typed_match(&q->type, n);
     }
     if ((n = obt_xml_find_node(node, "monitor"))) {
         q->client_monitor = obt_xml_node_int(n);
@@ -204,12 +283,11 @@ static void free_func(gpointer options)
     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);
+        free_typed_match(&q->title);
+        free_typed_match(&q->class);
+        free_typed_match(&q->name);
+        free_typed_match(&q->role);
+        free_typed_match(&q->type);
 
         g_slice_free(Query, q);
     }
@@ -228,7 +306,7 @@ static void free_func(gpointer options)
 }
 
 /* Always return FALSE because its not interactive */
-static gboolean run_func(ObActionsData *data, gpointer options)
+static gboolean run_func_if(ObActionsData *data, gpointer options)
 {
     Options *o = options;
     ObClient *action_target = data->client;
@@ -249,7 +327,10 @@ static gboolean run_func(ObActionsData *data, gpointer options)
         }
 
         /* If there's no client to query, then false. */
-        is_true &= query_target != NULL;
+        if (!query_target) {
+            is_true = FALSE;
+            break;
+        }
 
         if (q->shaded_on)
             is_true &= query_target->shaded;
@@ -321,18 +402,12 @@ static gboolean run_func(ObActionsData *data, gpointer options)
         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);
+        is_true &= check_typed_match(&q->title, query_target->original_title);
+        is_true &= check_typed_match(&q->class, query_target->class);
+        is_true &= check_typed_match(&q->name, query_target->name);
+        is_true &= check_typed_match(&q->role, query_target->role);
+        is_true &= check_typed_match(&q->type,
+                                     client_type_to_string(query_target));
 
         if (q->client_monitor)
             is_true &= client_monitor(query_target) == q->client_monitor - 1;
@@ -351,3 +426,34 @@ static gboolean run_func(ObActionsData *data, gpointer options)
 
     return FALSE;
 }
+
+static gboolean run_func_foreach(ObActionsData *data, gpointer options)
+{
+    GList *it;
+    Options *o = options;
+
+    o->stop = FALSE;
+
+    for (it = client_list; it; it = g_list_next(it)) {
+        data->client = it->data;
+        run_func_if(data, options);
+        if (o->stop) {
+            break;
+        }
+    }
+
+    return FALSE;
+}
+
+static gboolean run_func_stop(ObActionsData *data, gpointer options)
+{
+    Options *o = options;
+
+    /* This stops the loop above so we don't invoke actions on any more
+       clients */
+    o->stop = TRUE;
+
+    /* TRUE causes actions_run_acts to not run further actions on the current
+       client */
+    return TRUE;
+}