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