Add ForEach action which is like If but runs on all clients
[dana/openbox.git] / openbox / actions / if.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    if.c for the Openbox window manager
4    Copyright (c) 2007        Mikael Magnusson
5    Copyright (c) 2007        Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "openbox/actions.h"
21 #include "openbox/misc.h"
22 #include "openbox/client.h"
23 #include "openbox/frame.h"
24 #include "openbox/screen.h"
25 #include "openbox/focus.h"
26 #include <glib.h>
27
28 typedef enum {
29     QUERY_TARGET_IS_ACTION_TARGET,
30     QUERY_TARGET_IS_FOCUS_TARGET,
31 } QueryTarget;
32
33 typedef struct {
34     QueryTarget target;
35     gboolean shaded_on;
36     gboolean shaded_off;
37     gboolean maxvert_on;
38     gboolean maxvert_off;
39     gboolean maxhorz_on;
40     gboolean maxhorz_off;
41     gboolean maxfull_on;
42     gboolean maxfull_off;
43     gboolean iconic_on;
44     gboolean iconic_off;
45     gboolean focused;
46     gboolean unfocused;
47     gboolean urgent_on;
48     gboolean urgent_off;
49     gboolean decor_off;
50     gboolean decor_on;
51     gboolean omnipresent_on;
52     gboolean omnipresent_off;
53     gboolean desktop_current;
54     gboolean desktop_other;
55     guint    desktop_number;
56     guint    screendesktop_number;
57     guint    client_monitor;
58     GPatternSpec *matchtitle;
59     GRegex *regextitle;
60     gchar  *exacttitle;
61 } Query;
62
63 typedef struct {
64     GArray* queries;
65     GSList *thenacts;
66     GSList *elseacts;
67     gboolean stop;
68 } Options;
69
70 static gpointer setup_func(xmlNodePtr node);
71 static void     free_func(gpointer options);
72 static gboolean run_func_if(ObActionsData *data, gpointer options);
73 static gboolean run_func_stop(ObActionsData *data, gpointer options);
74 static gboolean run_func_foreach(ObActionsData *data, gpointer options);
75
76 void action_if_startup(void)
77 {
78     actions_register("If", setup_func, free_func, run_func_if);
79     actions_register("Stop", NULL, NULL, run_func_stop);
80     actions_register("ForEach", setup_func, free_func, run_func_foreach);
81
82     actions_set_can_stop("Stop", TRUE);
83 }
84
85 static inline void set_bool(xmlNodePtr node,
86                             const char *name,
87                             gboolean *on,
88                             gboolean *off)
89 {
90     xmlNodePtr n;
91
92     if ((n = obt_xml_find_node(node, name))) {
93         if (obt_xml_node_bool(n))
94             *on = TRUE;
95         else
96             *off = TRUE;
97     }
98 }
99
100 static void setup_query(Options* o, xmlNodePtr node, QueryTarget target) {
101     Query *q = g_slice_new0(Query);
102     g_array_append_val(o->queries, q);
103
104     q->target = target;
105
106     set_bool(node, "shaded", &q->shaded_on, &q->shaded_off);
107     set_bool(node, "maximized", &q->maxfull_on, &q->maxfull_off);
108     set_bool(node, "maximizedhorizontal", &q->maxhorz_on, &q->maxhorz_off);
109     set_bool(node, "maximizedvertical", &q->maxvert_on, &q->maxvert_off);
110     set_bool(node, "iconified", &q->iconic_on, &q->iconic_off);
111     set_bool(node, "focused", &q->focused, &q->unfocused);
112     set_bool(node, "urgent", &q->urgent_on, &q->urgent_off);
113     set_bool(node, "undecorated", &q->decor_off, &q->decor_on);
114     set_bool(node, "omnipresent", &q->omnipresent_on, &q->omnipresent_off);
115
116     xmlNodePtr n;
117     if ((n = obt_xml_find_node(node, "desktop"))) {
118         gchar *s;
119         if ((s = obt_xml_node_string(n))) {
120             if (!g_ascii_strcasecmp(s, "current"))
121                 q->desktop_current = TRUE;
122             if (!g_ascii_strcasecmp(s, "other"))
123                 q->desktop_other = TRUE;
124             else
125                 q->desktop_number = atoi(s);
126             g_free(s);
127         }
128     }
129     if ((n = obt_xml_find_node(node, "activedesktop"))) {
130         q->screendesktop_number = obt_xml_node_int(n);
131     }
132     if ((n = obt_xml_find_node(node, "title"))) {
133         gchar *s, *type = NULL;
134         if ((s = obt_xml_node_string(n))) {
135             if (!obt_xml_attr_string(n, "type", &type) ||
136                 !g_ascii_strcasecmp(type, "pattern"))
137             {
138                 q->matchtitle = g_pattern_spec_new(s);
139             } else if (type && !g_ascii_strcasecmp(type, "regex")) {
140                 q->regextitle = g_regex_new(s, 0, 0, NULL);
141             } else if (type && !g_ascii_strcasecmp(type, "exact")) {
142                 q->exacttitle = g_strdup(s);
143             }
144             g_free(s);
145         }
146     }
147     if ((n = obt_xml_find_node(node, "monitor"))) {
148         q->client_monitor = obt_xml_node_int(n);
149     }
150 }
151
152 static gpointer setup_func(xmlNodePtr node)
153 {
154     Options *o = g_slice_new0(Options);
155
156     gboolean zero_terminated = FALSE;
157     gboolean clear_to_zero_on_alloc = FALSE;
158     o->queries = g_array_new(zero_terminated,
159                              clear_to_zero_on_alloc,
160                              sizeof(Query*));
161
162     xmlNodePtr n;
163     if ((n = obt_xml_find_node(node, "then"))) {
164         xmlNodePtr m;
165
166         m = obt_xml_find_node(n->children, "action");
167         while (m) {
168             ObActionsAct *action = actions_parse(m);
169             if (action) o->thenacts = g_slist_append(o->thenacts, action);
170             m = obt_xml_find_node(m->next, "action");
171         }
172     }
173     if ((n = obt_xml_find_node(node, "else"))) {
174         xmlNodePtr m;
175
176         m = obt_xml_find_node(n->children, "action");
177         while (m) {
178             ObActionsAct *action = actions_parse(m);
179             if (action) o->elseacts = g_slist_append(o->elseacts, action);
180             m = obt_xml_find_node(m->next, "action");
181         }
182     }
183
184     xmlNodePtr query_node = obt_xml_find_node(node, "query");
185     if (!query_node) {
186         /* The default query if none is specified. It uses the conditions
187            found in the action's node. */
188         setup_query(o,
189                     node,
190                     QUERY_TARGET_IS_ACTION_TARGET);
191     } else {
192         while (query_node) {
193             QueryTarget query_target = QUERY_TARGET_IS_ACTION_TARGET;
194             if (obt_xml_attr_contains(query_node, "target", "focus"))
195                 query_target = QUERY_TARGET_IS_FOCUS_TARGET;
196
197             setup_query(o, query_node->children, query_target);
198
199             query_node = obt_xml_find_node(query_node->next, "query");
200         }
201     }
202
203     return o;
204 }
205
206 static void free_func(gpointer options)
207 {
208     Options *o = options;
209
210     guint i;
211     for (i = 0; i < o->queries->len; ++i) {
212         Query *q = g_array_index(o->queries, Query*, i);
213
214         if (q->matchtitle)
215             g_pattern_spec_free(q->matchtitle);
216         if (q->regextitle)
217             g_regex_unref(q->regextitle);
218         if (q->exacttitle)
219             g_free(q->exacttitle);
220
221         g_slice_free(Query, q);
222     }
223
224     while (o->thenacts) {
225         actions_act_unref(o->thenacts->data);
226         o->thenacts = g_slist_delete_link(o->thenacts, o->thenacts);
227     }
228     while (o->elseacts) {
229         actions_act_unref(o->elseacts->data);
230         o->elseacts = g_slist_delete_link(o->elseacts, o->elseacts);
231     }
232
233     g_array_unref(o->queries);
234     g_slice_free(Options, o);
235 }
236
237 /* Always return FALSE because its not interactive */
238 static gboolean run_func_if(ObActionsData *data, gpointer options)
239 {
240     Options *o = options;
241     ObClient *action_target = data->client;
242     gboolean is_true = TRUE;
243
244     guint i;
245     for (i = 0; i < o->queries->len; ++i) {
246         Query *q = g_array_index(o->queries, Query*, i);
247         ObClient *query_target = NULL;
248
249         switch (q->target) {
250         case QUERY_TARGET_IS_ACTION_TARGET:
251             query_target = data->client;
252             break;
253         case QUERY_TARGET_IS_FOCUS_TARGET:
254             query_target = focus_client;
255             break;
256         }
257
258         /* If there's no client to query, then false. */
259         is_true &= query_target != NULL;
260
261         if (q->shaded_on)
262             is_true &= query_target->shaded;
263         if (q->shaded_off)
264             is_true &= !query_target->shaded;
265
266         if (q->iconic_on)
267             is_true &= query_target->iconic;
268         if (q->iconic_off)
269             is_true &= !query_target->iconic;
270
271         if (q->maxhorz_on)
272             is_true &= query_target->max_horz;
273         if (q->maxhorz_off)
274             is_true &= !query_target->max_horz;
275
276         if (q->maxvert_on)
277             is_true &= query_target->max_vert;
278         if (q->maxvert_off)
279             is_true &= !query_target->max_vert;
280
281         gboolean is_max_full =
282             query_target->max_vert && query_target->max_horz;
283         if (q->maxfull_on)
284             is_true &= is_max_full;
285         if (q->maxfull_off)
286             is_true &= !is_max_full;
287
288         if (q->focused)
289             is_true &= query_target == focus_client;
290         if (q->unfocused)
291             is_true &= query_target != focus_client;
292
293         gboolean is_urgent =
294             query_target->urgent || query_target->demands_attention;
295         if (q->urgent_on)
296             is_true &= is_urgent;
297         if (q->urgent_off)
298             is_true &= !is_urgent;
299
300         gboolean has_visible_title_bar =
301             !query_target->undecorated &&
302             (query_target->decorations & OB_FRAME_DECOR_TITLEBAR);
303         if (q->decor_on)
304             is_true &= has_visible_title_bar;
305         if (q->decor_off)
306             is_true &= !has_visible_title_bar;
307
308         if (q->omnipresent_on)
309             is_true &= query_target->desktop == DESKTOP_ALL;
310         if (q->omnipresent_off)
311             is_true &= query_target->desktop != DESKTOP_ALL;
312
313         gboolean is_on_current_desktop =
314             query_target->desktop == screen_desktop ||
315             query_target->desktop == DESKTOP_ALL;
316         if (q->desktop_current)
317             is_true &= is_on_current_desktop;
318         if (q->desktop_other)
319             is_true &= !is_on_current_desktop;
320
321         if (q->desktop_number) {
322             gboolean is_on_desktop =
323                 query_target->desktop == q->desktop_number - 1 ||
324                 query_target->desktop == DESKTOP_ALL;
325             is_true &= is_on_desktop;
326         }
327
328         if (q->screendesktop_number)
329             is_true &= screen_desktop == q->screendesktop_number - 1;
330
331         if (q->matchtitle) {
332             is_true &= g_pattern_match_string(q->matchtitle,
333                                               query_target->original_title);
334         }
335         if (q->regextitle) {
336             is_true &= g_regex_match(q->regextitle,
337                                      query_target->original_title,
338                                      0,
339                                      NULL);
340         }
341         if (q->exacttitle)
342             is_true &= !strcmp(q->exacttitle, query_target->original_title);
343
344         if (q->client_monitor)
345             is_true &= client_monitor(query_target) == q->client_monitor - 1;
346
347     }
348
349     GSList *acts;
350     if (is_true)
351         acts = o->thenacts;
352     else
353         acts = o->elseacts;
354
355     actions_run_acts(acts, data->uact, data->state,
356                      data->x, data->y, data->button,
357                      data->context, action_target);
358
359     return FALSE;
360 }
361
362 static gboolean run_func_foreach(ObActionsData *data, gpointer options)
363 {
364     GList *it;
365     Options *o = options;
366
367     o->stop = FALSE;
368
369     for (it = client_list; it; it = g_list_next(it)) {
370         data->client = it->data;
371         run_func_if(data, options);
372         if (o->stop) {
373             break;
374         }
375     }
376
377     return FALSE;
378 }
379
380 static gboolean run_func_stop(ObActionsData *data, gpointer options)
381 {
382     Options *o = options;
383
384     /* This stops the loop above so we don't invoke actions on any more
385        clients */
386     o->stop = TRUE;
387
388     /* TRUE causes actions_run_acts to not run further actions on the current
389        client */
390     return TRUE;
391 }