add support for _NET_WM_ACTION_ABOVE and _NET_WM_ACTION_BELOW which aren't in the...
[mikachu/openbox.git] / openbox / screen.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    screen.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "debug.h"
21 #include "openbox.h"
22 #include "dock.h"
23 #include "xerror.h"
24 #include "prop.h"
25 #include "grab.h"
26 #include "startupnotify.h"
27 #include "moveresize.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "client.h"
31 #include "session.h"
32 #include "frame.h"
33 #include "event.h"
34 #include "focus.h"
35 #include "popup.h"
36 #include "extensions.h"
37 #include "render/render.h"
38 #include "gettext.h"
39
40 #include <X11/Xlib.h>
41 #ifdef HAVE_UNISTD_H
42 #  include <sys/types.h>
43 #  include <unistd.h>
44 #endif
45 #include <assert.h>
46
47 /*! The event mask to grab on the root window */
48 #define ROOT_EVENTMASK (StructureNotifyMask | PropertyChangeMask | \
49                         EnterWindowMask | LeaveWindowMask | \
50                         SubstructureRedirectMask | FocusChangeMask | \
51                         ButtonPressMask | ButtonReleaseMask | ButtonMotionMask)
52
53 guint    screen_num_desktops;
54 guint    screen_num_monitors;
55 guint    screen_desktop;
56 guint    screen_last_desktop;
57 Size     screen_physical_size;
58 gboolean screen_showing_desktop;
59 DesktopLayout screen_desktop_layout;
60 gchar  **screen_desktop_names;
61 Window   screen_support_win;
62 Time     screen_desktop_user_time = CurrentTime;
63
64 static Rect  **area; /* array of desktop holding array of xinerama areas */
65 static Rect  *monitor_area;
66
67 static ObPagerPopup *desktop_cycle_popup;
68
69 static gboolean replace_wm()
70 {
71     gchar *wm_sn;
72     Atom wm_sn_atom;
73     Window current_wm_sn_owner;
74     Time timestamp;
75
76     wm_sn = g_strdup_printf("WM_S%d", ob_screen);
77     wm_sn_atom = XInternAtom(ob_display, wm_sn, FALSE);
78     g_free(wm_sn);
79
80     current_wm_sn_owner = XGetSelectionOwner(ob_display, wm_sn_atom);
81     if (current_wm_sn_owner == screen_support_win)
82         current_wm_sn_owner = None;
83     if (current_wm_sn_owner) {
84         if (!ob_replace_wm) {
85             g_message(_("A window manager is already running on screen %d"),
86                       ob_screen);
87             return FALSE;
88         }
89         xerror_set_ignore(TRUE);
90         xerror_occured = FALSE;
91
92         /* We want to find out when the current selection owner dies */
93         XSelectInput(ob_display, current_wm_sn_owner, StructureNotifyMask);
94         XSync(ob_display, FALSE);
95
96         xerror_set_ignore(FALSE);
97         if (xerror_occured)
98             current_wm_sn_owner = None;
99     }
100
101     {
102         /* Generate a timestamp */
103         XEvent event;
104
105         XSelectInput(ob_display, screen_support_win, PropertyChangeMask);
106
107         XChangeProperty(ob_display, screen_support_win,
108                         prop_atoms.wm_class, prop_atoms.string,
109                         8, PropModeAppend, NULL, 0);
110         XWindowEvent(ob_display, screen_support_win,
111                      PropertyChangeMask, &event);
112
113         XSelectInput(ob_display, screen_support_win, NoEventMask);
114
115         timestamp = event.xproperty.time;
116     }
117
118     XSetSelectionOwner(ob_display, wm_sn_atom, screen_support_win,
119                        timestamp);
120
121     if (XGetSelectionOwner(ob_display, wm_sn_atom) != screen_support_win) {
122         g_message(_("Could not acquire window manager selection on screen %d"),
123                   ob_screen);
124         return FALSE;
125     }
126
127     /* Wait for old window manager to go away */
128     if (current_wm_sn_owner) {
129       XEvent event;
130       gulong wait = 0;
131       const gulong timeout = G_USEC_PER_SEC * 15; /* wait for 15s max */
132
133       while (wait < timeout) {
134           if (XCheckWindowEvent(ob_display, current_wm_sn_owner,
135                                 StructureNotifyMask, &event) &&
136               event.type == DestroyNotify)
137               break;
138           g_usleep(G_USEC_PER_SEC / 10);
139           wait += G_USEC_PER_SEC / 10;
140       }
141
142       if (wait >= timeout) {
143           g_message(_("The WM on screen %d is not exiting"), ob_screen);
144           return FALSE;
145       }
146     }
147
148     /* Send client message indicating that we are now the WM */
149     prop_message(RootWindow(ob_display, ob_screen), prop_atoms.manager,
150                  timestamp, wm_sn_atom, screen_support_win, 0,
151                  SubstructureNotifyMask);
152
153     return TRUE;
154 }
155
156 gboolean screen_annex(const gchar *program_name)
157 {
158     XSetWindowAttributes attrib;
159     pid_t pid;
160     gint i, num_support;
161     Atom *prop_atoms_start, *wm_supported_pos;
162     gulong *supported;
163
164     /* create the netwm support window */
165     attrib.override_redirect = TRUE;
166     screen_support_win = XCreateWindow(ob_display,
167                                        RootWindow(ob_display, ob_screen),
168                                        -100, -100, 1, 1, 0,
169                                        CopyFromParent, InputOutput,
170                                        CopyFromParent,
171                                        CWOverrideRedirect, &attrib);
172     XMapWindow(ob_display, screen_support_win);
173     XLowerWindow(ob_display, screen_support_win);
174
175     if (!replace_wm()) {
176         XDestroyWindow(ob_display, screen_support_win);
177         return FALSE;
178     }
179
180     xerror_set_ignore(TRUE);
181     xerror_occured = FALSE;
182     XSelectInput(ob_display, RootWindow(ob_display, ob_screen),
183                  ROOT_EVENTMASK);
184     xerror_set_ignore(FALSE);
185     if (xerror_occured) {
186         g_message(_("A window manager is already running on screen %d"),
187                   ob_screen);
188
189         XDestroyWindow(ob_display, screen_support_win);
190         return FALSE;
191     }
192
193
194     screen_set_root_cursor();
195
196     /* set the OPENBOX_PID hint */
197     pid = getpid();
198     PROP_SET32(RootWindow(ob_display, ob_screen),
199                openbox_pid, cardinal, pid);
200
201     /* set supporting window */
202     PROP_SET32(RootWindow(ob_display, ob_screen),
203                net_supporting_wm_check, window, screen_support_win);
204
205     /* set properties on the supporting window */
206     PROP_SETS(screen_support_win, net_wm_name, program_name);
207     PROP_SET32(screen_support_win, net_supporting_wm_check,
208                window, screen_support_win);
209
210     /* set the _NET_SUPPORTED_ATOMS hint */
211
212     /* this is all the atoms after net_supported in the prop_atoms struct */
213     prop_atoms_start = (Atom*)&prop_atoms;
214     wm_supported_pos = (Atom*)&(prop_atoms.net_supported);
215     num_support = sizeof(prop_atoms) / sizeof(Atom) -
216         (wm_supported_pos - prop_atoms_start) - 1;
217     i = 0;
218     supported = g_new(gulong, num_support);
219     supported[i++] = prop_atoms.net_supporting_wm_check;
220     supported[i++] = prop_atoms.net_wm_full_placement;
221     supported[i++] = prop_atoms.net_current_desktop;
222     supported[i++] = prop_atoms.net_number_of_desktops;
223     supported[i++] = prop_atoms.net_desktop_geometry;
224     supported[i++] = prop_atoms.net_desktop_viewport;
225     supported[i++] = prop_atoms.net_active_window;
226     supported[i++] = prop_atoms.net_workarea;
227     supported[i++] = prop_atoms.net_client_list;
228     supported[i++] = prop_atoms.net_client_list_stacking;
229     supported[i++] = prop_atoms.net_desktop_names;
230     supported[i++] = prop_atoms.net_close_window;
231     supported[i++] = prop_atoms.net_desktop_layout;
232     supported[i++] = prop_atoms.net_showing_desktop;
233     supported[i++] = prop_atoms.net_wm_name;
234     supported[i++] = prop_atoms.net_wm_visible_name;
235     supported[i++] = prop_atoms.net_wm_icon_name;
236     supported[i++] = prop_atoms.net_wm_visible_icon_name;
237     supported[i++] = prop_atoms.net_wm_desktop;
238     supported[i++] = prop_atoms.net_wm_strut;
239     supported[i++] = prop_atoms.net_wm_strut_partial;
240     supported[i++] = prop_atoms.net_wm_icon;
241     supported[i++] = prop_atoms.net_wm_icon_geometry;
242     supported[i++] = prop_atoms.net_wm_window_type;
243     supported[i++] = prop_atoms.net_wm_window_type_desktop;
244     supported[i++] = prop_atoms.net_wm_window_type_dock;
245     supported[i++] = prop_atoms.net_wm_window_type_toolbar;
246     supported[i++] = prop_atoms.net_wm_window_type_menu;
247     supported[i++] = prop_atoms.net_wm_window_type_utility;
248     supported[i++] = prop_atoms.net_wm_window_type_splash;
249     supported[i++] = prop_atoms.net_wm_window_type_dialog;
250     supported[i++] = prop_atoms.net_wm_window_type_normal;
251     supported[i++] = prop_atoms.net_wm_allowed_actions;
252     supported[i++] = prop_atoms.net_wm_action_move;
253     supported[i++] = prop_atoms.net_wm_action_resize;
254     supported[i++] = prop_atoms.net_wm_action_minimize;
255     supported[i++] = prop_atoms.net_wm_action_shade;
256     supported[i++] = prop_atoms.net_wm_action_maximize_horz;
257     supported[i++] = prop_atoms.net_wm_action_maximize_vert;
258     supported[i++] = prop_atoms.net_wm_action_fullscreen;
259     supported[i++] = prop_atoms.net_wm_action_change_desktop;
260     supported[i++] = prop_atoms.net_wm_action_close;
261     supported[i++] = prop_atoms.net_wm_action_above;
262     supported[i++] = prop_atoms.net_wm_action_below;
263     supported[i++] = prop_atoms.net_wm_state;
264     supported[i++] = prop_atoms.net_wm_state_modal;
265     supported[i++] = prop_atoms.net_wm_state_maximized_vert;
266     supported[i++] = prop_atoms.net_wm_state_maximized_horz;
267     supported[i++] = prop_atoms.net_wm_state_shaded;
268     supported[i++] = prop_atoms.net_wm_state_skip_taskbar;
269     supported[i++] = prop_atoms.net_wm_state_skip_pager;
270     supported[i++] = prop_atoms.net_wm_state_hidden;
271     supported[i++] = prop_atoms.net_wm_state_fullscreen;
272     supported[i++] = prop_atoms.net_wm_state_above;
273     supported[i++] = prop_atoms.net_wm_state_below;
274     supported[i++] = prop_atoms.net_wm_state_demands_attention;
275     supported[i++] = prop_atoms.net_moveresize_window;
276     supported[i++] = prop_atoms.net_wm_moveresize;
277     supported[i++] = prop_atoms.net_wm_user_time;
278     supported[i++] = prop_atoms.net_wm_user_time_window;
279     supported[i++] = prop_atoms.net_frame_extents;
280     supported[i++] = prop_atoms.net_request_frame_extents;
281     supported[i++] = prop_atoms.net_restack_window;
282     supported[i++] = prop_atoms.net_startup_id;
283 #ifdef SYNC
284     supported[i++] = prop_atoms.net_wm_sync_request;
285     supported[i++] = prop_atoms.net_wm_sync_request_counter;
286 #endif
287
288     supported[i++] = prop_atoms.kde_wm_change_state;
289     supported[i++] = prop_atoms.kde_net_wm_frame_strut;
290     supported[i++] = prop_atoms.kde_net_wm_window_type_override;
291
292     supported[i++] = prop_atoms.openbox_wm_state_undecorated;
293     supported[i++] = prop_atoms.openbox_pid;
294     supported[i++] = prop_atoms.openbox_config;
295     supported[i++] = prop_atoms.openbox_control;
296     g_assert(i == num_support);
297
298     PROP_SETA32(RootWindow(ob_display, ob_screen),
299                 net_supported, atom, supported, num_support);
300     g_free(supported);
301
302     return TRUE;
303 }
304
305 void screen_startup(gboolean reconfig)
306 {
307     guint i, numnames;
308     gchar **names;
309     GSList *it;
310     guint32 d;
311
312     desktop_cycle_popup = pager_popup_new(FALSE);
313     pager_popup_height(desktop_cycle_popup, POPUP_HEIGHT);
314
315     if (reconfig)
316         return;
317
318     /* get the initial size */
319     screen_resize();
320
321     /* get the desktop names */
322     numnames = g_slist_length(config_desktops_names);
323     names = g_new(gchar*, numnames + 1);
324     names[numnames] = NULL;
325     for (i = 0, it = config_desktops_names; it; ++i, it = g_slist_next(it))
326         names[i] = g_strdup(it->data);
327
328     /* set the root window property */
329     PROP_SETSS(RootWindow(ob_display, ob_screen), net_desktop_names,names);
330
331     g_strfreev(names);
332
333     /* set the number of desktops */
334     screen_num_desktops = 0;
335     screen_set_num_desktops(config_desktops_num);
336
337     /* start on the current desktop when a wm was already running */
338     if (PROP_GET32(RootWindow(ob_display, ob_screen),
339                    net_current_desktop, cardinal, &d) &&
340         d < screen_num_desktops)
341     {
342         screen_set_desktop(d, FALSE);
343     } else if (session_desktop >= 0)
344         screen_set_desktop(MIN((guint)session_desktop,
345                                screen_num_desktops), FALSE);
346     else
347         screen_set_desktop(MIN(config_screen_firstdesk,
348                                screen_num_desktops) - 1, FALSE);
349
350     /* don't start in showing-desktop mode */
351     screen_showing_desktop = FALSE;
352     PROP_SET32(RootWindow(ob_display, ob_screen),
353                net_showing_desktop, cardinal, screen_showing_desktop);
354
355     screen_update_layout();
356 }
357
358 void screen_shutdown(gboolean reconfig)
359 {
360     Rect **r;
361
362     pager_popup_free(desktop_cycle_popup);
363
364     if (reconfig)
365         return;
366
367     XSelectInput(ob_display, RootWindow(ob_display, ob_screen),
368                  NoEventMask);
369
370     /* we're not running here no more! */
371     PROP_ERASE(RootWindow(ob_display, ob_screen), openbox_pid);
372     /* not without us */
373     PROP_ERASE(RootWindow(ob_display, ob_screen), net_supported);
374     /* don't keep this mode */
375     PROP_ERASE(RootWindow(ob_display, ob_screen), net_showing_desktop);
376
377     XDestroyWindow(ob_display, screen_support_win);
378
379     g_strfreev(screen_desktop_names);
380     screen_desktop_names = NULL;
381
382     for (r = area; *r; ++r)
383         g_free(*r);
384     g_free(area);
385     area = NULL;
386 }
387
388 void screen_resize()
389 {
390     static gint oldw = 0, oldh = 0;
391     gint w, h;
392     GList *it;
393     gulong geometry[2];
394
395     w = WidthOfScreen(ScreenOfDisplay(ob_display, ob_screen));
396     h = HeightOfScreen(ScreenOfDisplay(ob_display, ob_screen));
397
398     if (w == oldw && h == oldh) return;
399
400     oldw = w; oldh = h;
401
402     /* Set the _NET_DESKTOP_GEOMETRY hint */
403     screen_physical_size.width = geometry[0] = w;
404     screen_physical_size.height = geometry[1] = h;
405     PROP_SETA32(RootWindow(ob_display, ob_screen),
406                 net_desktop_geometry, cardinal, geometry, 2);
407
408     if (ob_state() == OB_STATE_STARTING)
409         return;
410
411     screen_update_areas();
412     dock_configure();
413
414     for (it = client_list; it; it = g_list_next(it))
415         client_move_onscreen(it->data, FALSE);
416 }
417
418 void screen_set_num_desktops(guint num)
419 {
420     guint old;
421     gulong *viewport;
422     GList *it;
423
424     g_assert(num > 0);
425
426     if (screen_num_desktops == num) return;
427
428     old = screen_num_desktops;
429     screen_num_desktops = num;
430     PROP_SET32(RootWindow(ob_display, ob_screen),
431                net_number_of_desktops, cardinal, num);
432
433     /* set the viewport hint */
434     viewport = g_new0(gulong, num * 2);
435     PROP_SETA32(RootWindow(ob_display, ob_screen),
436                 net_desktop_viewport, cardinal, viewport, num * 2);
437     g_free(viewport);
438
439     /* the number of rows/columns will differ */
440     screen_update_layout();
441
442     /* move windows on desktops that will no longer exist! */
443     for (it = client_list; it; it = g_list_next(it)) {
444         ObClient *c = it->data;
445         if (c->desktop >= num && c->desktop != DESKTOP_ALL)
446             client_set_desktop(c, num - 1, FALSE);
447     }
448  
449     /* change our struts/area to match (after moving windows) */
450     screen_update_areas();
451
452     /* may be some unnamed desktops that we need to fill in with names
453      (after updating the areas so the popup can resize) */
454     screen_update_desktop_names();
455
456     /* change our desktop if we're on one that no longer exists! */
457     if (screen_desktop >= screen_num_desktops)
458         screen_set_desktop(num - 1, TRUE);
459 }
460
461 void screen_set_desktop(guint num, gboolean dofocus)
462 {
463     ObClient *c;
464     GList *it;
465     guint old;
466      
467     g_assert(num < screen_num_desktops);
468
469     old = screen_desktop;
470     screen_desktop = num;
471     PROP_SET32(RootWindow(ob_display, ob_screen),
472                net_current_desktop, cardinal, num);
473
474     if (old == num) return;
475
476     screen_last_desktop = old;
477
478     ob_debug("Moving to desktop %d\n", num+1);
479
480     if (moveresize_client)
481         client_set_desktop(moveresize_client, num, TRUE);
482
483     /* show windows before hiding the rest to lessen the enter/leave events */
484
485     /* show windows from top to bottom */
486     for (it = stacking_list; it; it = g_list_next(it)) {
487         if (WINDOW_IS_CLIENT(it->data)) {
488             ObClient *c = it->data;
489             client_show(c);
490         }
491     }
492
493     /* have to try focus here because when you leave an empty desktop
494        there is no focus out to watch for
495
496        do this before hiding the windows so if helper windows are coming
497        with us, they don't get hidden
498     */
499     if (dofocus && (c = focus_fallback(TRUE))) {
500         /* only do the flicker reducing stuff ahead of time if we are going
501            to call xsetinputfocus on the window ourselves. otherwise there is
502            no guarantee the window will actually take focus.. */
503         if (c->can_focus) {
504             /* do this here so that if you switch desktops to a window with
505                helper windows then the helper windows won't flash */
506             client_bring_helper_windows(c);
507             /* reduce flicker by hiliting now rather than waiting for the
508                server FocusIn event */
509             frame_adjust_focus(c->frame, TRUE);
510         }
511     }
512
513     /* hide windows from bottom to top */
514     for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
515         if (WINDOW_IS_CLIENT(it->data)) {
516             ObClient *c = it->data;
517             client_hide(c);
518         }
519     }
520
521     event_ignore_queued_enters();
522
523     if (event_curtime != CurrentTime)
524         screen_desktop_user_time = event_curtime;
525 }
526
527 static void get_row_col(guint d, guint *r, guint *c)
528 {
529     switch (screen_desktop_layout.orientation) {
530     case OB_ORIENTATION_HORZ:
531         switch (screen_desktop_layout.start_corner) {
532         case OB_CORNER_TOPLEFT:
533             *r = d / screen_desktop_layout.columns;
534             *c = d % screen_desktop_layout.columns;
535             break;
536         case OB_CORNER_BOTTOMLEFT:
537             *r = screen_desktop_layout.rows - 1 -
538                 d / screen_desktop_layout.columns;
539             *c = d % screen_desktop_layout.columns;
540             break;
541         case OB_CORNER_TOPRIGHT:
542             *r = d / screen_desktop_layout.columns;
543             *c = screen_desktop_layout.columns - 1 -
544                 d % screen_desktop_layout.columns;
545             break;
546         case OB_CORNER_BOTTOMRIGHT:
547             *r = screen_desktop_layout.rows - 1 -
548                 d / screen_desktop_layout.columns;
549             *c = screen_desktop_layout.columns - 1 -
550                 d % screen_desktop_layout.columns;
551             break;
552         }
553         break;
554     case OB_ORIENTATION_VERT:
555         switch (screen_desktop_layout.start_corner) {
556         case OB_CORNER_TOPLEFT:
557             *r = d % screen_desktop_layout.rows;
558             *c = d / screen_desktop_layout.rows;
559             break;
560         case OB_CORNER_BOTTOMLEFT:
561             *r = screen_desktop_layout.rows - 1 -
562                 d % screen_desktop_layout.rows;
563             *c = d / screen_desktop_layout.rows;
564             break;
565         case OB_CORNER_TOPRIGHT:
566             *r = d % screen_desktop_layout.rows;
567             *c = screen_desktop_layout.columns - 1 -
568                 d / screen_desktop_layout.rows;
569             break;
570         case OB_CORNER_BOTTOMRIGHT:
571             *r = screen_desktop_layout.rows - 1 -
572                 d % screen_desktop_layout.rows;
573             *c = screen_desktop_layout.columns - 1 -
574                 d / screen_desktop_layout.rows;
575             break;
576         }
577         break;
578     }
579 }
580
581 static guint translate_row_col(guint r, guint c)
582 {
583     switch (screen_desktop_layout.orientation) {
584     case OB_ORIENTATION_HORZ:
585         switch (screen_desktop_layout.start_corner) {
586         case OB_CORNER_TOPLEFT:
587             return r % screen_desktop_layout.rows *
588                 screen_desktop_layout.columns +
589                 c % screen_desktop_layout.columns;
590         case OB_CORNER_BOTTOMLEFT:
591             return (screen_desktop_layout.rows - 1 -
592                     r % screen_desktop_layout.rows) *
593                 screen_desktop_layout.columns +
594                 c % screen_desktop_layout.columns;
595         case OB_CORNER_TOPRIGHT:
596             return r % screen_desktop_layout.rows *
597                 screen_desktop_layout.columns +
598                 (screen_desktop_layout.columns - 1 -
599                  c % screen_desktop_layout.columns);
600         case OB_CORNER_BOTTOMRIGHT:
601             return (screen_desktop_layout.rows - 1 -
602                     r % screen_desktop_layout.rows) *
603                 screen_desktop_layout.columns +
604                 (screen_desktop_layout.columns - 1 -
605                  c % screen_desktop_layout.columns);
606         }
607     case OB_ORIENTATION_VERT:
608         switch (screen_desktop_layout.start_corner) {
609         case OB_CORNER_TOPLEFT:
610             return c % screen_desktop_layout.columns *
611                 screen_desktop_layout.rows +
612                 r % screen_desktop_layout.rows;
613         case OB_CORNER_BOTTOMLEFT:
614             return c % screen_desktop_layout.columns *
615                 screen_desktop_layout.rows +
616                 (screen_desktop_layout.rows - 1 -
617                  r % screen_desktop_layout.rows);
618         case OB_CORNER_TOPRIGHT:
619             return (screen_desktop_layout.columns - 1 -
620                     c % screen_desktop_layout.columns) *
621                 screen_desktop_layout.rows +
622                 r % screen_desktop_layout.rows;
623         case OB_CORNER_BOTTOMRIGHT:
624             return (screen_desktop_layout.columns - 1 -
625                     c % screen_desktop_layout.columns) *
626                 screen_desktop_layout.rows +
627                 (screen_desktop_layout.rows - 1 -
628                  r % screen_desktop_layout.rows);
629         }
630     }
631     g_assert_not_reached();
632     return 0;
633 }
634
635 void screen_desktop_popup(guint d, gboolean show)
636 {
637     Rect *a;
638
639     if (!show) {
640         pager_popup_hide(desktop_cycle_popup);
641     } else {
642         a = screen_physical_area_monitor(0);
643         pager_popup_position(desktop_cycle_popup, CenterGravity,
644                              a->x + a->width / 2, a->y + a->height / 2);
645         pager_popup_icon_size_multiplier(desktop_cycle_popup,
646                                          (screen_desktop_layout.columns /
647                                           screen_desktop_layout.rows) / 2,
648                                          (screen_desktop_layout.rows/
649                                           screen_desktop_layout.columns) / 2);
650         pager_popup_max_width(desktop_cycle_popup,
651                               MAX(a->width/3, POPUP_WIDTH));
652         pager_popup_show(desktop_cycle_popup, screen_desktop_names[d], d);
653     }
654 }
655
656 guint screen_cycle_desktop(ObDirection dir, gboolean wrap, gboolean linear,
657                            gboolean dialog, gboolean done, gboolean cancel)
658 {
659     guint r, c;
660     static guint d = (guint)-1;
661     guint ret;
662
663     if (d == (guint)-1)
664         d = screen_desktop;
665
666     if ((cancel || done) && dialog)
667         goto show_cycle_dialog;
668
669     get_row_col(d, &r, &c);
670
671     if (linear) {
672         switch (dir) {
673         case OB_DIRECTION_EAST:
674             if (d < screen_num_desktops - 1)
675                 ++d;
676             else if (wrap)
677                 d = 0;
678             break;
679         case OB_DIRECTION_WEST:
680             if (d > 0)
681                 --d;
682             else if (wrap)
683                 d = screen_num_desktops - 1;
684             break;
685         default:
686             assert(0);
687             return screen_desktop;
688         }
689     } else {
690         switch (dir) {
691         case OB_DIRECTION_EAST:
692             ++c;
693             if (c >= screen_desktop_layout.columns) {
694                 if (wrap) {
695                     c = 0;
696                 } else {
697                     d = screen_desktop;
698                     goto show_cycle_dialog;
699                 }
700             }
701             d = translate_row_col(r, c);
702             if (d >= screen_num_desktops) {
703                 if (wrap) {
704                     ++c;
705                 } else {
706                     d = screen_desktop;
707                     goto show_cycle_dialog;
708                 }
709             }
710             break;
711         case OB_DIRECTION_WEST:
712             --c;
713             if (c >= screen_desktop_layout.columns) {
714                 if (wrap) {
715                     c = screen_desktop_layout.columns - 1;
716                 } else {
717                     d = screen_desktop;
718                     goto show_cycle_dialog;
719                 }
720             }
721             d = translate_row_col(r, c);
722             if (d >= screen_num_desktops) {
723                 if (wrap) {
724                     --c;
725                 } else {
726                     d = screen_desktop;
727                     goto show_cycle_dialog;
728                 }
729             }
730             break;
731         case OB_DIRECTION_SOUTH:
732             ++r;
733             if (r >= screen_desktop_layout.rows) {
734                 if (wrap) {
735                     r = 0;
736                 } else {
737                     d = screen_desktop;
738                     goto show_cycle_dialog;
739                 }
740             }
741             d = translate_row_col(r, c);
742             if (d >= screen_num_desktops) {
743                 if (wrap) {
744                     ++r;
745                 } else {
746                     d = screen_desktop;
747                     goto show_cycle_dialog;
748                 }
749             }
750             break;
751         case OB_DIRECTION_NORTH:
752             --r;
753             if (r >= screen_desktop_layout.rows) {
754                 if (wrap) {
755                     r = screen_desktop_layout.rows - 1;
756                 } else {
757                     d = screen_desktop;
758                     goto show_cycle_dialog;
759                 }
760             }
761             d = translate_row_col(r, c);
762             if (d >= screen_num_desktops) {
763                 if (wrap) {
764                     --r;
765                 } else {
766                     d = screen_desktop;
767                     goto show_cycle_dialog;
768                 }
769             }
770             break;
771         default:
772             assert(0);
773             return d = screen_desktop;
774         }
775
776         d = translate_row_col(r, c);
777     }
778
779 show_cycle_dialog:
780     if (dialog && !cancel && !done) {
781         screen_desktop_popup(d, TRUE);
782     } else
783         screen_desktop_popup(0, FALSE);
784     ret = d;
785
786     if (!dialog || cancel || done)
787         d = (guint)-1;
788
789     return ret;
790 }
791
792 void screen_update_layout()
793 {
794     ObOrientation orient;
795     ObCorner corner;
796     guint rows;
797     guint cols;
798     guint32 *data;
799     guint num;
800     gboolean valid = FALSE;
801
802     if (PROP_GETA32(RootWindow(ob_display, ob_screen),
803                     net_desktop_layout, cardinal, &data, &num)) {
804         if (num == 3 || num == 4) {
805
806             if (data[0] == prop_atoms.net_wm_orientation_vert)
807                 orient = OB_ORIENTATION_VERT;
808             else if (data[0] == prop_atoms.net_wm_orientation_horz)
809                 orient = OB_ORIENTATION_HORZ;
810             else
811                 goto screen_update_layout_bail;
812
813             if (num < 4)
814                 corner = OB_CORNER_TOPLEFT;
815             else {
816                 if (data[3] == prop_atoms.net_wm_topleft)
817                     corner = OB_CORNER_TOPLEFT;
818                 else if (data[3] == prop_atoms.net_wm_topright)
819                     corner = OB_CORNER_TOPRIGHT;
820                 else if (data[3] == prop_atoms.net_wm_bottomright)
821                     corner = OB_CORNER_BOTTOMRIGHT;
822                 else if (data[3] == prop_atoms.net_wm_bottomleft)
823                     corner = OB_CORNER_BOTTOMLEFT;
824                 else
825                     goto screen_update_layout_bail;
826             }
827
828             cols = data[1];
829             rows = data[2];
830
831             /* fill in a zero rows/columns */
832             if ((cols == 0 && rows == 0)) { /* both 0's is bad data.. */
833                 goto screen_update_layout_bail;
834             } else {
835                 if (cols == 0) {
836                     cols = screen_num_desktops / rows;
837                     if (rows * cols < screen_num_desktops)
838                         cols++;
839                     if (rows * cols >= screen_num_desktops + cols)
840                         rows--;
841                 } else if (rows == 0) {
842                     rows = screen_num_desktops / cols;
843                     if (cols * rows < screen_num_desktops)
844                         rows++;
845                     if (cols * rows >= screen_num_desktops + rows)
846                         cols--;
847                 }
848             }
849
850             /* bounds checking */
851             if (orient == OB_ORIENTATION_HORZ) {
852                 cols = MIN(screen_num_desktops, cols);
853                 rows = MIN(rows, (screen_num_desktops + cols - 1) / cols);
854                 cols = screen_num_desktops / rows +
855                     !!(screen_num_desktops % rows);
856             } else {
857                 rows = MIN(screen_num_desktops, rows);
858                 cols = MIN(cols, (screen_num_desktops + rows - 1) / rows);
859                 rows = screen_num_desktops / cols +
860                     !!(screen_num_desktops % cols);
861             }
862
863             valid = TRUE;
864         }
865     screen_update_layout_bail:
866         g_free(data);
867     }
868
869     if (!valid) {
870         /* defaults */
871         orient = OB_ORIENTATION_HORZ;
872         corner = OB_CORNER_TOPLEFT;
873         rows = 1;
874         cols = screen_num_desktops;
875     }
876
877     screen_desktop_layout.orientation = orient;
878     screen_desktop_layout.start_corner = corner;
879     screen_desktop_layout.rows = rows;
880     screen_desktop_layout.columns = cols;
881 }
882
883 void screen_update_desktop_names()
884 {
885     guint i;
886
887     /* empty the array */
888     g_strfreev(screen_desktop_names);
889     screen_desktop_names = NULL;
890
891     if (PROP_GETSS(RootWindow(ob_display, ob_screen),
892                    net_desktop_names, utf8, &screen_desktop_names))
893         for (i = 0; screen_desktop_names[i] && i < screen_num_desktops; ++i);
894     else
895         i = 0;
896     if (i < screen_num_desktops) {
897         screen_desktop_names = g_renew(gchar*, screen_desktop_names,
898                                        screen_num_desktops + 1);
899         screen_desktop_names[screen_num_desktops] = NULL;
900         for (; i < screen_num_desktops; ++i)
901             screen_desktop_names[i] = g_strdup_printf("desktop %i", i + 1);
902     }
903
904     /* resize the pager for these names */
905     pager_popup_text_width_to_strings(desktop_cycle_popup,
906                                       screen_desktop_names,
907                                       screen_num_desktops);
908 }
909
910 void screen_show_desktop(gboolean show, ObClient *show_only)
911 {
912     GList *it;
913      
914     if (show == screen_showing_desktop) return; /* no change */
915
916     screen_showing_desktop = show;
917
918     if (show) {
919         /* hide windows bottom to top */
920         for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
921             if (WINDOW_IS_CLIENT(it->data)) {
922                 ObClient *client = it->data;
923                 client_showhide(client);
924             }
925         }
926     }
927     else {
928         /* restore windows top to bottom */
929         for (it = stacking_list; it; it = g_list_next(it)) {
930             if (WINDOW_IS_CLIENT(it->data)) {
931                 ObClient *client = it->data;
932                 if (client_should_show(client)) {
933                     if (!show_only || client == show_only)
934                         client_show(client);
935                     else
936                         client_iconify(client, TRUE, FALSE, TRUE);
937                 }
938             }
939         }
940     }
941
942     if (show) {
943         /* focus the desktop */
944         for (it = focus_order; it; it = g_list_next(it)) {
945             ObClient *c = it->data;
946             if (c->type == OB_CLIENT_TYPE_DESKTOP &&
947                 (c->desktop == screen_desktop || c->desktop == DESKTOP_ALL) &&
948                 client_focus(it->data))
949                 break;
950         }
951     }
952     else if (!show_only) {
953         ObClient *c;
954
955         if ((c = focus_fallback(TRUE))) {
956             /* only do the flicker reducing stuff ahead of time if we are going
957                to call xsetinputfocus on the window ourselves. otherwise there
958                is no guarantee the window will actually take focus.. */
959             if (c->can_focus) {
960                 /* reduce flicker by hiliting now rather than waiting for the
961                    server FocusIn event */
962                 frame_adjust_focus(c->frame, TRUE);
963             }
964         }
965     }
966
967     show = !!show; /* make it boolean */
968     PROP_SET32(RootWindow(ob_display, ob_screen),
969                net_showing_desktop, cardinal, show);
970 }
971
972 void screen_install_colormap(ObClient *client, gboolean install)
973 {
974     if (client == NULL) {
975         if (install)
976             XInstallColormap(RrDisplay(ob_rr_inst), RrColormap(ob_rr_inst));
977         else
978             XUninstallColormap(RrDisplay(ob_rr_inst), RrColormap(ob_rr_inst));
979     } else {
980         xerror_set_ignore(TRUE);
981         if (install) {
982             if (client->colormap != None)
983                 XInstallColormap(RrDisplay(ob_rr_inst), client->colormap);
984         } else
985             XUninstallColormap(RrDisplay(ob_rr_inst), client->colormap);
986         xerror_set_ignore(FALSE);
987     }
988 }
989
990 static inline void
991 screen_area_add_strut_left(const StrutPartial *s, const Rect *monitor_area,
992                            gint edge, Strut *ret)
993 {
994     if (s->left &&
995         ((s->left_end <= s->left_start) ||
996          (RECT_TOP(*monitor_area) < s->left_end &&
997           RECT_BOTTOM(*monitor_area) > s->left_start)))
998         ret->left = MAX(ret->left, edge);
999 }
1000
1001 static inline void
1002 screen_area_add_strut_top(const StrutPartial *s, const Rect *monitor_area,
1003                           gint edge, Strut *ret)
1004 {
1005     if (s->top &&
1006         ((s->top_end <= s->top_start) ||
1007          (RECT_LEFT(*monitor_area) < s->top_end &&
1008           RECT_RIGHT(*monitor_area) > s->top_start)))
1009         ret->top = MAX(ret->top, edge);
1010 }
1011
1012 static inline void
1013 screen_area_add_strut_right(const StrutPartial *s, const Rect *monitor_area,
1014                             gint edge, Strut *ret)
1015 {
1016     if (s->right &&
1017         ((s->right_end <= s->right_start) ||
1018          (RECT_TOP(*monitor_area) < s->right_end &&
1019           RECT_BOTTOM(*monitor_area) > s->right_start)))
1020         ret->right = MAX(ret->right, edge);
1021 }
1022
1023 static inline void
1024 screen_area_add_strut_bottom(const StrutPartial *s, const Rect *monitor_area,
1025                              gint edge, Strut *ret)
1026 {
1027     if (s->bottom &&
1028         ((s->bottom_end <= s->bottom_start) ||
1029          (RECT_LEFT(*monitor_area) < s->bottom_end &&
1030           RECT_RIGHT(*monitor_area) > s->bottom_start)))
1031         ret->bottom = MAX(ret->bottom, edge);
1032 }
1033
1034 void screen_update_areas()
1035 {
1036     guint i, x;
1037     gulong *dims;
1038     GList *it;
1039     gint o;
1040
1041     g_free(monitor_area);
1042     extensions_xinerama_screens(&monitor_area, &screen_num_monitors);
1043
1044     if (area) {
1045         for (i = 0; area[i]; ++i)
1046             g_free(area[i]);
1047         g_free(area);
1048     }
1049
1050     area = g_new(Rect*, screen_num_desktops + 2);
1051     for (i = 0; i < screen_num_desktops + 1; ++i)
1052         area[i] = g_new0(Rect, screen_num_monitors + 1);
1053     area[i] = NULL;
1054      
1055     dims = g_new(gulong, 4 * screen_num_desktops);
1056
1057     for (i = 0; i < screen_num_desktops + 1; ++i) {
1058         Strut *struts;
1059         gint l, r, t, b;
1060
1061         struts = g_new0(Strut, screen_num_monitors);
1062
1063         /* calc the xinerama areas */
1064         for (x = 0; x < screen_num_monitors; ++x) {
1065             area[i][x] = monitor_area[x];
1066             if (x == 0) {
1067                 l = monitor_area[x].x;
1068                 t = monitor_area[x].y;
1069                 r = monitor_area[x].x + monitor_area[x].width - 1;
1070                 b = monitor_area[x].y + monitor_area[x].height - 1;
1071             } else {
1072                 l = MIN(l, monitor_area[x].x);
1073                 t = MIN(t, monitor_area[x].y);
1074                 r = MAX(r, monitor_area[x].x + monitor_area[x].width - 1);
1075                 b = MAX(b, monitor_area[x].y + monitor_area[x].height - 1);
1076             }
1077         }
1078         RECT_SET(area[i][x], l, t, r - l + 1, b - t + 1);
1079
1080         /* apply the struts */
1081
1082         /* find the left-most xin heads, i do this in 2 loops :| */
1083         o = area[i][0].x;
1084         for (x = 1; x < screen_num_monitors; ++x)
1085             o = MIN(o, area[i][x].x);
1086
1087         for (x = 0; x < screen_num_monitors; ++x) {
1088             for (it = client_list; it; it = g_list_next(it)) {
1089                 ObClient *c = it->data;
1090                 screen_area_add_strut_left(&c->strut,
1091                                            &monitor_area[x],
1092                                            o + c->strut.left - area[i][x].x,
1093                                            &struts[x]);
1094             }
1095             screen_area_add_strut_left(&dock_strut,
1096                                        &monitor_area[x],
1097                                        o + dock_strut.left - area[i][x].x,
1098                                        &struts[x]);
1099
1100             area[i][x].x += struts[x].left;
1101             area[i][x].width -= struts[x].left;
1102         }
1103
1104         /* find the top-most xin heads, i do this in 2 loops :| */
1105         o = area[i][0].y;
1106         for (x = 1; x < screen_num_monitors; ++x)
1107             o = MIN(o, area[i][x].y);
1108
1109         for (x = 0; x < screen_num_monitors; ++x) {
1110             for (it = client_list; it; it = g_list_next(it)) {
1111                 ObClient *c = it->data;
1112                 screen_area_add_strut_top(&c->strut,
1113                                            &monitor_area[x],
1114                                            o + c->strut.top - area[i][x].y,
1115                                            &struts[x]);
1116             }
1117             screen_area_add_strut_top(&dock_strut,
1118                                       &monitor_area[x],
1119                                       o + dock_strut.top - area[i][x].y,
1120                                       &struts[x]);
1121
1122             area[i][x].y += struts[x].top;
1123             area[i][x].height -= struts[x].top;
1124         }
1125
1126         /* find the right-most xin heads, i do this in 2 loops :| */
1127         o = area[i][0].x + area[i][0].width - 1;
1128         for (x = 1; x < screen_num_monitors; ++x)
1129             o = MAX(o, area[i][x].x + area[i][x].width - 1);
1130
1131         for (x = 0; x < screen_num_monitors; ++x) {
1132             for (it = client_list; it; it = g_list_next(it)) {
1133                 ObClient *c = it->data;
1134                 screen_area_add_strut_right(&c->strut,
1135                                            &monitor_area[x],
1136                                            (area[i][x].x +
1137                                             area[i][x].width - 1) -
1138                                             (o - c->strut.right),
1139                                             &struts[x]);
1140             }
1141             screen_area_add_strut_right(&dock_strut,
1142                                         &monitor_area[x],
1143                                         (area[i][x].x +
1144                                          area[i][x].width - 1) -
1145                                         (o - dock_strut.right),
1146                                         &struts[x]);
1147
1148             area[i][x].width -= struts[x].right;
1149         }
1150
1151         /* find the bottom-most xin heads, i do this in 2 loops :| */
1152         o = area[i][0].y + area[i][0].height - 1;
1153         for (x = 1; x < screen_num_monitors; ++x)
1154             o = MAX(o, area[i][x].y + area[i][x].height - 1);
1155
1156         for (x = 0; x < screen_num_monitors; ++x) {
1157             for (it = client_list; it; it = g_list_next(it)) {
1158                 ObClient *c = it->data;
1159                 screen_area_add_strut_bottom(&c->strut,
1160                                              &monitor_area[x],
1161                                              (area[i][x].y +
1162                                               area[i][x].height - 1) - \
1163                                              (o - c->strut.bottom),
1164                                              &struts[x]);
1165             }
1166             screen_area_add_strut_bottom(&dock_strut,
1167                                          &monitor_area[x],
1168                                          (area[i][x].y +
1169                                           area[i][x].height - 1) - \
1170                                          (o - dock_strut.bottom),
1171                                          &struts[x]);
1172
1173             area[i][x].height -= struts[x].bottom;
1174         }
1175
1176         l = RECT_LEFT(area[i][0]);
1177         t = RECT_TOP(area[i][0]);
1178         r = RECT_RIGHT(area[i][0]);
1179         b = RECT_BOTTOM(area[i][0]);
1180         for (x = 1; x < screen_num_monitors; ++x) {
1181             l = MIN(l, RECT_LEFT(area[i][x]));
1182             t = MIN(l, RECT_TOP(area[i][x]));
1183             r = MAX(r, RECT_RIGHT(area[i][x]));
1184             b = MAX(b, RECT_BOTTOM(area[i][x]));
1185         }
1186         RECT_SET(area[i][screen_num_monitors], l, t,
1187                  r - l + 1, b - t + 1);
1188
1189         /* XXX optimize when this is run? */
1190
1191         /* the area has changed, adjust all the maximized 
1192            windows */
1193         for (it = client_list; it; it = g_list_next(it)) {
1194             ObClient *c = it->data; 
1195             if (i < screen_num_desktops) {
1196                 if (c->desktop == i)
1197                     client_reconfigure(c);
1198             } else if (c->desktop == DESKTOP_ALL)
1199                 client_reconfigure(c);
1200         }
1201         if (i < screen_num_desktops) {
1202             /* don't set these for the 'all desktops' area */
1203             dims[(i * 4) + 0] = area[i][screen_num_monitors].x;
1204             dims[(i * 4) + 1] = area[i][screen_num_monitors].y;
1205             dims[(i * 4) + 2] = area[i][screen_num_monitors].width;
1206             dims[(i * 4) + 3] = area[i][screen_num_monitors].height;
1207         }
1208
1209         g_free(struts);
1210     }
1211
1212     PROP_SETA32(RootWindow(ob_display, ob_screen), net_workarea, cardinal,
1213                 dims, 4 * screen_num_desktops);
1214
1215     g_free(dims);
1216 }
1217
1218 Rect *screen_area(guint desktop)
1219 {
1220     return screen_area_monitor(desktop, screen_num_monitors);
1221 }
1222
1223 Rect *screen_area_monitor(guint desktop, guint head)
1224 {
1225     if (head > screen_num_monitors)
1226         return NULL;
1227     if (desktop >= screen_num_desktops) {
1228         if (desktop == DESKTOP_ALL)
1229             return &area[screen_num_desktops][head];
1230         return NULL;
1231     }
1232     return &area[desktop][head];
1233 }
1234
1235 guint screen_find_monitor(Rect *search)
1236 {
1237     guint i;
1238     guint most = 0;
1239     guint mostv = 0;
1240
1241     for (i = 0; i < screen_num_monitors; ++i) {
1242         Rect *area = screen_physical_area_monitor(i);
1243         if (RECT_INTERSECTS_RECT(*area, *search)) {
1244             Rect r;
1245             guint v;
1246
1247             RECT_SET_INTERSECTION(r, *area, *search);
1248             v = r.width * r.height;
1249
1250             if (v > mostv) {
1251                 mostv = v;
1252                 most = i;
1253             }
1254         }
1255     }
1256     return most;
1257 }
1258
1259 Rect *screen_physical_area()
1260 {
1261     return screen_physical_area_monitor(screen_num_monitors);
1262 }
1263
1264 Rect *screen_physical_area_monitor(guint head)
1265 {
1266     if (head > screen_num_monitors)
1267         return NULL;
1268     return &monitor_area[head];
1269 }
1270
1271 void screen_set_root_cursor()
1272 {
1273     if (sn_app_starting())
1274         XDefineCursor(ob_display, RootWindow(ob_display, ob_screen),
1275                       ob_cursor(OB_CURSOR_BUSY));
1276     else
1277         XDefineCursor(ob_display, RootWindow(ob_display, ob_screen),
1278                       ob_cursor(OB_CURSOR_POINTER));
1279 }
1280
1281 gboolean screen_pointer_pos(gint *x, gint *y)
1282 {
1283     Window w;
1284     gint i;
1285     guint u;
1286     gboolean ret;
1287
1288     ret = !!XQueryPointer(ob_display, RootWindow(ob_display, ob_screen),
1289                           &w, &w, x, y, &i, &i, &u);
1290     if (!ret) {
1291         for (i = 0; i < ScreenCount(ob_display); ++i)
1292             if (i != ob_screen)
1293                 if (XQueryPointer(ob_display, RootWindow(ob_display, i),
1294                                   &w, &w, x, y, &i, &i, &u))
1295                     break;
1296     }
1297     return ret;
1298 }