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