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