Allow sending windows on other desktops to the current desktop.
[dana/openbox.git] / openbox / actions / desktop.c
1 #include "openbox/actions.h"
2 #include "openbox/screen.h"
3 #include "openbox/client.h"
4 #include "openbox/openbox.h"
5 #include "obt/keyboard.h"
6
7 typedef enum {
8     LAST,
9     CURRENT,
10     RELATIVE,
11     ABSOLUTE
12 } SwitchType;
13
14 typedef struct {
15     SwitchType type;
16     union {
17         struct {
18             guint desktop;
19         } abs;
20
21         struct {
22             gboolean linear;
23             gboolean wrap;
24             ObDirection dir;
25         } rel;
26     } u;
27     gboolean send;
28     gboolean follow;
29     gboolean interactive;
30 } Options;
31
32 static gpointer setup_go_func(xmlNodePtr node,
33                               ObActionsIPreFunc *pre,
34                               ObActionsIInputFunc *input,
35                               ObActionsICancelFunc *cancel,
36                               ObActionsIPostFunc *post);
37 static gpointer setup_send_func(xmlNodePtr node,
38                                 ObActionsIPreFunc *pre,
39                                 ObActionsIInputFunc *input,
40                                 ObActionsICancelFunc *cancel,
41                                 ObActionsIPostFunc *post);
42 static void free_func(gpointer o);
43 static gboolean run_func(ObActionsData *data, gpointer options);
44
45 static gboolean i_pre_func(guint state, gpointer options);
46 static gboolean i_input_func(guint initial_state,
47                              XEvent *e,
48                              ObtIC *ic,
49                              gpointer options,
50                              gboolean *used);
51 static void i_post_func(gpointer options);
52
53 /* 3.4-compatibility */
54 static gpointer setup_go_last_func(xmlNodePtr node);
55 static gpointer setup_send_last_func(xmlNodePtr node);
56 static gpointer setup_go_abs_func(xmlNodePtr node);
57 static gpointer setup_send_abs_func(xmlNodePtr node);
58 static gpointer setup_go_next_func(xmlNodePtr node,
59                                    ObActionsIPreFunc *pre,
60                                    ObActionsIInputFunc *input,
61                                    ObActionsICancelFunc *cancel,
62                                    ObActionsIPostFunc *post);
63 static gpointer setup_send_next_func(xmlNodePtr node,
64                                      ObActionsIPreFunc *pre,
65                                      ObActionsIInputFunc *input,
66                                      ObActionsICancelFunc *cancel,
67                                      ObActionsIPostFunc *post);
68 static gpointer setup_go_prev_func(xmlNodePtr node,
69                                    ObActionsIPreFunc *pre,
70                                    ObActionsIInputFunc *input,
71                                    ObActionsICancelFunc *cancel,
72                                    ObActionsIPostFunc *post);
73 static gpointer setup_send_prev_func(xmlNodePtr node,
74                                      ObActionsIPreFunc *pre,
75                                      ObActionsIInputFunc *input,
76                                      ObActionsICancelFunc *cancel,
77                                      ObActionsIPostFunc *post);
78 static gpointer setup_go_left_func(xmlNodePtr node,
79                                    ObActionsIPreFunc *pre,
80                                    ObActionsIInputFunc *input,
81                                    ObActionsICancelFunc *cancel,
82                                    ObActionsIPostFunc *post);
83 static gpointer setup_send_left_func(xmlNodePtr node,
84                                      ObActionsIPreFunc *pre,
85                                      ObActionsIInputFunc *input,
86                                      ObActionsICancelFunc *cancel,
87                                      ObActionsIPostFunc *post);
88 static gpointer setup_go_right_func(xmlNodePtr node,
89                                     ObActionsIPreFunc *pre,
90                                     ObActionsIInputFunc *input,
91                                     ObActionsICancelFunc *cancel,
92                                     ObActionsIPostFunc *post);
93 static gpointer setup_send_right_func(xmlNodePtr node,
94                                       ObActionsIPreFunc *pre,
95                                       ObActionsIInputFunc *input,
96                                       ObActionsICancelFunc *cancel,
97                                       ObActionsIPostFunc *post);
98 static gpointer setup_go_up_func(xmlNodePtr node,
99                                  ObActionsIPreFunc *pre,
100                                  ObActionsIInputFunc *input,
101                                  ObActionsICancelFunc *cancel,
102                                  ObActionsIPostFunc *post);
103 static gpointer setup_send_up_func(xmlNodePtr node,
104                                    ObActionsIPreFunc *pre,
105                                    ObActionsIInputFunc *input,
106                                    ObActionsICancelFunc *cancel,
107                                    ObActionsIPostFunc *post);
108 static gpointer setup_go_down_func(xmlNodePtr node,
109                                    ObActionsIPreFunc *pre,
110                                    ObActionsIInputFunc *input,
111                                    ObActionsICancelFunc *cancel,
112                                    ObActionsIPostFunc *post);
113 static gpointer setup_send_down_func(xmlNodePtr node,
114                                      ObActionsIPreFunc *pre,
115                                      ObActionsIInputFunc *input,
116                                      ObActionsICancelFunc *cancel,
117                                      ObActionsIPostFunc *post);
118
119 void action_desktop_startup(void)
120 {
121     actions_register_i("GoToDesktop", setup_go_func, free_func, run_func);
122     actions_register_i("SendToDesktop", setup_send_func, free_func, run_func);
123     /* 3.4-compatibility */
124     actions_register("DesktopLast", setup_go_last_func, free_func, run_func);
125     actions_register("SendToDesktopLast", setup_send_last_func,
126                      free_func, run_func);
127     actions_register("Desktop", setup_go_abs_func, free_func, run_func);
128     actions_register("SendToDesktop", setup_send_abs_func,
129                      free_func, run_func);
130     actions_register_i("DesktopNext", setup_go_next_func, free_func, run_func);
131     actions_register_i("SendToDesktopNext", setup_send_next_func,
132                        free_func, run_func);
133     actions_register_i("DesktopPrevious", setup_go_prev_func,
134                        free_func, run_func);
135     actions_register_i("SendToDesktopPrevious", setup_send_prev_func,
136                        free_func, run_func);
137     actions_register_i("DesktopLeft", setup_go_left_func, free_func, run_func);
138     actions_register_i("SendToDesktopLeft", setup_send_left_func,
139                        free_func, run_func);
140     actions_register_i("DesktopRight", setup_go_right_func,
141                        free_func, run_func);
142     actions_register_i("SendToDesktopRight", setup_send_right_func,
143                        free_func, run_func);
144     actions_register_i("DesktopUp", setup_go_up_func, free_func, run_func);
145     actions_register_i("SendToDesktopUp", setup_send_up_func,
146                        free_func, run_func);
147     actions_register_i("DesktopDown", setup_go_down_func, free_func, run_func);
148     actions_register_i("SendToDesktopDown", setup_send_down_func,
149                        free_func, run_func);
150 }
151
152 static gpointer setup_func(xmlNodePtr node,
153                            ObActionsIPreFunc *pre,
154                            ObActionsIInputFunc *input,
155                            ObActionsICancelFunc *cancel,
156                            ObActionsIPostFunc *post)
157 {
158     xmlNodePtr n;
159     Options *o;
160
161     o = g_slice_new0(Options);
162     /* don't go anywhere if there are no options given */
163     o->type = ABSOLUTE;
164     o->u.abs.desktop = screen_desktop;
165     /* wrap by default - it's handy! */
166     o->u.rel.wrap = TRUE;
167
168     if ((n = obt_xml_find_node(node, "to"))) {
169         gchar *s = obt_xml_node_string(n);
170         if (!g_ascii_strcasecmp(s, "last"))
171             o->type = LAST;
172         else if (!g_ascii_strcasecmp(s, "current"))
173             o->type = CURRENT;
174         else if (!g_ascii_strcasecmp(s, "next")) {
175             o->type = RELATIVE;
176             o->u.rel.linear = TRUE;
177             o->u.rel.dir = OB_DIRECTION_EAST;
178         }
179         else if (!g_ascii_strcasecmp(s, "previous")) {
180             o->type = RELATIVE;
181             o->u.rel.linear = TRUE;
182             o->u.rel.dir = OB_DIRECTION_WEST;
183         }
184         else if (!g_ascii_strcasecmp(s, "north") ||
185                  !g_ascii_strcasecmp(s, "up")) {
186             o->type = RELATIVE;
187             o->u.rel.dir = OB_DIRECTION_NORTH;
188         }
189         else if (!g_ascii_strcasecmp(s, "south") ||
190                  !g_ascii_strcasecmp(s, "down")) {
191             o->type = RELATIVE;
192             o->u.rel.dir = OB_DIRECTION_SOUTH;
193         }
194         else if (!g_ascii_strcasecmp(s, "west") ||
195                  !g_ascii_strcasecmp(s, "left")) {
196             o->type = RELATIVE;
197             o->u.rel.dir = OB_DIRECTION_WEST;
198         }
199         else if (!g_ascii_strcasecmp(s, "east") ||
200                  !g_ascii_strcasecmp(s, "right")) {
201             o->type = RELATIVE;
202             o->u.rel.dir = OB_DIRECTION_EAST;
203         }
204         else {
205             o->type = ABSOLUTE;
206             o->u.abs.desktop = atoi(s) - 1;
207         }
208         g_free(s);
209     }
210
211     if ((n = obt_xml_find_node(node, "wrap")))
212         o->u.rel.wrap = obt_xml_node_bool(n);
213
214     return o;
215 }
216
217
218 static gpointer setup_go_func(xmlNodePtr node,
219                               ObActionsIPreFunc *pre,
220                               ObActionsIInputFunc *input,
221                               ObActionsICancelFunc *cancel,
222                               ObActionsIPostFunc *post)
223 {
224     Options *o;
225
226     o = setup_func(node, pre, input, cancel, post);
227     if (o->type == RELATIVE) {
228         o->interactive = TRUE;
229         *pre = i_pre_func;
230         *input = i_input_func;
231         *post = i_post_func;
232     }
233
234     return o;
235 }
236
237 static gpointer setup_send_func(xmlNodePtr node,
238                                 ObActionsIPreFunc *pre,
239                                 ObActionsIInputFunc *input,
240                                 ObActionsICancelFunc *cancel,
241                                 ObActionsIPostFunc *post)
242 {
243     xmlNodePtr n;
244     Options *o;
245
246     o = setup_func(node, pre, input, cancel, post);
247     o->send = TRUE;
248     o->follow = TRUE;
249
250     if ((n = obt_xml_find_node(node, "follow")))
251         o->follow = obt_xml_node_bool(n);
252
253     if (o->type == RELATIVE && o->follow) {
254         o->interactive = TRUE;
255         *pre = i_pre_func;
256         *input = i_input_func;
257         *post = i_post_func;
258     }
259
260     return o;
261 }
262
263 static void free_func(gpointer o)
264 {
265     g_slice_free(Options, o);
266 }
267
268 /* Always return FALSE because its not interactive */
269 static gboolean run_func(ObActionsData *data, gpointer options)
270 {
271     Options *o = options;
272     guint d;
273
274     switch (o->type) {
275     case LAST:
276         d = screen_last_desktop;
277         break;
278     case CURRENT:
279         d = screen_desktop;
280         break;
281     case ABSOLUTE:
282         d = o->u.abs.desktop;
283         break;
284     case RELATIVE:
285         d = screen_find_desktop(screen_desktop,
286                                 o->u.rel.dir, o->u.rel.wrap, o->u.rel.linear);
287         break;
288     default:
289         g_assert_not_reached();
290     }
291
292     if (d < screen_num_desktops &&
293         (d != screen_desktop ||
294          (data->client && data->client->desktop != screen_desktop))) {
295         gboolean go = TRUE;
296
297         actions_client_move(data, TRUE);
298         if (o->send && data->client && client_normal(data->client)) {
299             client_set_desktop(data->client, d, o->follow, FALSE);
300             go = o->follow;
301         }
302
303         if (go) {
304             screen_set_desktop(d, TRUE);
305             if (data->client)
306                 client_bring_helper_windows(data->client);
307         }
308
309         actions_client_move(data, FALSE);
310     }
311
312     return o->interactive;
313 }
314
315 static gboolean i_input_func(guint initial_state,
316                              XEvent *e,
317                              ObtIC *ic,
318                              gpointer options,
319                              gboolean *used)
320 {
321     guint mods;
322
323     mods = obt_keyboard_only_modmasks(e->xkey.state);
324     if (e->type == KeyRelease) {
325         /* remove from the state the mask of the modifier key being
326            released, if it is a modifier key being released that is */
327         mods &= ~obt_keyboard_keyevent_to_modmask(e);
328     }
329
330     if (e->type == KeyPress) {
331         KeySym sym = obt_keyboard_keypress_to_keysym(e);
332
333         /* Escape cancels no matter what */
334         if (sym == XK_Escape)
335             return FALSE;
336
337         /* There were no modifiers and they pressed enter */
338         else if ((sym == XK_Return || sym == XK_KP_Enter) && !initial_state)
339             return FALSE;
340     }
341     /* They released the modifiers */
342     else if (e->type == KeyRelease && initial_state && !(mods & initial_state))
343     {
344         return FALSE;
345     }
346
347     return TRUE;
348 }
349
350 static gboolean i_pre_func(guint initial_state, gpointer options)
351 {
352     if (!initial_state) {
353         Options *o = options;
354         o->interactive = FALSE;
355         return FALSE;
356     }
357     else {
358         screen_show_desktop_popup(screen_desktop, TRUE);
359         return TRUE;
360     }
361 }
362
363 static void i_post_func(gpointer options)
364 {
365     screen_hide_desktop_popup();
366 }
367
368 /* 3.4-compatilibity */
369 static gpointer setup_follow(xmlNodePtr node)
370 {
371     xmlNodePtr n;
372     Options *o = g_slice_new0(Options);
373     o->send = TRUE;
374     o->follow = TRUE;
375     if ((n = obt_xml_find_node(node, "follow")))
376         o->follow = obt_xml_node_bool(n);
377     return o;
378 }
379
380 static gpointer setup_go_last_func(xmlNodePtr node)
381 {
382     Options *o = g_slice_new0(Options);
383     o->type = LAST;
384     return o;
385 }
386
387 static gpointer setup_send_last_func(xmlNodePtr node)
388 {
389     Options *o = setup_follow(node);
390     o->type = LAST;
391     return o;
392 }
393
394 static gpointer setup_go_abs_func(xmlNodePtr node)
395 {
396     xmlNodePtr n;
397     Options *o = g_slice_new0(Options);
398     o->type = ABSOLUTE;
399     if ((n = obt_xml_find_node(node, "desktop")))
400         o->u.abs.desktop = obt_xml_node_int(n) - 1;
401     else
402         o->u.abs.desktop = screen_desktop;
403     return o;
404 }
405
406 static gpointer setup_send_abs_func(xmlNodePtr node)
407 {
408     xmlNodePtr n;
409     Options *o = setup_follow(node);
410     o->type = ABSOLUTE;
411     if ((n = obt_xml_find_node(node, "desktop")))
412         o->u.abs.desktop = obt_xml_node_int(n) - 1;
413     else
414         o->u.abs.desktop = screen_desktop;
415     return o;
416 }
417
418 static void setup_rel(Options *o, xmlNodePtr node, gboolean lin,
419                       ObDirection dir,
420                       ObActionsIPreFunc *pre,
421                       ObActionsIInputFunc *input,
422                       ObActionsIPostFunc *post)
423 {
424     xmlNodePtr n;
425
426     o->type = RELATIVE;
427     o->u.rel.linear = lin;
428     o->u.rel.dir = dir;
429     o->u.rel.wrap = TRUE;
430
431     if ((n = obt_xml_find_node(node, "wrap")))
432         o->u.rel.wrap = obt_xml_node_bool(n);
433
434     if (input) {
435         o->interactive = TRUE;
436         *pre = i_pre_func;
437         *input = i_input_func;
438         *post = i_post_func;
439     }
440 }
441
442 static gpointer setup_go_next_func(xmlNodePtr node,
443                                    ObActionsIPreFunc *pre,
444                                    ObActionsIInputFunc *input,
445                                    ObActionsICancelFunc *cancel,
446                                    ObActionsIPostFunc *post)
447 {
448     Options *o = g_slice_new0(Options);
449     setup_rel(o, node, TRUE, OB_DIRECTION_EAST, pre, input, post);
450     return o;
451 }
452
453 static gpointer setup_send_next_func(xmlNodePtr node,
454                                      ObActionsIPreFunc *pre,
455                                      ObActionsIInputFunc *input,
456                                      ObActionsICancelFunc *cancel,
457                                      ObActionsIPostFunc *post)
458 {
459     Options *o = setup_follow(node);
460     setup_rel(o, node, TRUE, OB_DIRECTION_EAST,
461               pre, (o->follow ? input : NULL), post);
462     return o;
463 }
464
465 static gpointer setup_go_prev_func(xmlNodePtr node,
466                                    ObActionsIPreFunc *pre,
467                                    ObActionsIInputFunc *input,
468                                    ObActionsICancelFunc *cancel,
469                                    ObActionsIPostFunc *post)
470 {
471     Options *o = g_slice_new0(Options);
472     setup_rel(o, node, TRUE, OB_DIRECTION_WEST, pre, input, post);
473     return o;
474 }
475
476 static gpointer setup_send_prev_func(xmlNodePtr node,
477                                      ObActionsIPreFunc *pre,
478                                      ObActionsIInputFunc *input,
479                                      ObActionsICancelFunc *cancel,
480                                      ObActionsIPostFunc *post)
481 {
482     Options *o = setup_follow(node);
483     setup_rel(o, node, TRUE, OB_DIRECTION_WEST,
484               pre, (o->follow ? input : NULL), post);
485     return o;
486 }
487
488 static gpointer setup_go_left_func(xmlNodePtr node,
489                                    ObActionsIPreFunc *pre,
490                                    ObActionsIInputFunc *input,
491                                    ObActionsICancelFunc *cancel,
492                                    ObActionsIPostFunc *post)
493 {
494     Options *o = g_slice_new0(Options);
495     setup_rel(o, node, FALSE, OB_DIRECTION_WEST, pre, input, post);
496     return o;
497 }
498
499 static gpointer setup_send_left_func(xmlNodePtr node,
500                                      ObActionsIPreFunc *pre,
501                                      ObActionsIInputFunc *input,
502                                      ObActionsICancelFunc *cancel,
503                                      ObActionsIPostFunc *post)
504 {
505     Options *o = setup_follow(node);
506     setup_rel(o, node, FALSE, OB_DIRECTION_WEST,
507               pre, (o->follow ? input : NULL), post);
508     return o;
509 }
510
511 static gpointer setup_go_right_func(xmlNodePtr node,
512                                     ObActionsIPreFunc *pre,
513                                     ObActionsIInputFunc *input,
514                                     ObActionsICancelFunc *cancel,
515                                     ObActionsIPostFunc *post)
516 {
517     Options *o = g_slice_new0(Options);
518     setup_rel(o, node, FALSE, OB_DIRECTION_EAST, pre, input, post);
519     return o;
520 }
521
522 static gpointer setup_send_right_func(xmlNodePtr node,
523                                       ObActionsIPreFunc *pre,
524                                       ObActionsIInputFunc *input,
525                                       ObActionsICancelFunc *cancel,
526                                       ObActionsIPostFunc *post)
527 {
528     Options *o = setup_follow(node);
529     setup_rel(o, node, FALSE, OB_DIRECTION_EAST,
530               pre, (o->follow ? input : NULL), post);
531     return o;
532 }
533
534 static gpointer setup_go_up_func(xmlNodePtr node,
535                                  ObActionsIPreFunc *pre,
536                                  ObActionsIInputFunc *input,
537                                  ObActionsICancelFunc *cancel,
538                                  ObActionsIPostFunc *post)
539 {
540     Options *o = g_slice_new0(Options);
541     setup_rel(o, node, FALSE, OB_DIRECTION_NORTH, pre, input, post);
542     return o;
543 }
544
545 static gpointer setup_send_up_func(xmlNodePtr node,
546                                    ObActionsIPreFunc *pre,
547                                    ObActionsIInputFunc *input,
548                                    ObActionsICancelFunc *cancel,
549                                    ObActionsIPostFunc *post)
550 {
551     Options *o = setup_follow(node);
552     setup_rel(o, node, FALSE, OB_DIRECTION_NORTH,
553               pre, (o->follow ? input : NULL), post);
554     return o;
555 }
556
557 static gpointer setup_go_down_func(xmlNodePtr node,
558                                    ObActionsIPreFunc *pre,
559                                    ObActionsIInputFunc *input,
560                                    ObActionsICancelFunc *cancel,
561                                    ObActionsIPostFunc *post)
562 {
563     Options *o = g_slice_new0(Options);
564     setup_rel(o, node, FALSE, OB_DIRECTION_SOUTH, pre, input, post);
565     return o;
566 }
567
568 static gpointer setup_send_down_func(xmlNodePtr node,
569                                      ObActionsIPreFunc *pre,
570                                      ObActionsIInputFunc *input,
571                                      ObActionsICancelFunc *cancel,
572                                      ObActionsIPostFunc *post)
573 {
574     Options *o = setup_follow(node);
575     setup_rel(o, node, FALSE, OB_DIRECTION_SOUTH,
576               pre, (o->follow ? input : NULL), post);
577     return o;
578 }