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