67d067e2a1332e186ca5dd3dfc099f9f4391160b
[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 enum {
34     MATCH_TYPE_NONE = 0,
35     MATCH_TYPE_PATTERN,
36     MATCH_TYPE_REGEX,
37     MATCH_TYPE_EXACT,
38 } MatchType;
39
40 typedef struct {
41     MatchType type;
42     union m {
43         GPatternSpec *pattern;
44         GRegex *regex;
45         gchar *exact;
46     } m;
47 } TypedMatch;
48
49 typedef struct {
50     QueryTarget target;
51     gboolean shaded_on;
52     gboolean shaded_off;
53     gboolean maxvert_on;
54     gboolean maxvert_off;
55     gboolean maxhorz_on;
56     gboolean maxhorz_off;
57     gboolean maxfull_on;
58     gboolean maxfull_off;
59     gboolean iconic_on;
60     gboolean iconic_off;
61     gboolean focused;
62     gboolean unfocused;
63     gboolean urgent_on;
64     gboolean urgent_off;
65     gboolean decor_off;
66     gboolean decor_on;
67     gboolean omnipresent_on;
68     gboolean omnipresent_off;
69     gboolean desktop_current;
70     gboolean desktop_other;
71     guint    desktop_number;
72     guint    screendesktop_number;
73     guint    client_monitor;
74     TypedMatch title;
75     TypedMatch class;
76     TypedMatch name;
77     TypedMatch role;
78     TypedMatch type;
79 } Query;
80
81 typedef struct {
82     GArray* queries;
83     GSList *thenacts;
84     GSList *elseacts;
85     gboolean stop;
86 } Options;
87
88 static gpointer setup_func(xmlNodePtr node);
89 static void     free_func(gpointer options);
90 static gboolean run_func_if(ObActionsData *data, gpointer options);
91 static gboolean run_func_stop(ObActionsData *data, gpointer options);
92 static gboolean run_func_foreach(ObActionsData *data, gpointer options);
93
94 void action_if_startup(void)
95 {
96     actions_register("If", setup_func, free_func, run_func_if);
97     actions_register("Stop", NULL, NULL, run_func_stop);
98     actions_register("ForEach", setup_func, free_func, run_func_foreach);
99
100     actions_set_can_stop("Stop", TRUE);
101 }
102
103 static inline void set_bool(xmlNodePtr node,
104                             const char *name,
105                             gboolean *on,
106                             gboolean *off)
107 {
108     xmlNodePtr n;
109
110     if ((n = obt_xml_find_node(node, name))) {
111         if (obt_xml_node_bool(n))
112             *on = TRUE;
113         else
114             *off = TRUE;
115     }
116 }
117
118 static void setup_typed_match(TypedMatch *tm, xmlNodePtr n)
119 {
120     gchar *s;
121     if ((s = obt_xml_node_string(n))) {
122         gchar *type = NULL;
123         if (!obt_xml_attr_string(n, "type", &type) ||
124             !g_ascii_strcasecmp(type, "pattern"))
125         {
126             tm->type = MATCH_TYPE_PATTERN;
127             tm->m.pattern = g_pattern_spec_new(s);
128         } else if (type && !g_ascii_strcasecmp(type, "regex")) {
129             tm->type = MATCH_TYPE_REGEX;
130             tm->m.regex = g_regex_new(s, 0, 0, NULL);
131         } else if (type && !g_ascii_strcasecmp(type, "exact")) {
132             tm->type = MATCH_TYPE_EXACT;
133             tm->m.exact = g_strdup(s);
134         }
135         g_free(s);
136         g_free(type);
137     }
138 }
139
140 static void free_typed_match(TypedMatch *tm)
141 {
142     switch (tm->type) {
143     case MATCH_TYPE_PATTERN:
144         g_pattern_spec_free(tm->m.pattern);
145         break;
146     case MATCH_TYPE_REGEX:
147         g_regex_unref(tm->m.regex);
148         break;
149     case MATCH_TYPE_EXACT:
150         g_free(tm->m.exact);
151         break;
152     case MATCH_TYPE_NONE:
153         break;
154     }
155 }
156
157 static gboolean check_typed_match(TypedMatch *tm, const gchar *s)
158 {
159     switch (tm->type) {
160     case MATCH_TYPE_PATTERN:
161         return g_pattern_match_string(tm->m.pattern, s);
162     case MATCH_TYPE_REGEX:
163         return g_regex_match(tm->m.regex, s, 0, NULL);
164     case MATCH_TYPE_EXACT:
165         return !strcmp(tm->m.exact, s);
166     case MATCH_TYPE_NONE:
167         return TRUE;
168     }
169     g_assert_not_reached();
170 }
171
172 static void setup_query(Options* o, xmlNodePtr node, QueryTarget target) {
173     Query *q = g_slice_new0(Query);
174     g_array_append_val(o->queries, q);
175
176     q->target = target;
177
178     set_bool(node, "shaded", &q->shaded_on, &q->shaded_off);
179     set_bool(node, "maximized", &q->maxfull_on, &q->maxfull_off);
180     set_bool(node, "maximizedhorizontal", &q->maxhorz_on, &q->maxhorz_off);
181     set_bool(node, "maximizedvertical", &q->maxvert_on, &q->maxvert_off);
182     set_bool(node, "iconified", &q->iconic_on, &q->iconic_off);
183     set_bool(node, "focused", &q->focused, &q->unfocused);
184     set_bool(node, "urgent", &q->urgent_on, &q->urgent_off);
185     set_bool(node, "undecorated", &q->decor_off, &q->decor_on);
186     set_bool(node, "omnipresent", &q->omnipresent_on, &q->omnipresent_off);
187
188     xmlNodePtr n;
189     if ((n = obt_xml_find_node(node, "desktop"))) {
190         gchar *s;
191         if ((s = obt_xml_node_string(n))) {
192             if (!g_ascii_strcasecmp(s, "current"))
193                 q->desktop_current = TRUE;
194             if (!g_ascii_strcasecmp(s, "other"))
195                 q->desktop_other = TRUE;
196             else
197                 q->desktop_number = atoi(s);
198             g_free(s);
199         }
200     }
201     if ((n = obt_xml_find_node(node, "activedesktop"))) {
202         q->screendesktop_number = obt_xml_node_int(n);
203     }
204     if ((n = obt_xml_find_node(node, "title"))) {
205         setup_typed_match(&q->title, n);
206     }
207     if ((n = obt_xml_find_node(node, "class"))) {
208         setup_typed_match(&q->class, n);
209     }
210     if ((n = obt_xml_find_node(node, "name"))) {
211         setup_typed_match(&q->name, n);
212     }
213     if ((n = obt_xml_find_node(node, "role"))) {
214         setup_typed_match(&q->role, n);
215     }
216     if ((n = obt_xml_find_node(node, "type"))) {
217         setup_typed_match(&q->type, n);
218     }
219     if ((n = obt_xml_find_node(node, "monitor"))) {
220         q->client_monitor = obt_xml_node_int(n);
221     }
222 }
223
224 static gpointer setup_func(xmlNodePtr node)
225 {
226     Options *o = g_slice_new0(Options);
227
228     gboolean zero_terminated = FALSE;
229     gboolean clear_to_zero_on_alloc = FALSE;
230     o->queries = g_array_new(zero_terminated,
231                              clear_to_zero_on_alloc,
232                              sizeof(Query*));
233
234     xmlNodePtr n;
235     if ((n = obt_xml_find_node(node, "then"))) {
236         xmlNodePtr m;
237
238         m = obt_xml_find_node(n->children, "action");
239         while (m) {
240             ObActionsAct *action = actions_parse(m);
241             if (action) o->thenacts = g_slist_append(o->thenacts, action);
242             m = obt_xml_find_node(m->next, "action");
243         }
244     }
245     if ((n = obt_xml_find_node(node, "else"))) {
246         xmlNodePtr m;
247
248         m = obt_xml_find_node(n->children, "action");
249         while (m) {
250             ObActionsAct *action = actions_parse(m);
251             if (action) o->elseacts = g_slist_append(o->elseacts, action);
252             m = obt_xml_find_node(m->next, "action");
253         }
254     }
255
256     xmlNodePtr query_node = obt_xml_find_node(node, "query");
257     if (!query_node) {
258         /* The default query if none is specified. It uses the conditions
259            found in the action's node. */
260         setup_query(o,
261                     node,
262                     QUERY_TARGET_IS_ACTION_TARGET);
263     } else {
264         while (query_node) {
265             QueryTarget query_target = QUERY_TARGET_IS_ACTION_TARGET;
266             if (obt_xml_attr_contains(query_node, "target", "focus"))
267                 query_target = QUERY_TARGET_IS_FOCUS_TARGET;
268
269             setup_query(o, query_node->children, query_target);
270
271             query_node = obt_xml_find_node(query_node->next, "query");
272         }
273     }
274
275     return o;
276 }
277
278 static void free_func(gpointer options)
279 {
280     Options *o = options;
281
282     guint i;
283     for (i = 0; i < o->queries->len; ++i) {
284         Query *q = g_array_index(o->queries, Query*, i);
285
286         free_typed_match(&q->title);
287         free_typed_match(&q->class);
288         free_typed_match(&q->name);
289         free_typed_match(&q->role);
290         free_typed_match(&q->type);
291
292         g_slice_free(Query, q);
293     }
294
295     while (o->thenacts) {
296         actions_act_unref(o->thenacts->data);
297         o->thenacts = g_slist_delete_link(o->thenacts, o->thenacts);
298     }
299     while (o->elseacts) {
300         actions_act_unref(o->elseacts->data);
301         o->elseacts = g_slist_delete_link(o->elseacts, o->elseacts);
302     }
303
304     g_array_unref(o->queries);
305     g_slice_free(Options, o);
306 }
307
308 /* Always return FALSE because its not interactive */
309 static gboolean run_func_if(ObActionsData *data, gpointer options)
310 {
311     Options *o = options;
312     ObClient *action_target = data->client;
313     gboolean is_true = TRUE;
314
315     guint i;
316     for (i = 0; i < o->queries->len; ++i) {
317         Query *q = g_array_index(o->queries, Query*, i);
318         ObClient *query_target = NULL;
319
320         switch (q->target) {
321         case QUERY_TARGET_IS_ACTION_TARGET:
322             query_target = data->client;
323             break;
324         case QUERY_TARGET_IS_FOCUS_TARGET:
325             query_target = focus_client;
326             break;
327         }
328
329         /* If there's no client to query, then false. */
330         if (!query_target) {
331             is_true = FALSE;
332             break;
333         }
334
335         if (q->shaded_on)
336             is_true &= query_target->shaded;
337         if (q->shaded_off)
338             is_true &= !query_target->shaded;
339
340         if (q->iconic_on)
341             is_true &= query_target->iconic;
342         if (q->iconic_off)
343             is_true &= !query_target->iconic;
344
345         if (q->maxhorz_on)
346             is_true &= query_target->max_horz;
347         if (q->maxhorz_off)
348             is_true &= !query_target->max_horz;
349
350         if (q->maxvert_on)
351             is_true &= query_target->max_vert;
352         if (q->maxvert_off)
353             is_true &= !query_target->max_vert;
354
355         gboolean is_max_full =
356             query_target->max_vert && query_target->max_horz;
357         if (q->maxfull_on)
358             is_true &= is_max_full;
359         if (q->maxfull_off)
360             is_true &= !is_max_full;
361
362         if (q->focused)
363             is_true &= query_target == focus_client;
364         if (q->unfocused)
365             is_true &= query_target != focus_client;
366
367         gboolean is_urgent =
368             query_target->urgent || query_target->demands_attention;
369         if (q->urgent_on)
370             is_true &= is_urgent;
371         if (q->urgent_off)
372             is_true &= !is_urgent;
373
374         gboolean has_visible_title_bar =
375             !query_target->undecorated &&
376             (query_target->decorations & OB_FRAME_DECOR_TITLEBAR);
377         if (q->decor_on)
378             is_true &= has_visible_title_bar;
379         if (q->decor_off)
380             is_true &= !has_visible_title_bar;
381
382         if (q->omnipresent_on)
383             is_true &= query_target->desktop == DESKTOP_ALL;
384         if (q->omnipresent_off)
385             is_true &= query_target->desktop != DESKTOP_ALL;
386
387         gboolean is_on_current_desktop =
388             query_target->desktop == screen_desktop ||
389             query_target->desktop == DESKTOP_ALL;
390         if (q->desktop_current)
391             is_true &= is_on_current_desktop;
392         if (q->desktop_other)
393             is_true &= !is_on_current_desktop;
394
395         if (q->desktop_number) {
396             gboolean is_on_desktop =
397                 query_target->desktop == q->desktop_number - 1 ||
398                 query_target->desktop == DESKTOP_ALL;
399             is_true &= is_on_desktop;
400         }
401
402         if (q->screendesktop_number)
403             is_true &= screen_desktop == q->screendesktop_number - 1;
404
405         is_true &= check_typed_match(&q->title, query_target->original_title);
406         is_true &= check_typed_match(&q->class, query_target->class);
407         is_true &= check_typed_match(&q->name, query_target->name);
408         is_true &= check_typed_match(&q->role, query_target->role);
409         is_true &= check_typed_match(&q->type,
410                                      client_type_to_string(query_target));
411
412         if (q->client_monitor)
413             is_true &= client_monitor(query_target) == q->client_monitor - 1;
414
415     }
416
417     GSList *acts;
418     if (is_true)
419         acts = o->thenacts;
420     else
421         acts = o->elseacts;
422
423     actions_run_acts(acts, data->uact, data->state,
424                      data->x, data->y, data->button,
425                      data->context, action_target);
426
427     return FALSE;
428 }
429
430 static gboolean run_func_foreach(ObActionsData *data, gpointer options)
431 {
432     GList *it;
433     Options *o = options;
434
435     o->stop = FALSE;
436
437     for (it = client_list; it; it = g_list_next(it)) {
438         data->client = it->data;
439         run_func_if(data, options);
440         if (o->stop) {
441             break;
442         }
443     }
444
445     return FALSE;
446 }
447
448 static gboolean run_func_stop(ObActionsData *data, gpointer options)
449 {
450     Options *o = options;
451
452     /* This stops the loop above so we don't invoke actions on any more
453        clients */
454     o->stop = TRUE;
455
456     /* TRUE causes actions_run_acts to not run further actions on the current
457        client */
458     return TRUE;
459 }