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