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