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