]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/screen.c
resist for one key press at edges when keyboard move/resizing
[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_state;
262     supported[i++] = prop_atoms.net_wm_state_modal;
263     supported[i++] = prop_atoms.net_wm_state_maximized_vert;
264     supported[i++] = prop_atoms.net_wm_state_maximized_horz;
265     supported[i++] = prop_atoms.net_wm_state_shaded;
266     supported[i++] = prop_atoms.net_wm_state_skip_taskbar;
267     supported[i++] = prop_atoms.net_wm_state_skip_pager;
268     supported[i++] = prop_atoms.net_wm_state_hidden;
269     supported[i++] = prop_atoms.net_wm_state_fullscreen;
270     supported[i++] = prop_atoms.net_wm_state_above;
271     supported[i++] = prop_atoms.net_wm_state_below;
272     supported[i++] = prop_atoms.net_wm_state_demands_attention;
273     supported[i++] = prop_atoms.net_moveresize_window;
274     supported[i++] = prop_atoms.net_wm_moveresize;
275     supported[i++] = prop_atoms.net_wm_user_time;
276     supported[i++] = prop_atoms.net_frame_extents;
277     supported[i++] = prop_atoms.net_startup_id;
278 #ifdef SYNC
279     supported[i++] = prop_atoms.net_wm_sync_request;
280     supported[i++] = prop_atoms.net_wm_sync_request_counter;
281 #endif
282
283     supported[i++] = prop_atoms.kde_wm_change_state;
284     supported[i++] = prop_atoms.kde_net_wm_frame_strut;
285     supported[i++] = prop_atoms.kde_net_wm_window_type_override;
286
287     supported[i++] = prop_atoms.openbox_wm_state_undecorated;
288     supported[i++] = prop_atoms.openbox_pid;
289     supported[i++] = prop_atoms.openbox_config;
290     supported[i++] = prop_atoms.openbox_control;
291     g_assert(i == num_support);
292
293     PROP_SETA32(RootWindow(ob_display, ob_screen),
294                 net_supported, atom, supported, num_support);
295     g_free(supported);
296
297     return TRUE;
298 }
299
300 void screen_startup(gboolean reconfig)
301 {
302     guint i, numnames;
303     gchar **names;
304     GSList *it;
305     guint32 d;
306
307     desktop_cycle_popup = pager_popup_new(FALSE);
308     pager_popup_height(desktop_cycle_popup, POPUP_HEIGHT);
309
310     if (reconfig)
311         return;
312
313     /* get the initial size */
314     screen_resize();
315
316     /* get the desktop names */
317     numnames = g_slist_length(config_desktops_names);
318     names = g_new(gchar*, numnames + 1);
319     names[numnames] = NULL;
320     for (i = 0, it = config_desktops_names; it; ++i, it = g_slist_next(it))
321         names[i] = g_strdup(it->data);
322
323     /* set the root window property */
324     PROP_SETSS(RootWindow(ob_display, ob_screen), net_desktop_names,names);
325
326     g_strfreev(names);
327
328     /* set the number of desktops */
329     screen_num_desktops = 0;
330     screen_set_num_desktops(config_desktops_num);
331
332     /* start on the current desktop when a wm was already running */
333     if (PROP_GET32(RootWindow(ob_display, ob_screen),
334                    net_current_desktop, cardinal, &d) &&
335         d < screen_num_desktops)
336     {
337         screen_set_desktop(d, FALSE);
338     } else if (session_desktop >= 0)
339         screen_set_desktop(MIN((guint)session_desktop,
340                                screen_num_desktops), FALSE);
341     else
342         screen_set_desktop(MIN(config_screen_firstdesk,
343                                screen_num_desktops) - 1, FALSE);
344
345     /* don't start in showing-desktop mode */
346     screen_showing_desktop = FALSE;
347     PROP_SET32(RootWindow(ob_display, ob_screen),
348                net_showing_desktop, cardinal, screen_showing_desktop);
349
350     screen_update_layout();
351 }
352
353 void screen_shutdown(gboolean reconfig)
354 {
355     Rect **r;
356
357     pager_popup_free(desktop_cycle_popup);
358
359     if (reconfig)
360         return;
361
362     XSelectInput(ob_display, RootWindow(ob_display, ob_screen),
363                  NoEventMask);
364
365     /* we're not running here no more! */
366     PROP_ERASE(RootWindow(ob_display, ob_screen), openbox_pid);
367     /* not without us */
368     PROP_ERASE(RootWindow(ob_display, ob_screen), net_supported);
369     /* don't keep this mode */
370     PROP_ERASE(RootWindow(ob_display, ob_screen), net_showing_desktop);
371
372     XDestroyWindow(ob_display, screen_support_win);
373
374     g_strfreev(screen_desktop_names);
375     screen_desktop_names = NULL;
376
377     for (r = area; *r; ++r)
378         g_free(*r);
379     g_free(area);
380     area = NULL;
381 }
382
383 void screen_resize()
384 {
385     static gint oldw = 0, oldh = 0;
386     gint w, h;
387     GList *it;
388     gulong geometry[2];
389
390     w = WidthOfScreen(ScreenOfDisplay(ob_display, ob_screen));
391     h = HeightOfScreen(ScreenOfDisplay(ob_display, ob_screen));
392
393     if (w == oldw && h == oldh) return;
394
395     oldw = w; oldh = h;
396
397     /* Set the _NET_DESKTOP_GEOMETRY hint */
398     screen_physical_size.width = geometry[0] = w;
399     screen_physical_size.height = geometry[1] = h;
400     PROP_SETA32(RootWindow(ob_display, ob_screen),
401                 net_desktop_geometry, cardinal, geometry, 2);
402
403     if (ob_state() == OB_STATE_STARTING)
404         return;
405
406     screen_update_areas();
407     dock_configure();
408
409     for (it = client_list; it; it = g_list_next(it))
410         client_move_onscreen(it->data, FALSE);
411 }
412
413 void screen_set_num_desktops(guint num)
414 {
415     guint old;
416     gulong *viewport;
417     GList *it;
418
419     g_assert(num > 0);
420
421     if (screen_num_desktops == num) return;
422
423     old = screen_num_desktops;
424     screen_num_desktops = num;
425     PROP_SET32(RootWindow(ob_display, ob_screen),
426                net_number_of_desktops, cardinal, num);
427
428     /* set the viewport hint */
429     viewport = g_new0(gulong, num * 2);
430     PROP_SETA32(RootWindow(ob_display, ob_screen),
431                 net_desktop_viewport, cardinal, viewport, num * 2);
432     g_free(viewport);
433
434     /* the number of rows/columns will differ */
435     screen_update_layout();
436
437     /* move windows on desktops that will no longer exist! */
438     for (it = client_list; it; it = g_list_next(it)) {
439         ObClient *c = it->data;
440         if (c->desktop >= num && c->desktop != DESKTOP_ALL)
441             client_set_desktop(c, num - 1, FALSE);
442     }
443  
444     /* change our struts/area to match (after moving windows) */
445     screen_update_areas();
446
447     /* may be some unnamed desktops that we need to fill in with names
448      (after updating the areas so the popup can resize) */
449     screen_update_desktop_names();
450
451     /* change our desktop if we're on one that no longer exists! */
452     if (screen_desktop >= screen_num_desktops)
453         screen_set_desktop(num - 1, TRUE);
454 }
455
456 void screen_set_desktop(guint num, gboolean dofocus)
457 {
458     ObClient *c;
459     GList *it;
460     guint old;
461      
462     g_assert(num < screen_num_desktops);
463
464     old = screen_desktop;
465     screen_desktop = num;
466     PROP_SET32(RootWindow(ob_display, ob_screen),
467                net_current_desktop, cardinal, num);
468
469     if (old == num) return;
470
471     screen_last_desktop = old;
472
473     ob_debug("Moving to desktop %d\n", num+1);
474
475     if (moveresize_client)
476         client_set_desktop(moveresize_client, num, TRUE);
477
478     /* show windows before hiding the rest to lessen the enter/leave events */
479
480     /* show windows from top to bottom */
481     for (it = stacking_list; it; it = g_list_next(it)) {
482         if (WINDOW_IS_CLIENT(it->data)) {
483             ObClient *c = it->data;
484             client_show(c);
485         }
486     }
487
488     /* have to try focus here because when you leave an empty desktop
489        there is no focus out to watch for
490
491        do this before hiding the windows so if helper windows are coming
492        with us, they don't get hidden
493     */
494     if (dofocus && (c = focus_fallback_target(TRUE, focus_client))) {
495         /* only do the flicker reducing stuff ahead of time if we are going
496            to call xsetinputfocus on the window ourselves. otherwise there is
497            no guarantee the window will actually take focus.. */
498         if (c->can_focus) {
499             /* do this here so that if you switch desktops to a window with
500                helper windows then the helper windows won't flash */
501             client_bring_helper_windows(c);
502             /* reduce flicker by hiliting now rather than waiting for the
503                server FocusIn event */
504             frame_adjust_focus(c->frame, TRUE);
505         }
506         client_focus(c);
507     }
508
509     /* hide windows from bottom to top */
510     for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
511         if (WINDOW_IS_CLIENT(it->data)) {
512             ObClient *c = it->data;
513             client_hide(c);
514         }
515     }
516
517     event_ignore_queued_enters();
518
519     if (event_curtime != CurrentTime)
520         screen_desktop_user_time = event_curtime;
521 }
522
523 static void get_row_col(guint d, guint *r, guint *c)
524 {
525     switch (screen_desktop_layout.orientation) {
526     case OB_ORIENTATION_HORZ:
527         switch (screen_desktop_layout.start_corner) {
528         case OB_CORNER_TOPLEFT:
529             *r = d / screen_desktop_layout.columns;
530             *c = d % screen_desktop_layout.columns;
531             break;
532         case OB_CORNER_BOTTOMLEFT:
533             *r = screen_desktop_layout.rows - 1 -
534                 d / screen_desktop_layout.columns;
535             *c = d % screen_desktop_layout.columns;
536             break;
537         case OB_CORNER_TOPRIGHT:
538             *r = d / screen_desktop_layout.columns;
539             *c = screen_desktop_layout.columns - 1 -
540                 d % screen_desktop_layout.columns;
541             break;
542         case OB_CORNER_BOTTOMRIGHT:
543             *r = screen_desktop_layout.rows - 1 -
544                 d / screen_desktop_layout.columns;
545             *c = screen_desktop_layout.columns - 1 -
546                 d % screen_desktop_layout.columns;
547             break;
548         }
549         break;
550     case OB_ORIENTATION_VERT:
551         switch (screen_desktop_layout.start_corner) {
552         case OB_CORNER_TOPLEFT:
553             *r = d % screen_desktop_layout.rows;
554             *c = d / screen_desktop_layout.rows;
555             break;
556         case OB_CORNER_BOTTOMLEFT:
557             *r = screen_desktop_layout.rows - 1 -
558                 d % screen_desktop_layout.rows;
559             *c = d / screen_desktop_layout.rows;
560             break;
561         case OB_CORNER_TOPRIGHT:
562             *r = d % screen_desktop_layout.rows;
563             *c = screen_desktop_layout.columns - 1 -
564                 d / screen_desktop_layout.rows;
565             break;
566         case OB_CORNER_BOTTOMRIGHT:
567             *r = screen_desktop_layout.rows - 1 -
568                 d % screen_desktop_layout.rows;
569             *c = screen_desktop_layout.columns - 1 -
570                 d / screen_desktop_layout.rows;
571             break;
572         }
573         break;
574     }
575 }
576
577 static guint translate_row_col(guint r, guint c)
578 {
579     switch (screen_desktop_layout.orientation) {
580     case OB_ORIENTATION_HORZ:
581         switch (screen_desktop_layout.start_corner) {
582         case OB_CORNER_TOPLEFT:
583             return r % screen_desktop_layout.rows *
584                 screen_desktop_layout.columns +
585                 c % screen_desktop_layout.columns;
586         case OB_CORNER_BOTTOMLEFT:
587             return (screen_desktop_layout.rows - 1 -
588                     r % screen_desktop_layout.rows) *
589                 screen_desktop_layout.columns +
590                 c % screen_desktop_layout.columns;
591         case OB_CORNER_TOPRIGHT:
592             return r % screen_desktop_layout.rows *
593                 screen_desktop_layout.columns +
594                 (screen_desktop_layout.columns - 1 -
595                  c % screen_desktop_layout.columns);
596         case OB_CORNER_BOTTOMRIGHT:
597             return (screen_desktop_layout.rows - 1 -
598                     r % screen_desktop_layout.rows) *
599                 screen_desktop_layout.columns +
600                 (screen_desktop_layout.columns - 1 -
601                  c % screen_desktop_layout.columns);
602         }
603     case OB_ORIENTATION_VERT:
604         switch (screen_desktop_layout.start_corner) {
605         case OB_CORNER_TOPLEFT:
606             return c % screen_desktop_layout.columns *
607                 screen_desktop_layout.rows +
608                 r % screen_desktop_layout.rows;
609         case OB_CORNER_BOTTOMLEFT:
610             return c % screen_desktop_layout.columns *
611                 screen_desktop_layout.rows +
612                 (screen_desktop_layout.rows - 1 -
613                  r % screen_desktop_layout.rows);
614         case OB_CORNER_TOPRIGHT:
615             return (screen_desktop_layout.columns - 1 -
616                     c % screen_desktop_layout.columns) *
617                 screen_desktop_layout.rows +
618                 r % screen_desktop_layout.rows;
619         case OB_CORNER_BOTTOMRIGHT:
620             return (screen_desktop_layout.columns - 1 -
621                     c % screen_desktop_layout.columns) *
622                 screen_desktop_layout.rows +
623                 (screen_desktop_layout.rows - 1 -
624                  r % screen_desktop_layout.rows);
625         }
626     }
627     g_assert_not_reached();
628     return 0;
629 }
630
631 void screen_desktop_popup(guint d, gboolean show)
632 {
633     Rect *a;
634
635     if (!show) {
636         pager_popup_hide(desktop_cycle_popup);
637     } else {
638         a = screen_physical_area_monitor(0);
639         pager_popup_position(desktop_cycle_popup, CenterGravity,
640                              a->x + a->width / 2, a->y + a->height / 2);
641         pager_popup_max_width(desktop_cycle_popup,
642                               MAX(a->width/3, POPUP_WIDTH));
643         pager_popup_show(desktop_cycle_popup, screen_desktop_names[d], d);
644     }
645 }
646
647 guint screen_cycle_desktop(ObDirection dir, gboolean wrap, gboolean linear,
648                            gboolean dialog, gboolean done, gboolean cancel)
649 {
650     guint d, r, c;
651
652     d = screen_desktop;
653
654     if ((cancel || done) && dialog)
655         goto show_cycle_dialog;
656
657     get_row_col(d, &r, &c);
658
659     if (linear) {
660         switch (dir) {
661         case OB_DIRECTION_EAST:
662             if (d < screen_num_desktops - 1)
663                 ++d;
664             else if (wrap)
665                 d = 0;
666             break;
667         case OB_DIRECTION_WEST:
668             if (d > 0)
669                 --d;
670             else if (wrap)
671                 d = screen_num_desktops - 1;
672             break;
673         default:
674             assert(0);
675             return screen_desktop;
676         }
677     } else {
678         switch (dir) {
679         case OB_DIRECTION_EAST:
680             ++c;
681             if (c >= screen_desktop_layout.columns) {
682                 if (wrap) {
683                     c = 0;
684                 } else {
685                     d = screen_desktop;
686                     goto show_cycle_dialog;
687                 }
688             }
689             d = translate_row_col(r, c);
690             if (d >= screen_num_desktops) {
691                 if (wrap) {
692                     ++c;
693                 } else {
694                     d = screen_desktop;
695                     goto show_cycle_dialog;
696                 }
697             }
698             break;
699         case OB_DIRECTION_WEST:
700             --c;
701             if (c >= screen_desktop_layout.columns) {
702                 if (wrap) {
703                     c = screen_desktop_layout.columns - 1;
704                 } else {
705                     d = screen_desktop;
706                     goto show_cycle_dialog;
707                 }
708             }
709             d = translate_row_col(r, c);
710             if (d >= screen_num_desktops) {
711                 if (wrap) {
712                     --c;
713                 } else {
714                     d = screen_desktop;
715                     goto show_cycle_dialog;
716                 }
717             }
718             break;
719         case OB_DIRECTION_SOUTH:
720             ++r;
721             if (r >= screen_desktop_layout.rows) {
722                 if (wrap) {
723                     r = 0;
724                 } else {
725                     d = screen_desktop;
726                     goto show_cycle_dialog;
727                 }
728             }
729             d = translate_row_col(r, c);
730             if (d >= screen_num_desktops) {
731                 if (wrap) {
732                     ++r;
733                 } else {
734                     d = screen_desktop;
735                     goto show_cycle_dialog;
736                 }
737             }
738             break;
739         case OB_DIRECTION_NORTH:
740             --r;
741             if (r >= screen_desktop_layout.rows) {
742                 if (wrap) {
743                     r = screen_desktop_layout.rows - 1;
744                 } else {
745                     d = screen_desktop;
746                     goto show_cycle_dialog;
747                 }
748             }
749             d = translate_row_col(r, c);
750             if (d >= screen_num_desktops) {
751                 if (wrap) {
752                     --r;
753                 } else {
754                     d = screen_desktop;
755                     goto show_cycle_dialog;
756                 }
757             }
758             break;
759         default:
760             assert(0);
761             return d = screen_desktop;
762         }
763
764         d = translate_row_col(r, c);
765     }
766
767 show_cycle_dialog:
768     if (dialog && !cancel && !done) {
769         screen_desktop_popup(d, TRUE);
770     } else
771         screen_desktop_popup(0, FALSE);
772     return d;
773 }
774
775 void screen_update_layout()
776 {
777     ObOrientation orient;
778     ObCorner corner;
779     guint rows;
780     guint cols;
781     guint32 *data;
782     guint num;
783     gboolean valid = FALSE;
784
785     if (PROP_GETA32(RootWindow(ob_display, ob_screen),
786                     net_desktop_layout, cardinal, &data, &num)) {
787         if (num == 3 || num == 4) {
788
789             if (data[0] == prop_atoms.net_wm_orientation_vert)
790                 orient = OB_ORIENTATION_VERT;
791             else if (data[0] == prop_atoms.net_wm_orientation_horz)
792                 orient = OB_ORIENTATION_HORZ;
793             else
794                 goto screen_update_layout_bail;
795
796             if (num < 4)
797                 corner = OB_CORNER_TOPLEFT;
798             else {
799                 if (data[3] == prop_atoms.net_wm_topleft)
800                     corner = OB_CORNER_TOPLEFT;
801                 else if (data[3] == prop_atoms.net_wm_topright)
802                     corner = OB_CORNER_TOPRIGHT;
803                 else if (data[3] == prop_atoms.net_wm_bottomright)
804                     corner = OB_CORNER_BOTTOMRIGHT;
805                 else if (data[3] == prop_atoms.net_wm_bottomleft)
806                     corner = OB_CORNER_BOTTOMLEFT;
807                 else
808                     goto screen_update_layout_bail;
809             }
810
811             cols = data[1];
812             rows = data[2];
813
814             /* fill in a zero rows/columns */
815             if ((cols == 0 && rows == 0)) { /* both 0's is bad data.. */
816                 goto screen_update_layout_bail;
817             } else {
818                 if (cols == 0) {
819                     cols = screen_num_desktops / rows;
820                     if (rows * cols < screen_num_desktops)
821                         cols++;
822                     if (rows * cols >= screen_num_desktops + cols)
823                         rows--;
824                 } else if (rows == 0) {
825                     rows = screen_num_desktops / cols;
826                     if (cols * rows < screen_num_desktops)
827                         rows++;
828                     if (cols * rows >= screen_num_desktops + rows)
829                         cols--;
830                 }
831             }
832
833             /* bounds checking */
834             if (orient == OB_ORIENTATION_HORZ) {
835                 cols = MIN(screen_num_desktops, cols);
836                 rows = MIN(rows, (screen_num_desktops + cols - 1) / cols);
837                 cols = screen_num_desktops / rows +
838                     !!(screen_num_desktops % rows);
839             } else {
840                 rows = MIN(screen_num_desktops, rows);
841                 cols = MIN(cols, (screen_num_desktops + rows - 1) / rows);
842                 rows = screen_num_desktops / cols +
843                     !!(screen_num_desktops % cols);
844             }
845
846             valid = TRUE;
847         }
848     screen_update_layout_bail:
849         g_free(data);
850     }
851
852     if (!valid) {
853         /* defaults */
854         orient = OB_ORIENTATION_HORZ;
855         corner = OB_CORNER_TOPLEFT;
856         rows = 1;
857         cols = screen_num_desktops;
858     }
859
860     screen_desktop_layout.orientation = orient;
861     screen_desktop_layout.start_corner = corner;
862     screen_desktop_layout.rows = rows;
863     screen_desktop_layout.columns = cols;
864 }
865
866 void screen_update_desktop_names()
867 {
868     guint i;
869
870     /* empty the array */
871     g_strfreev(screen_desktop_names);
872     screen_desktop_names = NULL;
873
874     if (PROP_GETSS(RootWindow(ob_display, ob_screen),
875                    net_desktop_names, utf8, &screen_desktop_names))
876         for (i = 0; screen_desktop_names[i] && i < screen_num_desktops; ++i);
877     else
878         i = 0;
879     if (i < screen_num_desktops) {
880         screen_desktop_names = g_renew(gchar*, screen_desktop_names,
881                                        screen_num_desktops + 1);
882         screen_desktop_names[screen_num_desktops] = NULL;
883         for (; i < screen_num_desktops; ++i)
884             screen_desktop_names[i] = g_strdup_printf("desktop %i", i + 1);
885     }
886
887     /* resize the pager for these names */
888     pager_popup_text_width_to_strings(desktop_cycle_popup,
889                                       screen_desktop_names,
890                                       screen_num_desktops);
891 }
892
893 void screen_show_desktop(gboolean show, gboolean restore_focus)
894 {
895     GList *it;
896      
897     if (show == screen_showing_desktop) return; /* no change */
898
899     screen_showing_desktop = show;
900
901     if (show) {
902         /* bottom to top */
903         for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
904             if (WINDOW_IS_CLIENT(it->data)) {
905                 ObClient *client = it->data;
906                 client_showhide(client);
907             }
908         }
909     } else {
910         /* top to bottom */
911         for (it = stacking_list; it; it = g_list_next(it)) {
912             if (WINDOW_IS_CLIENT(it->data)) {
913                 ObClient *client = it->data;
914                 client_showhide(client);
915             }
916         }
917     }
918
919     if (show) {
920         /* focus desktop */
921         for (it = focus_order; it; it = g_list_next(it)) {
922             ObClient *c = it->data;
923             if (c->type == OB_CLIENT_TYPE_DESKTOP &&
924                 (c->desktop == screen_desktop || c->desktop == DESKTOP_ALL) &&
925                 client_focus(it->data))
926                 break;
927         }
928     } else if (restore_focus) {
929         ObClient *c;
930
931         /* use NULL for the "old" argument because the desktop was focused
932            and we don't want to fallback to the desktop by default */
933         if ((c = focus_fallback_target(TRUE, NULL)))
934             client_focus(c);
935     }
936
937     show = !!show; /* make it boolean */
938     PROP_SET32(RootWindow(ob_display, ob_screen),
939                net_showing_desktop, cardinal, show);
940 }
941
942 void screen_install_colormap(ObClient *client, gboolean install)
943 {
944     if (client == NULL) {
945         if (install)
946             XInstallColormap(RrDisplay(ob_rr_inst), RrColormap(ob_rr_inst));
947         else
948             XUninstallColormap(RrDisplay(ob_rr_inst), RrColormap(ob_rr_inst));
949     } else {
950         xerror_set_ignore(TRUE);
951         if (install) {
952             if (client->colormap != None)
953                 XInstallColormap(RrDisplay(ob_rr_inst), client->colormap);
954         } else
955             XUninstallColormap(RrDisplay(ob_rr_inst), client->colormap);
956         xerror_set_ignore(FALSE);
957     }
958 }
959
960 static inline void
961 screen_area_add_strut_left(const StrutPartial *s, const Rect *monitor_area,
962                            gint edge, Strut *ret)
963 {
964     if (s->left &&
965         ((s->left_end <= s->left_start) ||
966          (RECT_TOP(*monitor_area) < s->left_end &&
967           RECT_BOTTOM(*monitor_area) > s->left_start)))
968         ret->left = MAX(ret->left, edge);
969 }
970
971 static inline void
972 screen_area_add_strut_top(const StrutPartial *s, const Rect *monitor_area,
973                           gint edge, Strut *ret)
974 {
975     if (s->top &&
976         ((s->top_end <= s->top_start) ||
977          (RECT_LEFT(*monitor_area) < s->top_end &&
978           RECT_RIGHT(*monitor_area) > s->top_start)))
979         ret->top = MAX(ret->top, edge);
980 }
981
982 static inline void
983 screen_area_add_strut_right(const StrutPartial *s, const Rect *monitor_area,
984                             gint edge, Strut *ret)
985 {
986     if (s->right &&
987         ((s->right_end <= s->right_start) ||
988          (RECT_TOP(*monitor_area) < s->right_end &&
989           RECT_BOTTOM(*monitor_area) > s->right_start)))
990         ret->right = MAX(ret->right, edge);
991 }
992
993 static inline void
994 screen_area_add_strut_bottom(const StrutPartial *s, const Rect *monitor_area,
995                              gint edge, Strut *ret)
996 {
997     if (s->bottom &&
998         ((s->bottom_end <= s->bottom_start) ||
999          (RECT_LEFT(*monitor_area) < s->bottom_end &&
1000           RECT_RIGHT(*monitor_area) > s->bottom_start)))
1001         ret->bottom = MAX(ret->bottom, edge);
1002 }
1003
1004 void screen_update_areas()
1005 {
1006     guint i, x;
1007     gulong *dims;
1008     GList *it;
1009     gint o;
1010
1011     g_free(monitor_area);
1012     extensions_xinerama_screens(&monitor_area, &screen_num_monitors);
1013
1014     if (area) {
1015         for (i = 0; area[i]; ++i)
1016             g_free(area[i]);
1017         g_free(area);
1018     }
1019
1020     area = g_new(Rect*, screen_num_desktops + 2);
1021     for (i = 0; i < screen_num_desktops + 1; ++i)
1022         area[i] = g_new0(Rect, screen_num_monitors + 1);
1023     area[i] = NULL;
1024      
1025     dims = g_new(gulong, 4 * screen_num_desktops);
1026
1027     for (i = 0; i < screen_num_desktops + 1; ++i) {
1028         Strut *struts;
1029         gint l, r, t, b;
1030
1031         struts = g_new0(Strut, screen_num_monitors);
1032
1033         /* calc the xinerama areas */
1034         for (x = 0; x < screen_num_monitors; ++x) {
1035             area[i][x] = monitor_area[x];
1036             if (x == 0) {
1037                 l = monitor_area[x].x;
1038                 t = monitor_area[x].y;
1039                 r = monitor_area[x].x + monitor_area[x].width - 1;
1040                 b = monitor_area[x].y + monitor_area[x].height - 1;
1041             } else {
1042                 l = MIN(l, monitor_area[x].x);
1043                 t = MIN(t, monitor_area[x].y);
1044                 r = MAX(r, monitor_area[x].x + monitor_area[x].width - 1);
1045                 b = MAX(b, monitor_area[x].y + monitor_area[x].height - 1);
1046             }
1047         }
1048         RECT_SET(area[i][x], l, t, r - l + 1, b - t + 1);
1049
1050         /* apply the struts */
1051
1052         /* find the left-most xin heads, i do this in 2 loops :| */
1053         o = area[i][0].x;
1054         for (x = 1; x < screen_num_monitors; ++x)
1055             o = MIN(o, area[i][x].x);
1056
1057         for (x = 0; x < screen_num_monitors; ++x) {
1058             for (it = client_list; it; it = g_list_next(it)) {
1059                 ObClient *c = it->data;
1060                 screen_area_add_strut_left(&c->strut,
1061                                            &monitor_area[x],
1062                                            o + c->strut.left - area[i][x].x,
1063                                            &struts[x]);
1064             }
1065             screen_area_add_strut_left(&dock_strut,
1066                                        &monitor_area[x],
1067                                        o + dock_strut.left - area[i][x].x,
1068                                        &struts[x]);
1069
1070             area[i][x].x += struts[x].left;
1071             area[i][x].width -= struts[x].left;
1072         }
1073
1074         /* find the top-most xin heads, i do this in 2 loops :| */
1075         o = area[i][0].y;
1076         for (x = 1; x < screen_num_monitors; ++x)
1077             o = MIN(o, area[i][x].y);
1078
1079         for (x = 0; x < screen_num_monitors; ++x) {
1080             for (it = client_list; it; it = g_list_next(it)) {
1081                 ObClient *c = it->data;
1082                 screen_area_add_strut_top(&c->strut,
1083                                            &monitor_area[x],
1084                                            o + c->strut.top - area[i][x].y,
1085                                            &struts[x]);
1086             }
1087             screen_area_add_strut_top(&dock_strut,
1088                                       &monitor_area[x],
1089                                       o + dock_strut.top - area[i][x].y,
1090                                       &struts[x]);
1091
1092             area[i][x].y += struts[x].top;
1093             area[i][x].height -= struts[x].top;
1094         }
1095
1096         /* find the right-most xin heads, i do this in 2 loops :| */
1097         o = area[i][0].x + area[i][0].width - 1;
1098         for (x = 1; x < screen_num_monitors; ++x)
1099             o = MAX(o, area[i][x].x + area[i][x].width - 1);
1100
1101         for (x = 0; x < screen_num_monitors; ++x) {
1102             for (it = client_list; it; it = g_list_next(it)) {
1103                 ObClient *c = it->data;
1104                 screen_area_add_strut_right(&c->strut,
1105                                            &monitor_area[x],
1106                                            (area[i][x].x +
1107                                             area[i][x].width - 1) -
1108                                             (o - c->strut.right),
1109                                             &struts[x]);
1110             }
1111             screen_area_add_strut_right(&dock_strut,
1112                                         &monitor_area[x],
1113                                         (area[i][x].x +
1114                                          area[i][x].width - 1) -
1115                                         (o - dock_strut.right),
1116                                         &struts[x]);
1117
1118             area[i][x].width -= struts[x].right;
1119         }
1120
1121         /* find the bottom-most xin heads, i do this in 2 loops :| */
1122         o = area[i][0].y + area[i][0].height - 1;
1123         for (x = 1; x < screen_num_monitors; ++x)
1124             o = MAX(o, area[i][x].y + area[i][x].height - 1);
1125
1126         for (x = 0; x < screen_num_monitors; ++x) {
1127             for (it = client_list; it; it = g_list_next(it)) {
1128                 ObClient *c = it->data;
1129                 screen_area_add_strut_bottom(&c->strut,
1130                                              &monitor_area[x],
1131                                              (area[i][x].y +
1132                                               area[i][x].height - 1) - \
1133                                              (o - c->strut.bottom),
1134                                              &struts[x]);
1135             }
1136             screen_area_add_strut_bottom(&dock_strut,
1137                                          &monitor_area[x],
1138                                          (area[i][x].y +
1139                                           area[i][x].height - 1) - \
1140                                          (o - dock_strut.bottom),
1141                                          &struts[x]);
1142
1143             area[i][x].height -= struts[x].bottom;
1144         }
1145
1146         l = RECT_LEFT(area[i][0]);
1147         t = RECT_TOP(area[i][0]);
1148         r = RECT_RIGHT(area[i][0]);
1149         b = RECT_BOTTOM(area[i][0]);
1150         for (x = 1; x < screen_num_monitors; ++x) {
1151             l = MIN(l, RECT_LEFT(area[i][x]));
1152             t = MIN(l, RECT_TOP(area[i][x]));
1153             r = MAX(r, RECT_RIGHT(area[i][x]));
1154             b = MAX(b, RECT_BOTTOM(area[i][x]));
1155         }
1156         RECT_SET(area[i][screen_num_monitors], l, t,
1157                  r - l + 1, b - t + 1);
1158
1159         /* XXX optimize when this is run? */
1160
1161         /* the area has changed, adjust all the maximized 
1162            windows */
1163         for (it = client_list; it; it = g_list_next(it)) {
1164             ObClient *c = it->data; 
1165             if (i < screen_num_desktops) {
1166                 if (c->desktop == i)
1167                     client_reconfigure(c);
1168             } else if (c->desktop == DESKTOP_ALL)
1169                 client_reconfigure(c);
1170         }
1171         if (i < screen_num_desktops) {
1172             /* don't set these for the 'all desktops' area */
1173             dims[(i * 4) + 0] = area[i][screen_num_monitors].x;
1174             dims[(i * 4) + 1] = area[i][screen_num_monitors].y;
1175             dims[(i * 4) + 2] = area[i][screen_num_monitors].width;
1176             dims[(i * 4) + 3] = area[i][screen_num_monitors].height;
1177         }
1178
1179         g_free(struts);
1180     }
1181
1182     PROP_SETA32(RootWindow(ob_display, ob_screen), net_workarea, cardinal,
1183                 dims, 4 * screen_num_desktops);
1184
1185     g_free(dims);
1186 }
1187
1188 Rect *screen_area(guint desktop)
1189 {
1190     return screen_area_monitor(desktop, screen_num_monitors);
1191 }
1192
1193 Rect *screen_area_monitor(guint desktop, guint head)
1194 {
1195     if (head > screen_num_monitors)
1196         return NULL;
1197     if (desktop >= screen_num_desktops) {
1198         if (desktop == DESKTOP_ALL)
1199             return &area[screen_num_desktops][head];
1200         return NULL;
1201     }
1202     return &area[desktop][head];
1203 }
1204
1205 guint screen_find_monitor(Rect *search)
1206 {
1207     guint i;
1208     guint most = 0;
1209     guint mostv = 0;
1210
1211     for (i = 0; i < screen_num_monitors; ++i) {
1212         Rect *area = screen_physical_area_monitor(i);
1213         if (RECT_INTERSECTS_RECT(*area, *search)) {
1214             Rect r;
1215             guint v;
1216
1217             RECT_SET_INTERSECTION(r, *area, *search);
1218             v = r.width * r.height;
1219
1220             if (v > mostv) {
1221                 mostv = v;
1222                 most = i;
1223             }
1224         }
1225     }
1226     return most;
1227 }
1228
1229 Rect *screen_physical_area()
1230 {
1231     return screen_physical_area_monitor(screen_num_monitors);
1232 }
1233
1234 Rect *screen_physical_area_monitor(guint head)
1235 {
1236     if (head > screen_num_monitors)
1237         return NULL;
1238     return &monitor_area[head];
1239 }
1240
1241 void screen_set_root_cursor()
1242 {
1243     if (sn_app_starting())
1244         XDefineCursor(ob_display, RootWindow(ob_display, ob_screen),
1245                       ob_cursor(OB_CURSOR_BUSY));
1246     else
1247         XDefineCursor(ob_display, RootWindow(ob_display, ob_screen),
1248                       ob_cursor(OB_CURSOR_POINTER));
1249 }
1250
1251 gboolean screen_pointer_pos(gint *x, gint *y)
1252 {
1253     Window w;
1254     gint i;
1255     guint u;
1256     gboolean ret;
1257
1258     ret = !!XQueryPointer(ob_display, RootWindow(ob_display, ob_screen),
1259                           &w, &w, x, y, &i, &i, &u);
1260     if (!ret) {
1261         for (i = 0; i < ScreenCount(ob_display); ++i)
1262             if (i != ob_screen)
1263                 if (XQueryPointer(ob_display, RootWindow(ob_display, i),
1264                                   &w, &w, x, y, &i, &i, &u))
1265                     break;
1266     }
1267     return ret;
1268 }