More work on refreshing the focus cycle dialog when windows are added/removed from...
[mikachu/openbox.git] / openbox / screen.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    screen.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-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 "debug.h"
21 #include "openbox.h"
22 #include "dock.h"
23 #include "xerror.h"
24 #include "prop.h"
25 #include "grab.h"
26 #include "startupnotify.h"
27 #include "moveresize.h"
28 #include "config.h"
29 #include "mainloop.h"
30 #include "screen.h"
31 #include "client.h"
32 #include "session.h"
33 #include "frame.h"
34 #include "event.h"
35 #include "focus.h"
36 #include "focus_cycle.h"
37 #include "popup.h"
38 #include "extensions.h"
39 #include "render/render.h"
40 #include "gettext.h"
41
42 #include <X11/Xlib.h>
43 #ifdef HAVE_UNISTD_H
44 #  include <sys/types.h>
45 #  include <unistd.h>
46 #endif
47 #include <assert.h>
48
49 /*! The event mask to grab on the root window */
50 #define ROOT_EVENTMASK (StructureNotifyMask | PropertyChangeMask | \
51                         EnterWindowMask | LeaveWindowMask | \
52                         SubstructureRedirectMask | FocusChangeMask | \
53                         ButtonPressMask | ButtonReleaseMask)
54
55 static gboolean screen_validate_layout(ObDesktopLayout *l);
56 static gboolean replace_wm(void);
57 static void     screen_tell_ksplash(void);
58 static void     screen_fallback_focus(void);
59
60 guint           screen_num_desktops;
61 guint           screen_num_monitors;
62 guint           screen_desktop;
63 guint           screen_last_desktop;
64 gboolean        screen_showing_desktop;
65 ObDesktopLayout screen_desktop_layout;
66 gchar         **screen_desktop_names;
67 Window          screen_support_win;
68 Time            screen_desktop_user_time = CurrentTime;
69
70 static Size     screen_physical_size;
71 static guint    screen_old_desktop;
72 static gboolean screen_desktop_timeout = TRUE;
73 /*! An array of desktops, holding an array of areas per monitor */
74 static Rect  *monitor_area = NULL;
75 /*! An array of desktops, holding an array of struts */
76 static GSList *struts_top = NULL;
77 static GSList *struts_left = NULL;
78 static GSList *struts_right = NULL;
79 static GSList *struts_bottom = NULL;
80
81 static ObPagerPopup *desktop_popup;
82
83 /*! The number of microseconds that you need to be on a desktop before it will
84   replace the remembered "last desktop" */
85 #define REMEMBER_LAST_DESKTOP_TIME 750000
86
87 static gboolean replace_wm(void)
88 {
89     gchar *wm_sn;
90     Atom wm_sn_atom;
91     Window current_wm_sn_owner;
92     Time timestamp;
93
94     wm_sn = g_strdup_printf("WM_S%d", ob_screen);
95     wm_sn_atom = XInternAtom(ob_display, wm_sn, FALSE);
96     g_free(wm_sn);
97
98     current_wm_sn_owner = XGetSelectionOwner(ob_display, wm_sn_atom);
99     if (current_wm_sn_owner == screen_support_win)
100         current_wm_sn_owner = None;
101     if (current_wm_sn_owner) {
102         if (!ob_replace_wm) {
103             g_message(_("A window manager is already running on screen %d"),
104                       ob_screen);
105             return FALSE;
106         }
107         xerror_set_ignore(TRUE);
108         xerror_occured = FALSE;
109
110         /* We want to find out when the current selection owner dies */
111         XSelectInput(ob_display, current_wm_sn_owner, StructureNotifyMask);
112         XSync(ob_display, FALSE);
113
114         xerror_set_ignore(FALSE);
115         if (xerror_occured)
116             current_wm_sn_owner = None;
117     }
118
119     timestamp = event_get_server_time();
120
121     XSetSelectionOwner(ob_display, wm_sn_atom, screen_support_win,
122                        timestamp);
123
124     if (XGetSelectionOwner(ob_display, wm_sn_atom) != screen_support_win) {
125         g_message(_("Could not acquire window manager selection on screen %d"),
126                   ob_screen);
127         return FALSE;
128     }
129
130     /* Wait for old window manager to go away */
131     if (current_wm_sn_owner) {
132       XEvent event;
133       gulong wait = 0;
134       const gulong timeout = G_USEC_PER_SEC * 15; /* wait for 15s max */
135
136       while (wait < timeout) {
137           if (XCheckWindowEvent(ob_display, current_wm_sn_owner,
138                                 StructureNotifyMask, &event) &&
139               event.type == DestroyNotify)
140               break;
141           g_usleep(G_USEC_PER_SEC / 10);
142           wait += G_USEC_PER_SEC / 10;
143       }
144
145       if (wait >= timeout) {
146           g_message(_("The WM on screen %d is not exiting"), ob_screen);
147           return FALSE;
148       }
149     }
150
151     /* Send client message indicating that we are now the WM */
152     prop_message(RootWindow(ob_display, ob_screen), prop_atoms.manager,
153                  timestamp, wm_sn_atom, screen_support_win, 0,
154                  SubstructureNotifyMask);
155
156     return TRUE;
157 }
158
159 gboolean screen_annex(void)
160 {
161     XSetWindowAttributes attrib;
162     pid_t pid;
163     gint i, num_support;
164     Atom *prop_atoms_start, *wm_supported_pos;
165     gulong *supported;
166
167     /* create the netwm support window */
168     attrib.override_redirect = TRUE;
169     attrib.event_mask = PropertyChangeMask;
170     screen_support_win = XCreateWindow(ob_display,
171                                        RootWindow(ob_display, ob_screen),
172                                        -100, -100, 1, 1, 0,
173                                        CopyFromParent, InputOutput,
174                                        CopyFromParent,
175                                        CWEventMask | CWOverrideRedirect,
176                                        &attrib);
177     XMapWindow(ob_display, screen_support_win);
178     XLowerWindow(ob_display, screen_support_win);
179
180     if (!replace_wm()) {
181         XDestroyWindow(ob_display, screen_support_win);
182         return FALSE;
183     }
184
185     xerror_set_ignore(TRUE);
186     xerror_occured = FALSE;
187     XSelectInput(ob_display, RootWindow(ob_display, ob_screen),
188                  ROOT_EVENTMASK);
189     xerror_set_ignore(FALSE);
190     if (xerror_occured) {
191         g_message(_("A window manager is already running on screen %d"),
192                   ob_screen);
193
194         XDestroyWindow(ob_display, screen_support_win);
195         return FALSE;
196     }
197
198     screen_set_root_cursor();
199
200     /* set the OPENBOX_PID hint */
201     pid = getpid();
202     PROP_SET32(RootWindow(ob_display, ob_screen),
203                openbox_pid, cardinal, pid);
204
205     /* set supporting window */
206     PROP_SET32(RootWindow(ob_display, ob_screen),
207                net_supporting_wm_check, window, screen_support_win);
208
209     /* set properties on the supporting window */
210     PROP_SETS(screen_support_win, net_wm_name, "Openbox");
211     PROP_SET32(screen_support_win, net_supporting_wm_check,
212                window, screen_support_win);
213
214     /* set the _NET_SUPPORTED_ATOMS hint */
215
216     /* this is all the atoms after net_supported in the prop_atoms struct */
217     prop_atoms_start = (Atom*)&prop_atoms;
218     wm_supported_pos = (Atom*)&(prop_atoms.net_supported);
219     num_support = sizeof(prop_atoms) / sizeof(Atom) -
220         (wm_supported_pos - prop_atoms_start) - 1;
221     i = 0;
222     supported = g_new(gulong, num_support);
223     supported[i++] = prop_atoms.net_supporting_wm_check;
224     supported[i++] = prop_atoms.net_wm_full_placement;
225     supported[i++] = prop_atoms.net_current_desktop;
226     supported[i++] = prop_atoms.net_number_of_desktops;
227     supported[i++] = prop_atoms.net_desktop_geometry;
228     supported[i++] = prop_atoms.net_desktop_viewport;
229     supported[i++] = prop_atoms.net_active_window;
230     supported[i++] = prop_atoms.net_workarea;
231     supported[i++] = prop_atoms.net_client_list;
232     supported[i++] = prop_atoms.net_client_list_stacking;
233     supported[i++] = prop_atoms.net_desktop_names;
234     supported[i++] = prop_atoms.net_close_window;
235     supported[i++] = prop_atoms.net_desktop_layout;
236     supported[i++] = prop_atoms.net_showing_desktop;
237     supported[i++] = prop_atoms.net_wm_name;
238     supported[i++] = prop_atoms.net_wm_visible_name;
239     supported[i++] = prop_atoms.net_wm_icon_name;
240     supported[i++] = prop_atoms.net_wm_visible_icon_name;
241     supported[i++] = prop_atoms.net_wm_desktop;
242     supported[i++] = prop_atoms.net_wm_strut;
243     supported[i++] = prop_atoms.net_wm_strut_partial;
244     supported[i++] = prop_atoms.net_wm_icon;
245     supported[i++] = prop_atoms.net_wm_icon_geometry;
246     supported[i++] = prop_atoms.net_wm_window_type;
247     supported[i++] = prop_atoms.net_wm_window_type_desktop;
248     supported[i++] = prop_atoms.net_wm_window_type_dock;
249     supported[i++] = prop_atoms.net_wm_window_type_toolbar;
250     supported[i++] = prop_atoms.net_wm_window_type_menu;
251     supported[i++] = prop_atoms.net_wm_window_type_utility;
252     supported[i++] = prop_atoms.net_wm_window_type_splash;
253     supported[i++] = prop_atoms.net_wm_window_type_dialog;
254     supported[i++] = prop_atoms.net_wm_window_type_normal;
255     supported[i++] = prop_atoms.net_wm_allowed_actions;
256     supported[i++] = prop_atoms.net_wm_action_move;
257     supported[i++] = prop_atoms.net_wm_action_resize;
258     supported[i++] = prop_atoms.net_wm_action_minimize;
259     supported[i++] = prop_atoms.net_wm_action_shade;
260     supported[i++] = prop_atoms.net_wm_action_maximize_horz;
261     supported[i++] = prop_atoms.net_wm_action_maximize_vert;
262     supported[i++] = prop_atoms.net_wm_action_fullscreen;
263     supported[i++] = prop_atoms.net_wm_action_change_desktop;
264     supported[i++] = prop_atoms.net_wm_action_close;
265     supported[i++] = prop_atoms.net_wm_action_above;
266     supported[i++] = prop_atoms.net_wm_action_below;
267     supported[i++] = prop_atoms.net_wm_state;
268     supported[i++] = prop_atoms.net_wm_state_modal;
269     supported[i++] = prop_atoms.net_wm_state_maximized_vert;
270     supported[i++] = prop_atoms.net_wm_state_maximized_horz;
271     supported[i++] = prop_atoms.net_wm_state_shaded;
272     supported[i++] = prop_atoms.net_wm_state_skip_taskbar;
273     supported[i++] = prop_atoms.net_wm_state_skip_pager;
274     supported[i++] = prop_atoms.net_wm_state_hidden;
275     supported[i++] = prop_atoms.net_wm_state_fullscreen;
276     supported[i++] = prop_atoms.net_wm_state_above;
277     supported[i++] = prop_atoms.net_wm_state_below;
278     supported[i++] = prop_atoms.net_wm_state_demands_attention;
279     supported[i++] = prop_atoms.net_moveresize_window;
280     supported[i++] = prop_atoms.net_wm_moveresize;
281     supported[i++] = prop_atoms.net_wm_user_time;
282 /*
283     supported[i++] = prop_atoms.net_wm_user_time_window;
284 */
285     supported[i++] = prop_atoms.net_frame_extents;
286     supported[i++] = prop_atoms.net_request_frame_extents;
287     supported[i++] = prop_atoms.net_restack_window;
288     supported[i++] = prop_atoms.net_startup_id;
289 #ifdef SYNC
290     supported[i++] = prop_atoms.net_wm_sync_request;
291     supported[i++] = prop_atoms.net_wm_sync_request_counter;
292 #endif
293     supported[i++] = prop_atoms.net_wm_pid;
294     supported[i++] = prop_atoms.net_wm_ping;
295
296     supported[i++] = prop_atoms.kde_wm_change_state;
297     supported[i++] = prop_atoms.kde_net_wm_frame_strut;
298     supported[i++] = prop_atoms.kde_net_wm_window_type_override;
299
300     supported[i++] = prop_atoms.ob_wm_action_undecorate;
301     supported[i++] = prop_atoms.ob_wm_state_undecorated;
302     supported[i++] = prop_atoms.openbox_pid;
303     supported[i++] = prop_atoms.ob_theme;
304     supported[i++] = prop_atoms.ob_config_file;
305     supported[i++] = prop_atoms.ob_control;
306     supported[i++] = prop_atoms.ob_version;
307     supported[i++] = prop_atoms.ob_app_role;
308     supported[i++] = prop_atoms.ob_app_name;
309     supported[i++] = prop_atoms.ob_app_class;
310     supported[i++] = prop_atoms.ob_app_type;
311     g_assert(i == num_support);
312
313     PROP_SETA32(RootWindow(ob_display, ob_screen),
314                 net_supported, atom, supported, num_support);
315     g_free(supported);
316
317     PROP_SETS(RootWindow(ob_display, ob_screen), ob_version,
318               OB_VERSION);
319
320     screen_tell_ksplash();
321
322     return TRUE;
323 }
324
325 static void screen_tell_ksplash(void)
326 {
327     XEvent e;
328     char **argv;
329
330     argv = g_new(gchar*, 6);
331     argv[0] = g_strdup("dcop");
332     argv[1] = g_strdup("ksplash");
333     argv[2] = g_strdup("ksplash");
334     argv[3] = g_strdup("upAndRunning(QString)");
335     argv[4] = g_strdup("wm started");
336     argv[5] = NULL;
337
338     /* tell ksplash through the dcop server command line interface */
339     g_spawn_async(NULL, argv, NULL,
340                   G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
341                   G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_STDOUT_TO_DEV_NULL,
342                   NULL, NULL, NULL, NULL);
343     g_strfreev(argv);
344
345     /* i'm not sure why we do this, kwin does it, but ksplash doesn't seem to
346        hear it anyways. perhaps it is for old ksplash. or new ksplash. or
347        something. oh well. */
348     e.xclient.type = ClientMessage;
349     e.xclient.display = ob_display;
350     e.xclient.window = RootWindow(ob_display, ob_screen);
351     e.xclient.message_type =
352         XInternAtom(ob_display, "_KDE_SPLASH_PROGRESS", False);
353     e.xclient.format = 8;
354     strcpy(e.xclient.data.b, "wm started");
355     XSendEvent(ob_display, RootWindow(ob_display, ob_screen),
356                False, SubstructureNotifyMask, &e );
357 }
358
359 void screen_startup(gboolean reconfig)
360 {
361     gchar **names = NULL;
362     guint32 d;
363     gboolean namesexist = FALSE;
364
365     desktop_popup = pager_popup_new();
366     pager_popup_height(desktop_popup, POPUP_HEIGHT);
367
368     if (reconfig) {
369         /* update the pager popup's width */
370         pager_popup_text_width_to_strings(desktop_popup,
371                                           screen_desktop_names,
372                                           screen_num_desktops);
373         return;
374     }
375
376     /* get the initial size */
377     screen_resize();
378
379     /* have names already been set for the desktops? */
380     if (PROP_GETSS(RootWindow(ob_display, ob_screen),
381                    net_desktop_names, utf8, &names))
382     {
383         g_strfreev(names);
384         namesexist = TRUE;
385     }
386
387     /* if names don't exist and we have session names, set those.
388        do this stuff BEFORE setting the number of desktops, because that
389        will create default names for them
390     */
391     if (!namesexist && session_desktop_names != NULL) {
392         guint i, numnames;
393         GSList *it;
394
395         /* get the desktop names */
396         numnames = g_slist_length(session_desktop_names);
397         names = g_new(gchar*, numnames + 1);
398         names[numnames] = NULL;
399         for (i = 0, it = session_desktop_names; it; ++i, it = g_slist_next(it))
400             names[i] = g_strdup(it->data);
401
402         /* set the root window property */
403         PROP_SETSS(RootWindow(ob_display, ob_screen), net_desktop_names,names);
404
405         g_strfreev(names);
406     }
407
408     /* set the number of desktops, if it's not already set.
409
410        this will also set the default names from the config file up for
411        desktops that don't have names yet */
412     screen_num_desktops = 0;
413     if (PROP_GET32(RootWindow(ob_display, ob_screen),
414                    net_number_of_desktops, cardinal, &d))
415     {
416         if (d != config_desktops_num) {
417             /* TRANSLATORS: If you need to specify a different order of the
418                arguments, you can use %1$d for the first one and %2$d for the
419                second one. For example,
420                "The current session has %2$d desktops, but Openbox is configured for %1$d ..." */
421             g_warning(ngettext("Openbox is configured for %d desktop, but the current session has %d.  Overriding the Openbox configuration.", "Openbox is configured for %d desktops, but the current session has %d.  Overriding the Openbox configuration.", config_desktops_num),
422                       config_desktops_num, d);
423         }
424         screen_set_num_desktops(d);
425     }
426     /* restore from session if possible */
427     else if (session_num_desktops)
428         screen_set_num_desktops(session_num_desktops);
429     else
430         screen_set_num_desktops(config_desktops_num);
431
432     screen_desktop = screen_num_desktops;  /* something invalid */
433     /* start on the current desktop when a wm was already running */
434     if (PROP_GET32(RootWindow(ob_display, ob_screen),
435                    net_current_desktop, cardinal, &d) &&
436         d < screen_num_desktops)
437     {
438         screen_set_desktop(d, FALSE);
439     } else if (session_desktop >= 0)
440         screen_set_desktop(MIN((guint)session_desktop,
441                                screen_num_desktops), FALSE);
442     else
443         screen_set_desktop(MIN(config_screen_firstdesk,
444                                screen_num_desktops) - 1, FALSE);
445     screen_last_desktop = screen_desktop;
446
447     /* don't start in showing-desktop mode */
448     screen_showing_desktop = FALSE;
449     PROP_SET32(RootWindow(ob_display, ob_screen),
450                net_showing_desktop, cardinal, screen_showing_desktop);
451
452     if (session_desktop_layout_present &&
453         screen_validate_layout(&session_desktop_layout))
454     {
455         screen_desktop_layout = session_desktop_layout;
456     }
457     else
458         screen_update_layout();
459 }
460
461 void screen_shutdown(gboolean reconfig)
462 {
463     pager_popup_free(desktop_popup);
464
465     if (reconfig)
466         return;
467
468     XSelectInput(ob_display, RootWindow(ob_display, ob_screen),
469                  NoEventMask);
470
471     /* we're not running here no more! */
472     PROP_ERASE(RootWindow(ob_display, ob_screen), openbox_pid);
473     /* not without us */
474     PROP_ERASE(RootWindow(ob_display, ob_screen), net_supported);
475     /* don't keep this mode */
476     PROP_ERASE(RootWindow(ob_display, ob_screen), net_showing_desktop);
477
478     XDestroyWindow(ob_display, screen_support_win);
479
480     g_strfreev(screen_desktop_names);
481     screen_desktop_names = NULL;
482 }
483
484 void screen_resize(void)
485 {
486     static gint oldw = 0, oldh = 0;
487     gint w, h;
488     GList *it;
489     gulong geometry[2];
490
491     w = WidthOfScreen(ScreenOfDisplay(ob_display, ob_screen));
492     h = HeightOfScreen(ScreenOfDisplay(ob_display, ob_screen));
493
494     if (w == oldw && h == oldh) return;
495
496     oldw = w; oldh = h;
497
498     /* Set the _NET_DESKTOP_GEOMETRY hint */
499     screen_physical_size.width = geometry[0] = w;
500     screen_physical_size.height = geometry[1] = h;
501     PROP_SETA32(RootWindow(ob_display, ob_screen),
502                 net_desktop_geometry, cardinal, geometry, 2);
503
504     if (ob_state() != OB_STATE_RUNNING)
505         return;
506
507     screen_update_areas();
508     dock_configure();
509
510     for (it = client_list; it; it = g_list_next(it))
511         client_move_onscreen(it->data, FALSE);
512 }
513
514 void screen_set_num_desktops(guint num)
515 {
516     guint old;
517     gulong *viewport;
518     GList *it, *stacking_copy;
519
520     g_assert(num > 0);
521
522     if (screen_num_desktops == num) return;
523
524     old = screen_num_desktops;
525     screen_num_desktops = num;
526     PROP_SET32(RootWindow(ob_display, ob_screen),
527                net_number_of_desktops, cardinal, num);
528
529     /* set the viewport hint */
530     viewport = g_new0(gulong, num * 2);
531     PROP_SETA32(RootWindow(ob_display, ob_screen),
532                 net_desktop_viewport, cardinal, viewport, num * 2);
533     g_free(viewport);
534
535     /* the number of rows/columns will differ */
536     screen_update_layout();
537
538     /* move windows on desktops that will no longer exist!
539        make a copy of the list cuz we're changing it */
540     stacking_copy = g_list_copy(stacking_list);
541     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
542         if (WINDOW_IS_CLIENT(it->data)) {
543             ObClient *c = it->data;
544             if (c->desktop != DESKTOP_ALL && c->desktop >= num)
545                 client_set_desktop(c, num - 1, FALSE, TRUE);
546             /* raise all the windows that are on the current desktop which
547                is being merged */
548             else if (screen_desktop == num - 1 &&
549                      (c->desktop == DESKTOP_ALL ||
550                       c->desktop == screen_desktop))
551                 stacking_raise(CLIENT_AS_WINDOW(c));
552         }
553     }
554     g_list_free(stacking_copy);
555
556     /* change our struts/area to match (after moving windows) */
557     screen_update_areas();
558
559     /* may be some unnamed desktops that we need to fill in with names
560        (after updating the areas so the popup can resize) */
561     screen_update_desktop_names();
562
563     /* change our desktop if we're on one that no longer exists! */
564     if (screen_desktop >= screen_num_desktops)
565         screen_set_desktop(num - 1, TRUE);
566 }
567
568 static void screen_fallback_focus(void)
569 {
570     ObClient *c;
571     gboolean allow_omni;
572
573     /* only allow omnipresent windows to get focus on desktop change if
574        an omnipresent window is already focused (it'll keep focus probably, but
575        maybe not depending on mouse-focus options) */
576     allow_omni = focus_client && (client_normal(focus_client) &&
577                                   focus_client->desktop == DESKTOP_ALL);
578
579     /* the client moved there already so don't move focus. prevent flicker
580        on sendtodesktop + follow */
581     if (focus_client && focus_client->desktop == screen_desktop)
582         return;
583
584     /* have to try focus here because when you leave an empty desktop
585        there is no focus out to watch for. also, we have different rules
586        here. we always allow it to look under the mouse pointer if
587        config_focus_last is FALSE
588
589        do this before hiding the windows so if helper windows are coming
590        with us, they don't get hidden
591     */
592     if ((c = focus_fallback(TRUE, !config_focus_last, allow_omni,
593                             !allow_omni)))
594     {
595         /* only do the flicker reducing stuff ahead of time if we are going
596            to call xsetinputfocus on the window ourselves. otherwise there is
597            no guarantee the window will actually take focus.. */
598         if (c->can_focus) {
599             /* reduce flicker by hiliting now rather than waiting for the
600                server FocusIn event */
601             frame_adjust_focus(c->frame, TRUE);
602             /* do this here so that if you switch desktops to a window with
603                helper windows then the helper windows won't flash */
604             client_bring_helper_windows(c);
605         }
606     }
607 }
608
609 static gboolean last_desktop_func(gpointer data)
610 {
611     screen_desktop_timeout = TRUE;
612     return FALSE;
613 }
614
615 void screen_set_desktop(guint num, gboolean dofocus)
616 {
617     GList *it;
618     guint previous;
619     gulong ignore_start;
620
621     g_assert(num < screen_num_desktops);
622
623     previous = screen_desktop;
624     screen_desktop = num;
625
626     if (previous == num) return;
627
628     PROP_SET32(RootWindow(ob_display, ob_screen),
629                net_current_desktop, cardinal, num);
630
631     /* This whole thing decides when/how to save the screen_last_desktop so
632        that it can be restored later if you want */
633     if (screen_desktop_timeout) {
634         /* If screen_desktop_timeout is true, then we've been on this desktop
635            long enough and we can save it as the last desktop. */
636
637         if (screen_last_desktop == previous)
638             /* this is the startup state only */
639             screen_old_desktop = screen_desktop;
640         else {
641             /* save the "last desktop" as the "old desktop" */
642             screen_old_desktop = screen_last_desktop;
643             /* save the desktop we're coming from as the "last desktop" */
644             screen_last_desktop = previous;
645         }
646     }
647     else {
648         /* If screen_desktop_timeout is false, then we just got to this desktop
649            and we are moving away again. */
650
651         if (screen_desktop == screen_last_desktop) {
652             /* If we are moving to the "last desktop" .. */
653             if (previous == screen_old_desktop) {
654                 /* .. from the "old desktop", change the last desktop to
655                    be where we are coming from */
656                 screen_last_desktop = screen_old_desktop;
657             }
658             else if (screen_last_desktop == screen_old_desktop) {
659                 /* .. and also to the "old desktop", change the "last
660                    desktop" to be where we are coming from */
661                 screen_last_desktop = previous;
662             }
663             else {
664                 /* .. from some other desktop, then set the "last desktop" to
665                    be the saved "old desktop", i.e. where we were before the
666                    "last desktop" */
667                 screen_last_desktop = screen_old_desktop;
668             }
669         }
670         else {
671             /* If we are moving to any desktop besides the "last desktop"..
672                (this is the normal case) */
673             if (screen_desktop == screen_old_desktop) {
674                 /* If moving to the "old desktop", which is not the
675                    "last desktop", don't save anything */
676             }
677             else if (previous == screen_old_desktop) {
678                 /* If moving from the "old desktop", and not to the
679                    "last desktop", don't save anything */
680             }
681             else if (screen_last_desktop == screen_old_desktop) {
682                 /* If the "last desktop" is the same as "old desktop" and
683                    you're not moving to the "last desktop" then save where
684                    we're coming from as the "last desktop" */
685                 screen_last_desktop = previous;
686             }
687             else {
688                 /* If the "last desktop" is different from the "old desktop"
689                    and you're not moving to the "last desktop", then don't save
690                    anything */
691             }
692         }
693     }
694     screen_desktop_timeout = FALSE;
695     ob_main_loop_timeout_remove(ob_main_loop, last_desktop_func);
696     ob_main_loop_timeout_add(ob_main_loop, REMEMBER_LAST_DESKTOP_TIME,
697                              last_desktop_func, NULL, NULL, NULL);
698
699     ob_debug("Moving to desktop %d\n", num+1);
700
701     if (ob_state() == OB_STATE_RUNNING)
702         screen_show_desktop_popup(screen_desktop);
703
704     /* ignore enter events caused by the move */
705     ignore_start = event_start_ignore_all_enters();
706
707     if (moveresize_client)
708         client_set_desktop(moveresize_client, num, TRUE, FALSE);
709
710     /* show windows before hiding the rest to lessen the enter/leave events */
711
712     /* show windows from top to bottom */
713     for (it = stacking_list; it; it = g_list_next(it)) {
714         if (WINDOW_IS_CLIENT(it->data)) {
715             ObClient *c = it->data;
716             client_show(c);
717         }
718     }
719
720     if (dofocus) screen_fallback_focus();
721
722     /* hide windows from bottom to top */
723     for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
724         if (WINDOW_IS_CLIENT(it->data)) {
725             ObClient *c = it->data;
726             if (client_hide(c)) {
727                 if (c == focus_client) {
728                     /* c was focused and we didn't do fallback clearly so make
729                        sure openbox doesnt still consider the window focused.
730                        this happens when using NextWindow with allDesktops,
731                        since it doesnt want to move focus on desktop change,
732                        but the focus is not going to stay with the current
733                        window, which has now disappeared.
734                        only do this if the client was actually hidden,
735                        otherwise it can keep focus. */
736                     focus_set_client(NULL);
737                 }
738             }
739         }
740     }
741
742     focus_cycle_addremove(NULL, TRUE);
743
744     event_end_ignore_all_enters(ignore_start);
745
746     if (event_curtime != CurrentTime)
747         screen_desktop_user_time = event_curtime;
748 }
749
750 void screen_add_desktop(gboolean current)
751 {
752     gulong ignore_start;
753
754     /* ignore enter events caused by this */
755     ignore_start = event_start_ignore_all_enters();
756
757     screen_set_num_desktops(screen_num_desktops+1);
758
759     /* move all the clients over */
760     if (current) {
761         GList *it;
762
763         for (it = client_list; it; it = g_list_next(it)) {
764             ObClient *c = it->data;
765             if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop &&
766                 /* don't move direct children, they'll be moved with their
767                    parent - which will have to be on the same desktop */
768                 !client_direct_parent(c))
769             {
770                 ob_debug("moving window %s\n", c->title);
771                 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
772             }
773         }
774     }
775
776     event_end_ignore_all_enters(ignore_start);
777 }
778
779 void screen_remove_desktop(gboolean current)
780 {
781     guint rmdesktop, movedesktop;
782     GList *it, *stacking_copy;
783     gulong ignore_start;
784
785     if (screen_num_desktops <= 1) return;
786
787     /* ignore enter events caused by this */
788     ignore_start = event_start_ignore_all_enters();
789
790     /* what desktop are we removing and moving to? */
791     if (current)
792         rmdesktop = screen_desktop;
793     else
794         rmdesktop = screen_num_desktops - 1;
795     if (rmdesktop < screen_num_desktops - 1)
796         movedesktop = rmdesktop + 1;
797     else
798         movedesktop = rmdesktop;
799
800     /* make a copy of the list cuz we're changing it */
801     stacking_copy = g_list_copy(stacking_list);
802     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
803         if (WINDOW_IS_CLIENT(it->data)) {
804             ObClient *c = it->data;
805             guint d = c->desktop;
806             if (d != DESKTOP_ALL && d >= movedesktop &&
807                 /* don't move direct children, they'll be moved with their
808                    parent - which will have to be on the same desktop */
809                 !client_direct_parent(c))
810             {
811                 ob_debug("moving window %s\n", c->title);
812                 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
813             }
814             /* raise all the windows that are on the current desktop which
815                is being merged */
816             if ((screen_desktop == rmdesktop - 1 ||
817                  screen_desktop == rmdesktop) &&
818                 (d == DESKTOP_ALL || d == screen_desktop))
819             {
820                 stacking_raise(CLIENT_AS_WINDOW(c));
821                 ob_debug("raising window %s\n", c->title);
822             }
823         }
824     }
825     g_list_free(stacking_copy);
826
827     /* fallback focus like we're changing desktops */
828     if (screen_desktop < screen_num_desktops - 1) {
829         screen_fallback_focus();
830         ob_debug("fake desktop change\n");
831     }
832
833     screen_set_num_desktops(screen_num_desktops-1);
834
835     event_end_ignore_all_enters(ignore_start);
836 }
837
838 static void get_row_col(guint d, guint *r, guint *c)
839 {
840     switch (screen_desktop_layout.orientation) {
841     case OB_ORIENTATION_HORZ:
842         switch (screen_desktop_layout.start_corner) {
843         case OB_CORNER_TOPLEFT:
844             *r = d / screen_desktop_layout.columns;
845             *c = d % screen_desktop_layout.columns;
846             break;
847         case OB_CORNER_BOTTOMLEFT:
848             *r = screen_desktop_layout.rows - 1 -
849                 d / screen_desktop_layout.columns;
850             *c = d % screen_desktop_layout.columns;
851             break;
852         case OB_CORNER_TOPRIGHT:
853             *r = d / screen_desktop_layout.columns;
854             *c = screen_desktop_layout.columns - 1 -
855                 d % screen_desktop_layout.columns;
856             break;
857         case OB_CORNER_BOTTOMRIGHT:
858             *r = screen_desktop_layout.rows - 1 -
859                 d / screen_desktop_layout.columns;
860             *c = screen_desktop_layout.columns - 1 -
861                 d % screen_desktop_layout.columns;
862             break;
863         }
864         break;
865     case OB_ORIENTATION_VERT:
866         switch (screen_desktop_layout.start_corner) {
867         case OB_CORNER_TOPLEFT:
868             *r = d % screen_desktop_layout.rows;
869             *c = d / screen_desktop_layout.rows;
870             break;
871         case OB_CORNER_BOTTOMLEFT:
872             *r = screen_desktop_layout.rows - 1 -
873                 d % screen_desktop_layout.rows;
874             *c = d / screen_desktop_layout.rows;
875             break;
876         case OB_CORNER_TOPRIGHT:
877             *r = d % screen_desktop_layout.rows;
878             *c = screen_desktop_layout.columns - 1 -
879                 d / screen_desktop_layout.rows;
880             break;
881         case OB_CORNER_BOTTOMRIGHT:
882             *r = screen_desktop_layout.rows - 1 -
883                 d % screen_desktop_layout.rows;
884             *c = screen_desktop_layout.columns - 1 -
885                 d / screen_desktop_layout.rows;
886             break;
887         }
888         break;
889     }
890 }
891
892 static guint translate_row_col(guint r, guint c)
893 {
894     switch (screen_desktop_layout.orientation) {
895     case OB_ORIENTATION_HORZ:
896         switch (screen_desktop_layout.start_corner) {
897         case OB_CORNER_TOPLEFT:
898             return r % screen_desktop_layout.rows *
899                 screen_desktop_layout.columns +
900                 c % screen_desktop_layout.columns;
901         case OB_CORNER_BOTTOMLEFT:
902             return (screen_desktop_layout.rows - 1 -
903                     r % screen_desktop_layout.rows) *
904                 screen_desktop_layout.columns +
905                 c % screen_desktop_layout.columns;
906         case OB_CORNER_TOPRIGHT:
907             return r % screen_desktop_layout.rows *
908                 screen_desktop_layout.columns +
909                 (screen_desktop_layout.columns - 1 -
910                  c % screen_desktop_layout.columns);
911         case OB_CORNER_BOTTOMRIGHT:
912             return (screen_desktop_layout.rows - 1 -
913                     r % screen_desktop_layout.rows) *
914                 screen_desktop_layout.columns +
915                 (screen_desktop_layout.columns - 1 -
916                  c % screen_desktop_layout.columns);
917         }
918     case OB_ORIENTATION_VERT:
919         switch (screen_desktop_layout.start_corner) {
920         case OB_CORNER_TOPLEFT:
921             return c % screen_desktop_layout.columns *
922                 screen_desktop_layout.rows +
923                 r % screen_desktop_layout.rows;
924         case OB_CORNER_BOTTOMLEFT:
925             return c % screen_desktop_layout.columns *
926                 screen_desktop_layout.rows +
927                 (screen_desktop_layout.rows - 1 -
928                  r % screen_desktop_layout.rows);
929         case OB_CORNER_TOPRIGHT:
930             return (screen_desktop_layout.columns - 1 -
931                     c % screen_desktop_layout.columns) *
932                 screen_desktop_layout.rows +
933                 r % screen_desktop_layout.rows;
934         case OB_CORNER_BOTTOMRIGHT:
935             return (screen_desktop_layout.columns - 1 -
936                     c % screen_desktop_layout.columns) *
937                 screen_desktop_layout.rows +
938                 (screen_desktop_layout.rows - 1 -
939                  r % screen_desktop_layout.rows);
940         }
941     }
942     g_assert_not_reached();
943     return 0;
944 }
945
946 static gboolean hide_desktop_popup_func(gpointer data)
947 {
948     pager_popup_hide(desktop_popup);
949     return FALSE; /* don't repeat */
950 }
951
952 void screen_show_desktop_popup(guint d)
953 {
954     Rect *a;
955
956     /* 0 means don't show the popup */
957     if (!config_desktop_popup_time) return;
958
959     a = screen_physical_area_primary(FALSE);
960     pager_popup_position(desktop_popup, CenterGravity,
961                          a->x + a->width / 2, a->y + a->height / 2);
962     pager_popup_icon_size_multiplier(desktop_popup,
963                                      (screen_desktop_layout.columns /
964                                       screen_desktop_layout.rows) / 2,
965                                      (screen_desktop_layout.rows/
966                                       screen_desktop_layout.columns) / 2);
967     pager_popup_max_width(desktop_popup,
968                           MAX(a->width/3, POPUP_WIDTH));
969     pager_popup_show(desktop_popup, screen_desktop_names[d], d);
970
971     ob_main_loop_timeout_remove(ob_main_loop, hide_desktop_popup_func);
972     ob_main_loop_timeout_add(ob_main_loop, config_desktop_popup_time * 1000,
973                              hide_desktop_popup_func, NULL, NULL, NULL);
974     g_free(a);
975 }
976
977 void screen_hide_desktop_popup(void)
978 {
979     ob_main_loop_timeout_remove(ob_main_loop, hide_desktop_popup_func);
980     pager_popup_hide(desktop_popup);
981 }
982
983 guint screen_find_desktop(guint from, ObDirection dir,
984                           gboolean wrap, gboolean linear)
985 {
986     guint r, c;
987     guint d;
988
989     d = from;
990     get_row_col(d, &r, &c);
991     if (linear) {
992         switch (dir) {
993         case OB_DIRECTION_EAST:
994             if (d < screen_num_desktops - 1)
995                 ++d;
996             else if (wrap)
997                 d = 0;
998             else
999                 return from;
1000             break;
1001         case OB_DIRECTION_WEST:
1002             if (d > 0)
1003                 --d;
1004             else if (wrap)
1005                 d = screen_num_desktops - 1;
1006             else
1007                 return from;
1008             break;
1009         default:
1010             g_assert_not_reached();
1011             return from;
1012         }
1013     } else {
1014         switch (dir) {
1015         case OB_DIRECTION_EAST:
1016             ++c;
1017             if (c >= screen_desktop_layout.columns) {
1018                 if (wrap)
1019                     c = 0;
1020                 else
1021                     return from;
1022             }
1023             d = translate_row_col(r, c);
1024             if (d >= screen_num_desktops) {
1025                 if (wrap)
1026                     ++c;
1027                 else
1028                     return from;
1029             }
1030             break;
1031         case OB_DIRECTION_WEST:
1032             --c;
1033             if (c >= screen_desktop_layout.columns) {
1034                 if (wrap)
1035                     c = screen_desktop_layout.columns - 1;
1036                 else
1037                     return from;
1038             }
1039             d = translate_row_col(r, c);
1040             if (d >= screen_num_desktops) {
1041                 if (wrap)
1042                     --c;
1043                 else
1044                     return from;
1045             }
1046             break;
1047         case OB_DIRECTION_SOUTH:
1048             ++r;
1049             if (r >= screen_desktop_layout.rows) {
1050                 if (wrap)
1051                     r = 0;
1052                 else
1053                     return from;
1054             }
1055             d = translate_row_col(r, c);
1056             if (d >= screen_num_desktops) {
1057                 if (wrap)
1058                     ++r;
1059                 else
1060                     return from;
1061             }
1062             break;
1063         case OB_DIRECTION_NORTH:
1064             --r;
1065             if (r >= screen_desktop_layout.rows) {
1066                 if (wrap)
1067                     r = screen_desktop_layout.rows - 1;
1068                 else
1069                     return from;
1070             }
1071             d = translate_row_col(r, c);
1072             if (d >= screen_num_desktops) {
1073                 if (wrap)
1074                     --r;
1075                 else
1076                     return from;
1077             }
1078             break;
1079         default:
1080             g_assert_not_reached();
1081             return from;
1082         }
1083
1084         d = translate_row_col(r, c);
1085     }
1086     return d;
1087 }
1088
1089 static gboolean screen_validate_layout(ObDesktopLayout *l)
1090 {
1091     if (l->columns == 0 && l->rows == 0) /* both 0's is bad data.. */
1092         return FALSE;
1093
1094     /* fill in a zero rows/columns */
1095     if (l->columns == 0) {
1096         l->columns = screen_num_desktops / l->rows;
1097         if (l->rows * l->columns < screen_num_desktops)
1098             l->columns++;
1099         if (l->rows * l->columns >= screen_num_desktops + l->columns)
1100             l->rows--;
1101     } else if (l->rows == 0) {
1102         l->rows = screen_num_desktops / l->columns;
1103         if (l->columns * l->rows < screen_num_desktops)
1104             l->rows++;
1105         if (l->columns * l->rows >= screen_num_desktops + l->rows)
1106             l->columns--;
1107     }
1108
1109     /* bounds checking */
1110     if (l->orientation == OB_ORIENTATION_HORZ) {
1111         l->columns = MIN(screen_num_desktops, l->columns);
1112         l->rows = MIN(l->rows,
1113                       (screen_num_desktops + l->columns - 1) / l->columns);
1114         l->columns = screen_num_desktops / l->rows +
1115             !!(screen_num_desktops % l->rows);
1116     } else {
1117         l->rows = MIN(screen_num_desktops, l->rows);
1118         l->columns = MIN(l->columns,
1119                          (screen_num_desktops + l->rows - 1) / l->rows);
1120         l->rows = screen_num_desktops / l->columns +
1121             !!(screen_num_desktops % l->columns);
1122     }
1123     return TRUE;
1124 }
1125
1126 void screen_update_layout(void)
1127
1128 {
1129     ObDesktopLayout l;
1130     guint32 *data;
1131     guint num;
1132
1133     screen_desktop_layout.orientation = OB_ORIENTATION_HORZ;
1134     screen_desktop_layout.start_corner = OB_CORNER_TOPLEFT;
1135     screen_desktop_layout.rows = 1;
1136     screen_desktop_layout.columns = screen_num_desktops;
1137
1138     if (PROP_GETA32(RootWindow(ob_display, ob_screen),
1139                     net_desktop_layout, cardinal, &data, &num)) {
1140         if (num == 3 || num == 4) {
1141
1142             if (data[0] == prop_atoms.net_wm_orientation_vert)
1143                 l.orientation = OB_ORIENTATION_VERT;
1144             else if (data[0] == prop_atoms.net_wm_orientation_horz)
1145                 l.orientation = OB_ORIENTATION_HORZ;
1146             else
1147                 return;
1148
1149             if (num < 4)
1150                 l.start_corner = OB_CORNER_TOPLEFT;
1151             else {
1152                 if (data[3] == prop_atoms.net_wm_topleft)
1153                     l.start_corner = OB_CORNER_TOPLEFT;
1154                 else if (data[3] == prop_atoms.net_wm_topright)
1155                     l.start_corner = OB_CORNER_TOPRIGHT;
1156                 else if (data[3] == prop_atoms.net_wm_bottomright)
1157                     l.start_corner = OB_CORNER_BOTTOMRIGHT;
1158                 else if (data[3] == prop_atoms.net_wm_bottomleft)
1159                     l.start_corner = OB_CORNER_BOTTOMLEFT;
1160                 else
1161                     return;
1162             }
1163
1164             l.columns = data[1];
1165             l.rows = data[2];
1166
1167             if (screen_validate_layout(&l))
1168                 screen_desktop_layout = l;
1169
1170             g_free(data);
1171         }
1172     }
1173 }
1174
1175 void screen_update_desktop_names(void)
1176 {
1177     guint i;
1178
1179     /* empty the array */
1180     g_strfreev(screen_desktop_names);
1181     screen_desktop_names = NULL;
1182
1183     if (PROP_GETSS(RootWindow(ob_display, ob_screen),
1184                    net_desktop_names, utf8, &screen_desktop_names))
1185         for (i = 0; screen_desktop_names[i] && i < screen_num_desktops; ++i);
1186     else
1187         i = 0;
1188     if (i < screen_num_desktops) {
1189         GSList *it;
1190
1191         screen_desktop_names = g_renew(gchar*, screen_desktop_names,
1192                                        screen_num_desktops + 1);
1193         screen_desktop_names[screen_num_desktops] = NULL;
1194
1195         it = g_slist_nth(config_desktops_names, i);
1196
1197         for (; i < screen_num_desktops; ++i) {
1198             if (it && ((char*)it->data)[0]) /* not empty */
1199                 /* use the names from the config file when possible */
1200                 screen_desktop_names[i] = g_strdup(it->data);
1201             else
1202                 /* make up a nice name if it's not though */
1203                 screen_desktop_names[i] = g_strdup_printf(_("desktop %i"),
1204                                                           i + 1);
1205             if (it) it = g_slist_next(it);
1206         }
1207
1208         /* if we changed any names, then set the root property so we can
1209            all agree on the names */
1210         PROP_SETSS(RootWindow(ob_display, ob_screen), net_desktop_names,
1211                    screen_desktop_names);
1212     }
1213
1214     /* resize the pager for these names */
1215     pager_popup_text_width_to_strings(desktop_popup,
1216                                       screen_desktop_names,
1217                                       screen_num_desktops);
1218 }
1219
1220 void screen_show_desktop(gboolean show, ObClient *show_only)
1221 {
1222     GList *it;
1223
1224     if (show == screen_showing_desktop) return; /* no change */
1225
1226     screen_showing_desktop = show;
1227
1228     if (show) {
1229         /* hide windows bottom to top */
1230         for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
1231             if (WINDOW_IS_CLIENT(it->data)) {
1232                 ObClient *client = it->data;
1233                 client_showhide(client);
1234             }
1235         }
1236     }
1237     else {
1238         /* restore windows top to bottom */
1239         for (it = stacking_list; it; it = g_list_next(it)) {
1240             if (WINDOW_IS_CLIENT(it->data)) {
1241                 ObClient *client = it->data;
1242                 if (client_should_show(client)) {
1243                     if (!show_only || client == show_only)
1244                         client_show(client);
1245                     else
1246                         client_iconify(client, TRUE, FALSE, TRUE);
1247                 }
1248             }
1249         }
1250     }
1251
1252     if (show) {
1253         /* focus the desktop */
1254         for (it = focus_order; it; it = g_list_next(it)) {
1255             ObClient *c = it->data;
1256             if (c->type == OB_CLIENT_TYPE_DESKTOP &&
1257                 (c->desktop == screen_desktop || c->desktop == DESKTOP_ALL) &&
1258                 client_focus(it->data))
1259                 break;
1260         }
1261     }
1262     else if (!show_only) {
1263         ObClient *c;
1264
1265         if ((c = focus_fallback(TRUE, FALSE, TRUE, FALSE))) {
1266             /* only do the flicker reducing stuff ahead of time if we are going
1267                to call xsetinputfocus on the window ourselves. otherwise there
1268                is no guarantee the window will actually take focus.. */
1269             if (c->can_focus) {
1270                 /* reduce flicker by hiliting now rather than waiting for the
1271                    server FocusIn event */
1272                 frame_adjust_focus(c->frame, TRUE);
1273             }
1274         }
1275     }
1276
1277     show = !!show; /* make it boolean */
1278     PROP_SET32(RootWindow(ob_display, ob_screen),
1279                net_showing_desktop, cardinal, show);
1280 }
1281
1282 void screen_install_colormap(ObClient *client, gboolean install)
1283 {
1284     if (client == NULL || client->colormap == None) {
1285         if (install)
1286             XInstallColormap(RrDisplay(ob_rr_inst), RrColormap(ob_rr_inst));
1287         else
1288             XUninstallColormap(RrDisplay(ob_rr_inst), RrColormap(ob_rr_inst));
1289     } else {
1290         xerror_set_ignore(TRUE);
1291         if (install)
1292             XInstallColormap(RrDisplay(ob_rr_inst), client->colormap);
1293         else
1294             XUninstallColormap(RrDisplay(ob_rr_inst), client->colormap);
1295         xerror_set_ignore(FALSE);
1296     }
1297 }
1298
1299 typedef struct {
1300     guint desktop;
1301     StrutPartial *strut;
1302 } ObScreenStrut;
1303
1304 #define RESET_STRUT_LIST(sl) \
1305     (g_slist_free(sl), sl = NULL)
1306
1307 #define ADD_STRUT_TO_LIST(sl, d, s) \
1308 { \
1309     ObScreenStrut *ss = g_new(ObScreenStrut, 1); \
1310     ss->desktop = d; \
1311     ss->strut = s;  \
1312     sl = g_slist_prepend(sl, ss); \
1313 }
1314
1315 #define VALIDATE_STRUTS(sl, side, max) \
1316 { \
1317     GSList *it; \
1318     for (it = sl; it; it = g_slist_next(it)) { \
1319       ObScreenStrut *ss = it->data; \
1320       ss->strut->side = MIN(max, ss->strut->side); \
1321     } \
1322 }
1323
1324 void screen_update_areas(void)
1325 {
1326     guint i;
1327     gulong *dims;
1328     GList *it;
1329
1330     g_free(monitor_area);
1331     extensions_xinerama_screens(&monitor_area, &screen_num_monitors);
1332
1333     /* set up the user-specified margins */
1334     config_margins.top_start = RECT_LEFT(monitor_area[screen_num_monitors]);
1335     config_margins.top_end = RECT_RIGHT(monitor_area[screen_num_monitors]);
1336     config_margins.bottom_start = RECT_LEFT(monitor_area[screen_num_monitors]);
1337     config_margins.bottom_end = RECT_RIGHT(monitor_area[screen_num_monitors]);
1338     config_margins.left_start = RECT_TOP(monitor_area[screen_num_monitors]);
1339     config_margins.left_end = RECT_BOTTOM(monitor_area[screen_num_monitors]);
1340     config_margins.right_start = RECT_TOP(monitor_area[screen_num_monitors]);
1341     config_margins.right_end = RECT_BOTTOM(monitor_area[screen_num_monitors]);
1342
1343     RESET_STRUT_LIST(struts_left);
1344     RESET_STRUT_LIST(struts_top);
1345     RESET_STRUT_LIST(struts_right);
1346     RESET_STRUT_LIST(struts_bottom);
1347
1348     /* collect the struts */
1349     for (it = client_list; it; it = g_list_next(it)) {
1350         ObClient *c = it->data;
1351         if (c->strut.left)
1352             ADD_STRUT_TO_LIST(struts_left, c->desktop, &c->strut);
1353         if (c->strut.top)
1354             ADD_STRUT_TO_LIST(struts_top, c->desktop, &c->strut);
1355         if (c->strut.right)
1356             ADD_STRUT_TO_LIST(struts_right, c->desktop, &c->strut);
1357         if (c->strut.bottom)
1358             ADD_STRUT_TO_LIST(struts_bottom, c->desktop, &c->strut);
1359     }
1360     if (dock_strut.left)
1361         ADD_STRUT_TO_LIST(struts_left, DESKTOP_ALL, &dock_strut);
1362     if (dock_strut.top)
1363         ADD_STRUT_TO_LIST(struts_top, DESKTOP_ALL, &dock_strut);
1364     if (dock_strut.right)
1365         ADD_STRUT_TO_LIST(struts_right, DESKTOP_ALL, &dock_strut);
1366     if (dock_strut.bottom)
1367         ADD_STRUT_TO_LIST(struts_bottom, DESKTOP_ALL, &dock_strut);
1368
1369     if (config_margins.left)
1370         ADD_STRUT_TO_LIST(struts_left, DESKTOP_ALL, &config_margins);
1371     if (config_margins.top)
1372         ADD_STRUT_TO_LIST(struts_top, DESKTOP_ALL, &config_margins);
1373     if (config_margins.right)
1374         ADD_STRUT_TO_LIST(struts_right, DESKTOP_ALL, &config_margins);
1375     if (config_margins.bottom)
1376         ADD_STRUT_TO_LIST(struts_bottom, DESKTOP_ALL, &config_margins);
1377
1378     VALIDATE_STRUTS(struts_left, left,
1379                     monitor_area[screen_num_monitors].width / 2);
1380     VALIDATE_STRUTS(struts_right, right,
1381                     monitor_area[screen_num_monitors].width / 2);
1382     VALIDATE_STRUTS(struts_top, top,
1383                     monitor_area[screen_num_monitors].height / 2);
1384     VALIDATE_STRUTS(struts_bottom, bottom,
1385                     monitor_area[screen_num_monitors].height / 2);
1386
1387     dims = g_new(gulong, 4 * screen_num_desktops);
1388     for (i = 0; i < screen_num_desktops; ++i) {
1389         Rect *area = screen_area(i, SCREEN_AREA_ALL_MONITORS, NULL);
1390         dims[i*4+0] = area->x;
1391         dims[i*4+1] = area->y;
1392         dims[i*4+2] = area->width;
1393         dims[i*4+3] = area->height;
1394         g_free(area);
1395     }
1396
1397     /* set the legacy workarea hint to the union of all the monitors */
1398     PROP_SETA32(RootWindow(ob_display, ob_screen), net_workarea, cardinal,
1399                 dims, 4 * screen_num_desktops);
1400
1401     /* the area has changed, adjust all the windows if they need it */
1402     for (it = client_list; it; it = g_list_next(it))
1403         client_reconfigure(it->data, FALSE);
1404
1405     g_free(dims);
1406 }
1407
1408 #if 0
1409 Rect* screen_area_all_monitors(guint desktop)
1410 {
1411     guint i;
1412     Rect *a;
1413
1414     a = screen_area_monitor(desktop, 0);
1415
1416     /* combine all the monitors together */
1417     for (i = 1; i < screen_num_monitors; ++i) {
1418         Rect *m = screen_area_monitor(desktop, i);
1419         gint l, r, t, b;
1420
1421         l = MIN(RECT_LEFT(*a), RECT_LEFT(*m));
1422         t = MIN(RECT_TOP(*a), RECT_TOP(*m));
1423         r = MAX(RECT_RIGHT(*a), RECT_RIGHT(*m));
1424         b = MAX(RECT_BOTTOM(*a), RECT_BOTTOM(*m));
1425
1426         RECT_SET(*a, l, t, r - l + 1, b - t + 1);
1427
1428         g_free(m);
1429     }
1430
1431     return a;
1432 }
1433 #endif
1434
1435 #define STRUT_LEFT_IN_SEARCH(s, search) \
1436     (RANGES_INTERSECT(search->y, search->height, \
1437                       s->left_start, s->left_end - s->left_start + 1))
1438 #define STRUT_RIGHT_IN_SEARCH(s, search) \
1439     (RANGES_INTERSECT(search->y, search->height, \
1440                       s->right_start, s->right_end - s->right_start + 1))
1441 #define STRUT_TOP_IN_SEARCH(s, search) \
1442     (RANGES_INTERSECT(search->x, search->width, \
1443                       s->top_start, s->top_end - s->top_start + 1))
1444 #define STRUT_BOTTOM_IN_SEARCH(s, search) \
1445     (RANGES_INTERSECT(search->x, search->width, \
1446                       s->bottom_start, s->bottom_end - s->bottom_start + 1))
1447
1448 #define STRUT_LEFT_IGNORE(s, us, search) \
1449     (head == SCREEN_AREA_ALL_MONITORS && us && \
1450      RECT_LEFT(monitor_area[i]) + s->left > RECT_LEFT(*search))
1451 #define STRUT_RIGHT_IGNORE(s, us, search) \
1452     (head == SCREEN_AREA_ALL_MONITORS && us && \
1453      RECT_RIGHT(monitor_area[i]) - s->right < RECT_RIGHT(*search))
1454 #define STRUT_TOP_IGNORE(s, us, search) \
1455     (head == SCREEN_AREA_ALL_MONITORS && us && \
1456      RECT_TOP(monitor_area[i]) + s->top > RECT_TOP(*search))
1457 #define STRUT_BOTTOM_IGNORE(s, us, search) \
1458     (head == SCREEN_AREA_ALL_MONITORS && us && \
1459      RECT_BOTTOM(monitor_area[i]) - s->bottom < RECT_BOTTOM(*search))
1460
1461 Rect* screen_area(guint desktop, guint head, Rect *search)
1462 {
1463     Rect *a;
1464     GSList *it;
1465     gint l, r, t, b, al, ar, at, ab;
1466     guint i, d;
1467     gboolean us = search != NULL; /* user provided search */
1468
1469     g_assert(desktop < screen_num_desktops || desktop == DESKTOP_ALL);
1470     g_assert(head < screen_num_monitors || head == SCREEN_AREA_ONE_MONITOR ||
1471              head == SCREEN_AREA_ALL_MONITORS);
1472     g_assert(!(head == SCREEN_AREA_ONE_MONITOR && search == NULL));
1473
1474     /* find any struts for this monitor
1475        which will be affecting the search area.
1476     */
1477
1478     /* search everything if search is null */
1479     if (!search) {
1480         if (head < screen_num_monitors) search = &monitor_area[head];
1481         else search = &monitor_area[screen_num_monitors];
1482     }
1483     if (head == SCREEN_AREA_ONE_MONITOR) head = screen_find_monitor(search);
1484
1485     /* al is "all left" meaning the furthest left you can get, l is our
1486        "working left" meaning our current strut edge which we're calculating
1487     */
1488
1489     /* only include monitors which the search area lines up with */
1490     if (RECT_INTERSECTS_RECT(monitor_area[screen_num_monitors], *search)) {
1491         al = l = RECT_RIGHT(monitor_area[screen_num_monitors]);
1492         at = t = RECT_BOTTOM(monitor_area[screen_num_monitors]);
1493         ar = r = RECT_LEFT(monitor_area[screen_num_monitors]);
1494         ab = b = RECT_TOP(monitor_area[screen_num_monitors]);
1495         for (i = 0; i < screen_num_monitors; ++i) {
1496             /* add the monitor if applicable */
1497             if (RANGES_INTERSECT(search->x, search->width,
1498                                  monitor_area[i].x, monitor_area[i].width))
1499             {
1500                 at = t = MIN(t, RECT_TOP(monitor_area[i]));
1501                 ab = b = MAX(b, RECT_BOTTOM(monitor_area[i]));
1502             }
1503             if (RANGES_INTERSECT(search->y, search->height,
1504                                  monitor_area[i].y, monitor_area[i].height))
1505             {
1506                 al = l = MIN(l, RECT_LEFT(monitor_area[i]));
1507                 ar = r = MAX(r, RECT_RIGHT(monitor_area[i]));
1508             }
1509         }
1510     } else {
1511         al = l = RECT_LEFT(monitor_area[screen_num_monitors]);
1512         at = t = RECT_TOP(monitor_area[screen_num_monitors]);
1513         ar = r = RECT_RIGHT(monitor_area[screen_num_monitors]);
1514         ab = b = RECT_BOTTOM(monitor_area[screen_num_monitors]);
1515     }
1516
1517     for (d = 0; d < screen_num_desktops; ++d) {
1518         if (d != desktop && desktop != DESKTOP_ALL) continue;
1519
1520         for (i = 0; i < screen_num_monitors; ++i) {
1521             if (head != SCREEN_AREA_ALL_MONITORS && head != i) continue;
1522
1523             for (it = struts_left; it; it = g_slist_next(it)) {
1524                 ObScreenStrut *s = it->data;
1525                 if ((s->desktop == d || s->desktop == DESKTOP_ALL) &&
1526                     STRUT_LEFT_IN_SEARCH(s->strut, search) &&
1527                     !STRUT_LEFT_IGNORE(s->strut, us, search))
1528                     l = MAX(l, RECT_LEFT(monitor_area[screen_num_monitors])
1529                                + s->strut->left);
1530             }
1531             for (it = struts_top; it; it = g_slist_next(it)) {
1532                 ObScreenStrut *s = it->data;
1533                 if ((s->desktop == d || s->desktop == DESKTOP_ALL) &&
1534                     STRUT_TOP_IN_SEARCH(s->strut, search) &&
1535                     !STRUT_TOP_IGNORE(s->strut, us, search))
1536                     t = MAX(t, RECT_TOP(monitor_area[screen_num_monitors])
1537                                + s->strut->top);
1538             }
1539             for (it = struts_right; it; it = g_slist_next(it)) {
1540                 ObScreenStrut *s = it->data;
1541                 if ((s->desktop == d || s->desktop == DESKTOP_ALL) &&
1542                     STRUT_RIGHT_IN_SEARCH(s->strut, search) &&
1543                     !STRUT_RIGHT_IGNORE(s->strut, us, search))
1544                     r = MIN(r, RECT_RIGHT(monitor_area[screen_num_monitors])
1545                                - s->strut->right);
1546             }
1547             for (it = struts_bottom; it; it = g_slist_next(it)) {
1548                 ObScreenStrut *s = it->data;
1549                 if ((s->desktop == d || s->desktop == DESKTOP_ALL) &&
1550                     STRUT_BOTTOM_IN_SEARCH(s->strut, search) &&
1551                     !STRUT_BOTTOM_IGNORE(s->strut, us, search))
1552                     b = MIN(b, RECT_BOTTOM(monitor_area[screen_num_monitors])
1553                                - s->strut->bottom);
1554             }
1555
1556             /* limit to this monitor */
1557             if (head == i) {
1558                 l = MAX(l, RECT_LEFT(monitor_area[i]));
1559                 t = MAX(t, RECT_TOP(monitor_area[i]));
1560                 r = MIN(r, RECT_RIGHT(monitor_area[i]));
1561                 b = MIN(b, RECT_BOTTOM(monitor_area[i]));
1562             }
1563         }
1564     }
1565
1566     a = g_new(Rect, 1);
1567     a->x = l;
1568     a->y = t;
1569     a->width = r - l + 1;
1570     a->height = b - t + 1;
1571     return a;
1572 }
1573
1574 guint screen_find_monitor(Rect *search)
1575 {
1576     guint i;
1577     guint most = screen_num_monitors;
1578     guint mostv = 0;
1579
1580     for (i = 0; i < screen_num_monitors; ++i) {
1581         Rect *area = screen_physical_area_monitor(i);
1582         if (RECT_INTERSECTS_RECT(*area, *search)) {
1583             Rect r;
1584             guint v;
1585
1586             RECT_SET_INTERSECTION(r, *area, *search);
1587             v = r.width * r.height;
1588
1589             if (v > mostv) {
1590                 mostv = v;
1591                 most = i;
1592             }
1593         }
1594         g_free(area);
1595     }
1596     return most;
1597 }
1598
1599 Rect* screen_physical_area_all_monitors(void)
1600 {
1601     return screen_physical_area_monitor(screen_num_monitors);
1602 }
1603
1604 Rect* screen_physical_area_monitor(guint head)
1605 {
1606     Rect *a;
1607     g_assert(head <= screen_num_monitors);
1608
1609     a = g_new(Rect, 1);
1610     *a = monitor_area[head];
1611     return a;
1612 }
1613
1614 gboolean screen_physical_area_monitor_contains(guint head, Rect *search)
1615 {
1616     g_assert(head <= screen_num_monitors);
1617     g_assert(search);
1618     return RECT_INTERSECTS_RECT(monitor_area[head], *search);
1619 }
1620
1621 guint screen_monitor_active(void)
1622 {
1623     if (moveresize_client)
1624         return client_monitor(moveresize_client);
1625     else if (focus_client)
1626         return client_monitor(focus_client);
1627     else
1628         return screen_monitor_pointer();
1629 }
1630
1631 Rect* screen_physical_area_active(void)
1632 {
1633     return screen_physical_area_monitor(screen_monitor_active());
1634 }
1635
1636 guint screen_monitor_primary(gboolean fixed)
1637 {
1638     if (config_primary_monitor_index > 0) {
1639         if (config_primary_monitor_index-1 < screen_num_monitors)
1640             return config_primary_monitor_index - 1;
1641         else
1642             return 0;
1643     }
1644     else if (fixed)
1645         return 0;
1646     else if (config_primary_monitor == OB_PLACE_MONITOR_ACTIVE)
1647         return screen_monitor_active();
1648     else /* config_primary_monitor == OB_PLACE_MONITOR_MOUSE */
1649         return screen_monitor_pointer();
1650 }
1651
1652 Rect *screen_physical_area_primary(gboolean fixed)
1653 {
1654     return screen_physical_area_monitor(screen_monitor_primary(fixed));
1655 }
1656
1657 void screen_set_root_cursor(void)
1658 {
1659     if (sn_app_starting())
1660         XDefineCursor(ob_display, RootWindow(ob_display, ob_screen),
1661                       ob_cursor(OB_CURSOR_BUSYPOINTER));
1662     else
1663         XDefineCursor(ob_display, RootWindow(ob_display, ob_screen),
1664                       ob_cursor(OB_CURSOR_POINTER));
1665 }
1666
1667 guint screen_find_monitor_point(guint x, guint y)
1668 {
1669     Rect mon;
1670     RECT_SET(mon, x, y, 1, 1);
1671     return screen_find_monitor(&mon);
1672 }
1673
1674 guint screen_monitor_pointer()
1675 {
1676     gint x, y;
1677     if (!screen_pointer_pos(&x, &y))
1678         x = y = 0;
1679     return screen_find_monitor_point(x, y);
1680 }
1681
1682 gboolean screen_pointer_pos(gint *x, gint *y)
1683 {
1684     Window w;
1685     gint i;
1686     guint u;
1687     gboolean ret;
1688
1689     ret = !!XQueryPointer(ob_display, RootWindow(ob_display, ob_screen),
1690                           &w, &w, x, y, &i, &i, &u);
1691     if (!ret) {
1692         for (i = 0; i < ScreenCount(ob_display); ++i)
1693             if (i != ob_screen)
1694                 if (XQueryPointer(ob_display, RootWindow(ob_display, i),
1695                                   &w, &w, x, y, &i, &i, &u))
1696                     break;
1697     }
1698     return ret;
1699 }