fix for bug introduced in revision 5643. logical size is supposed to be updated only...
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.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 "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43
44 #include <glib.h>
45 #include <X11/Xutil.h>
46
47 /*! The event mask to grab on client windows */
48 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask)
49
50 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
51                                 ButtonMotionMask)
52
53 typedef struct
54 {
55     ObClientDestructor func;
56     gpointer data;
57 } Destructor;
58
59 GList         *client_list           = NULL;
60
61 static GSList *client_destructors    = NULL;
62 static Time    client_last_user_time = CurrentTime;
63
64 static void client_get_all(ObClient *self);
65 static void client_toggle_border(ObClient *self, gboolean show);
66 static void client_get_startup_id(ObClient *self);
67 static void client_get_area(ObClient *self);
68 static void client_get_desktop(ObClient *self);
69 static void client_get_state(ObClient *self);
70 static void client_get_layer(ObClient *self);
71 static void client_get_shaped(ObClient *self);
72 static void client_get_mwm_hints(ObClient *self);
73 static void client_get_gravity(ObClient *self);
74 static void client_change_allowed_actions(ObClient *self);
75 static void client_change_state(ObClient *self);
76 static void client_change_wm_state(ObClient *self);
77 static void client_apply_startup_state(ObClient *self, gint x, gint y);
78 static void client_restore_session_state(ObClient *self);
79 static void client_restore_session_stacking(ObClient *self);
80 static ObAppSettings *client_get_settings_state(ObClient *self);
81 static void client_unfocus(ObClient *self);
82
83 void client_startup(gboolean reconfig)
84 {
85     if (reconfig) return;
86
87     client_set_list();
88 }
89
90 void client_shutdown(gboolean reconfig)
91 {
92 }
93
94 void client_add_destructor(ObClientDestructor func, gpointer data)
95 {
96     Destructor *d = g_new(Destructor, 1);
97     d->func = func;
98     d->data = data;
99     client_destructors = g_slist_prepend(client_destructors, d);
100 }
101
102 void client_remove_destructor(ObClientDestructor func)
103 {
104     GSList *it;
105
106     for (it = client_destructors; it; it = g_slist_next(it)) {
107         Destructor *d = it->data;
108         if (d->func == func) {
109             g_free(d);
110             client_destructors = g_slist_delete_link(client_destructors, it);
111             break;
112         }
113     }
114 }
115
116 void client_set_list()
117 {
118     Window *windows, *win_it;
119     GList *it;
120     guint size = g_list_length(client_list);
121
122     /* create an array of the window ids */
123     if (size > 0) {
124         windows = g_new(Window, size);
125         win_it = windows;
126         for (it = client_list; it; it = g_list_next(it), ++win_it)
127             *win_it = ((ObClient*)it->data)->window;
128     } else
129         windows = NULL;
130
131     PROP_SETA32(RootWindow(ob_display, ob_screen),
132                 net_client_list, window, (gulong*)windows, size);
133
134     if (windows)
135         g_free(windows);
136
137     stacking_set_list();
138 }
139
140 /*
141   void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
142   {
143   GSList *it;
144
145   for (it = self->transients; it; it = g_slist_next(it)) {
146   if (!func(it->data, data)) return;
147   client_foreach_transient(it->data, func, data);
148   }
149   }
150
151   void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
152   {
153   if (self->transient_for) {
154   if (self->transient_for != OB_TRAN_GROUP) {
155   if (!func(self->transient_for, data)) return;
156   client_foreach_ancestor(self->transient_for, func, data);
157   } else {
158   GSList *it;
159
160   for (it = self->group->members; it; it = g_slist_next(it))
161   if (it->data != self &&
162   !((ObClient*)it->data)->transient_for) {
163   if (!func(it->data, data)) return;
164   client_foreach_ancestor(it->data, func, data);
165   }
166   }
167   }
168   }
169 */
170
171 void client_manage_all()
172 {
173     guint i, j, nchild;
174     Window w, *children;
175     XWMHints *wmhints;
176     XWindowAttributes attrib;
177
178     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
179                &w, &w, &children, &nchild);
180
181     /* remove all icon windows from the list */
182     for (i = 0; i < nchild; i++) {
183         if (children[i] == None) continue;
184         wmhints = XGetWMHints(ob_display, children[i]);
185         if (wmhints) {
186             if ((wmhints->flags & IconWindowHint) &&
187                 (wmhints->icon_window != children[i]))
188                 for (j = 0; j < nchild; j++)
189                     if (children[j] == wmhints->icon_window) {
190                         children[j] = None;
191                         break;
192                     }
193             XFree(wmhints);
194         }
195     }
196
197     for (i = 0; i < nchild; ++i) {
198         if (children[i] == None)
199             continue;
200         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
201             if (attrib.override_redirect) continue;
202
203             if (attrib.map_state != IsUnmapped)
204                 client_manage(children[i]);
205         }
206     }
207     XFree(children);
208 }
209
210 void client_manage(Window window)
211 {
212     ObClient *self;
213     XEvent e;
214     XWindowAttributes attrib;
215     XSetWindowAttributes attrib_set;
216     XWMHints *wmhint;
217     gboolean activate = FALSE;
218     ObAppSettings *settings;
219     gint newx, newy;
220
221     grab_server(TRUE);
222
223     /* check if it has already been unmapped by the time we started mapping
224        the grab does a sync so we don't have to here */
225     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
226         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e))
227     {
228         XPutBackEvent(ob_display, &e);
229
230         grab_server(FALSE);
231         return; /* don't manage it */
232     }
233
234     /* make sure it isn't an override-redirect window */
235     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
236         attrib.override_redirect)
237     {
238         grab_server(FALSE);
239         return; /* don't manage it */
240     }
241   
242     /* is the window a docking app */
243     if ((wmhint = XGetWMHints(ob_display, window))) {
244         if ((wmhint->flags & StateHint) &&
245             wmhint->initial_state == WithdrawnState)
246         {
247             dock_add(window, wmhint);
248             grab_server(FALSE);
249             XFree(wmhint);
250             return;
251         }
252         XFree(wmhint);
253     }
254
255     ob_debug("Managing window: %lx\n", window);
256
257     /* choose the events we want to receive on the CLIENT window */
258     attrib_set.event_mask = CLIENT_EVENTMASK;
259     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
260     XChangeWindowAttributes(ob_display, window,
261                             CWEventMask|CWDontPropagate, &attrib_set);
262
263
264     /* create the ObClient struct, and populate it from the hints on the
265        window */
266     self = g_new0(ObClient, 1);
267     self->obwin.type = Window_Client;
268     self->window = window;
269
270     /* non-zero defaults */
271     self->title_count = 1;
272     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
273     self->layer = -1;
274     self->desktop = screen_num_desktops; /* always an invalid value */
275     self->user_time = ~0; /* maximum value, always newer than the real time */
276
277     client_get_all(self);
278     client_restore_session_state(self);
279     /* per-app settings override stuff, and return the settings for other
280        uses too */
281     settings = client_get_settings_state(self);
282
283     client_calc_layer(self);
284
285     {
286         Time t = sn_app_started(self->startup_id, self->class);
287         if (t) self->user_time = t;
288     }
289
290     /* update the focus lists, do this before the call to change_state or
291        it can end up in the list twice! */
292     focus_order_add_new(self);
293
294     /* remove the client's border (and adjust re gravity) */
295     client_toggle_border(self, FALSE);
296      
297     /* specify that if we exit, the window should not be destroyed and should
298        be reparented back to root automatically */
299     XChangeSaveSet(ob_display, window, SetModeInsert);
300
301     /* create the decoration frame for the client window */
302     self->frame = frame_new(self);
303
304     frame_grab_client(self->frame, self);
305
306     /* do this after we have a frame.. it uses the frame to help determine the
307        WM_STATE to apply. */
308     client_change_state(self);
309
310     grab_server(FALSE);
311
312     stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
313     client_restore_session_stacking(self);
314
315     /* focus the new window? */
316     if (ob_state() != OB_STATE_STARTING &&
317         /* this means focus=true for window is same as config_focus_new=true */
318         ((config_focus_new || (settings && settings->focus == 1)) ||
319          client_search_focus_parent(self)) &&
320         /* this checks for focus=false for the window */
321         (!settings || settings->focus != 0) &&
322         /* note the check against Type_Normal/Dialog, not client_normal(self),
323            which would also include other types. in this case we want more
324            strict rules for focus */
325         (self->type == OB_CLIENT_TYPE_NORMAL ||
326          self->type == OB_CLIENT_TYPE_DIALOG))
327     {
328         activate = TRUE;
329 #if 0
330         if (self->desktop != screen_desktop) {
331             /* activate the window */
332             activate = TRUE;
333         } else {
334             gboolean group_foc = FALSE;
335
336             if (self->group) {
337                 GSList *it;
338
339                 for (it = self->group->members; it; it = g_slist_next(it))
340                 {
341                     if (client_focused(it->data))
342                     {
343                         group_foc = TRUE;
344                         break;
345                     }
346                 }
347             }
348             if ((group_foc ||
349                  (!self->transient_for && (!self->group ||
350                                            !self->group->members->next))) ||
351                 client_search_focus_tree_full(self) ||
352                 !focus_client ||
353                 !client_normal(focus_client))
354             {
355                 /* activate the window */
356                 activate = TRUE;
357             }
358         }
359 #endif
360     }
361
362     /* get the current position */
363     newx = self->area.x;
364     newy = self->area.y;
365
366     /* figure out placement for the window */
367     if (ob_state() == OB_STATE_RUNNING) {
368         gboolean transient;
369
370         transient = place_client(self, &newx, &newy, settings);
371
372         /* make sure the window is visible. */
373         client_find_onscreen(self, &newx, &newy,
374                              self->frame->area.width,
375                              self->frame->area.height,
376                              /* non-normal clients has less rules, and
377                                 windows that are being restored from a
378                                 session do also. we can assume you want
379                                 it back where you saved it. Clients saying
380                                 they placed themselves are subjected to
381                                 harder rules, ones that are placed by
382                                 place.c or by the user are allowed partially
383                                 off-screen and on xinerama divides (ie,
384                                 it is up to the placement routines to avoid
385                                 the xinerama divides) */
386                              transient ||
387                              (((self->positioned & PPosition) &&
388                                !(self->positioned & USPosition)) &&
389                               client_normal(self) &&
390                               !self->session));
391     }
392
393     /* do this after the window is placed, so the premax/prefullscreen numbers
394        won't be all wacko!!
395        also, this moves the window to the position where it has been placed
396     */
397     client_apply_startup_state(self, newx, newy);
398
399     keyboard_grab_for_client(self, TRUE);
400     mouse_grab_for_client(self, TRUE);
401
402     if (activate) {
403         /* This is focus stealing prevention */
404         ob_debug("Want to focus new window 0x%x with time %u (last time %u)\n",
405                  self->window, self->user_time, client_last_user_time);
406
407         /* If a nothing at all, or a parent was focused, then focus this
408            always
409         */
410         if (!focus_client || client_search_focus_parent(self) != NULL)
411             activate = TRUE;
412         else
413         {
414             /* If time stamp is old, don't steal focus */
415             if (self->user_time && self->user_time < client_last_user_time)
416                 activate = FALSE;
417             /* Don't steal focus from globally active clients.
418                I stole this idea from KWin. It seems nice.
419              */
420             if (!focus_client->can_focus && focus_client->focus_notify)
421                 activate = FALSE;
422         }
423
424         if (activate)
425         {
426             /* since focus can change the stacking orders, if we focus the
427                window then the standard raise it gets is not enough, we need
428                to queue one for after the focus change takes place */
429             client_raise(self);
430         } else {
431             ob_debug("Focus stealing prevention activated for %s with time %u "
432                      "(last time %u)\n",
433                      self->title, self->user_time, client_last_user_time);
434             /* if the client isn't focused, then hilite it so the user
435                knows it is there */
436             client_hilite(self, TRUE);
437         }
438     }
439     else {
440         /* This may look rather odd. Well it's because new windows are added
441            to the stacking order non-intrusively. If we're not going to focus
442            the new window or hilite it, then we raise it to the top. This will
443            take affect for things that don't get focused like splash screens.
444            Also if you don't have focus_new enabled, then it's going to get
445            raised to the top. Legacy begets legacy I guess?
446         */
447         client_raise(self);
448     }
449
450     /* this has to happen before we try focus the window, but we want it to
451        happen after the client's stacking has been determined or it looks bad
452     */
453     client_showhide(self);
454
455     /* use client_focus instead of client_activate cuz client_activate does
456        stuff like switch desktops etc and I'm not interested in all that when
457        a window maps since its not based on an action from the user like
458        clicking a window to activate it. so keep the new window out of the way
459        but do focus it. */
460     if (activate) {
461         /* if using focus_delay, stop the timer now so that focus doesn't
462            go moving on us */
463         event_halt_focus_delay();
464         client_focus(self);
465     }
466
467     /* client_activate does this but we aret using it so we have to do it
468        here as well */
469     if (screen_showing_desktop)
470         screen_show_desktop(FALSE);
471
472     /* add to client list/map */
473     client_list = g_list_append(client_list, self);
474     g_hash_table_insert(window_map, &self->window, self);
475
476     /* this has to happen after we're in the client_list */
477     if (STRUT_EXISTS(self->strut))
478         screen_update_areas();
479
480     /* update the list hints */
481     client_set_list();
482
483     ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
484 }
485
486 void client_unmanage_all()
487 {
488     while (client_list != NULL)
489         client_unmanage(client_list->data);
490 }
491
492 void client_unmanage(ObClient *self)
493 {
494     guint j;
495     GSList *it;
496
497     ob_debug("Unmanaging window: %lx (%s) (%s)\n", self->window, self->class,
498              self->title ? self->title : "");
499
500     g_assert(self != NULL);
501
502     /* update the focus lists */
503     focus_order_remove(self);
504
505     if (focus_client == self) {
506         XEvent e;
507
508         /* focus the last focused window on the desktop, and ignore enter
509            events from the unmap so it doesnt mess with the focus */
510         while (XCheckTypedEvent(ob_display, EnterNotify, &e));
511         /* remove these flags so we don't end up getting focused in the
512            fallback! */
513         self->can_focus = FALSE;
514         self->focus_notify = FALSE;
515         self->modal = FALSE;
516         client_unfocus(self);
517     }
518
519     /* potentially fix focusLast */
520     if (config_focus_last)
521         grab_pointer(TRUE, OB_CURSOR_NONE);
522
523     frame_hide(self->frame);
524     XFlush(ob_display);
525
526     keyboard_grab_for_client(self, FALSE);
527     mouse_grab_for_client(self, FALSE);
528
529     /* remove the window from our save set */
530     XChangeSaveSet(ob_display, self->window, SetModeDelete);
531
532     /* we dont want events no more */
533     XSelectInput(ob_display, self->window, NoEventMask);
534
535     client_list = g_list_remove(client_list, self);
536     stacking_remove(self);
537     g_hash_table_remove(window_map, &self->window);
538
539     /* once the client is out of the list, update the struts to remove its
540        influence */
541     if (STRUT_EXISTS(self->strut))
542         screen_update_areas();
543
544     for (it = client_destructors; it; it = g_slist_next(it)) {
545         Destructor *d = it->data;
546         d->func(self, d->data);
547     }
548
549     /* tell our parent(s) that we're gone */
550     if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
551         for (it = self->group->members; it; it = g_slist_next(it))
552             if (it->data != self)
553                 ((ObClient*)it->data)->transients =
554                     g_slist_remove(((ObClient*)it->data)->transients, self);
555     } else if (self->transient_for) {        /* transient of window */
556         self->transient_for->transients =
557             g_slist_remove(self->transient_for->transients, self);
558     }
559
560     /* tell our transients that we're gone */
561     for (it = self->transients; it; it = g_slist_next(it)) {
562         if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
563             ((ObClient*)it->data)->transient_for = NULL;
564             client_calc_layer(it->data);
565         }
566     }
567
568     /* remove from its group */
569     if (self->group) {
570         group_remove(self->group, self);
571         self->group = NULL;
572     }
573
574     /* give the client its border back */
575     client_toggle_border(self, TRUE);
576
577     /* reparent the window out of the frame, and free the frame */
578     frame_release_client(self->frame, self);
579     self->frame = NULL;
580
581     /* restore the window's original geometry so it is not lost */
582     if (self->fullscreen)
583         XMoveResizeWindow(ob_display, self->window,
584                           self->pre_fullscreen_area.x,
585                           self->pre_fullscreen_area.y,
586                           self->pre_fullscreen_area.width,
587                           self->pre_fullscreen_area.height);
588     else if (self->max_horz || self->max_vert) {
589         Rect a = self->area;
590         if (self->max_horz) {
591             a.x = self->pre_max_area.x;
592             a.width = self->pre_max_area.width;
593         }
594         if (self->max_vert) {
595             a.y = self->pre_max_area.y;
596             a.height = self->pre_max_area.height;
597         }
598         XMoveResizeWindow(ob_display, self->window,
599                           a.x, a.y, a.width, a.height);
600     }
601      
602     if (ob_state() != OB_STATE_EXITING) {
603         /* these values should not be persisted across a window
604            unmapping/mapping */
605         PROP_ERASE(self->window, net_wm_desktop);
606         PROP_ERASE(self->window, net_wm_state);
607         PROP_ERASE(self->window, wm_state);
608     } else {
609         /* if we're left in an unmapped state, the client wont be mapped. this
610            is bad, since we will no longer be managing the window on restart */
611         XMapWindow(ob_display, self->window);
612     }
613
614
615     ob_debug("Unmanaged window 0x%lx\n", self->window);
616
617     /* free all data allocated in the client struct */
618     g_slist_free(self->transients);
619     for (j = 0; j < self->nicons; ++j)
620         g_free(self->icons[j].data);
621     if (self->nicons > 0)
622         g_free(self->icons);
623     g_free(self->title);
624     g_free(self->icon_title);
625     g_free(self->name);
626     g_free(self->class);
627     g_free(self->role);
628     g_free(self->sm_client_id);
629     g_free(self);
630      
631     /* update the list hints */
632     client_set_list();
633
634     if (config_focus_last)
635         grab_pointer(FALSE, OB_CURSOR_NONE);
636 }
637
638 static ObAppSettings *client_get_settings_state(ObClient *self)
639 {
640     ObAppSettings *settings = NULL;
641     GSList *it;
642
643     for (it = config_per_app_settings; it; it = g_slist_next(it)) {
644         ObAppSettings *app = it->data;
645         
646         if ((app->name && !app->class && !strcmp(app->name, self->name))
647             || (app->class && !app->name && !strcmp(app->class, self->class))
648             || (app->class && app->name && !strcmp(app->class, self->class)
649                 && !strcmp(app->name, self->name)))
650         {
651             ob_debug("Window matching: %s\n", app->name);
652             /* Match if no role was specified in the per app setting, or if the
653              * string matches the beginning of the role, since apps like to set
654              * the role to things like browser-window-23c4b2f */
655             if (!app->role
656                 || !strncmp(app->role, self->role, strlen(app->role)))
657             {
658                 /* use this one */
659                 settings = app;
660                 break;
661             }
662         }
663     }
664
665     if (settings) {
666         if (settings->shade != -1)
667             self->shaded = !!settings->shade;
668         if (settings->decor != -1)
669             self->undecorated = !settings->decor;
670         if (settings->iconic != -1)
671             self->iconic = !!settings->iconic;
672         if (settings->skip_pager != -1)
673             self->skip_pager = !!settings->skip_pager;
674         if (settings->skip_taskbar != -1)
675             self->skip_taskbar = !!settings->skip_taskbar;
676
677         if (settings->max_vert != -1)
678             self->max_vert = !!settings->max_vert;
679         if (settings->max_horz != -1)
680             self->max_vert = !!settings->max_horz;
681
682         if (settings->fullscreen != -1)
683             self->fullscreen = !!settings->fullscreen;
684
685         if (settings->desktop < screen_num_desktops
686             || settings->desktop == DESKTOP_ALL)
687             client_set_desktop(self, settings->desktop, TRUE);
688
689         if (settings->layer == -1) {
690             self->below = TRUE;
691             self->above = FALSE;
692         }
693         else if (settings->layer == 0) {
694             self->below = FALSE;
695             self->above = FALSE;
696         }
697         else if (settings->layer == 1) {
698             self->below = FALSE;
699             self->above = TRUE;
700         }
701     }
702     return settings;
703 }
704
705 static void client_restore_session_state(ObClient *self)
706 {
707     GList *it;
708
709     if (!(it = session_state_find(self)))
710         return;
711
712     self->session = it->data;
713
714     RECT_SET_POINT(self->area, self->session->x, self->session->y);
715     self->positioned = PPosition;
716     if (self->session->w > 0)
717         self->area.width = self->session->w;
718     if (self->session->h > 0)
719         self->area.height = self->session->h;
720     XResizeWindow(ob_display, self->window,
721                   self->area.width, self->area.height);
722
723     self->desktop = (self->session->desktop == DESKTOP_ALL ?
724                      self->session->desktop :
725                      MIN(screen_num_desktops - 1, self->session->desktop));
726     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
727
728     self->shaded = self->session->shaded;
729     self->iconic = self->session->iconic;
730     self->skip_pager = self->session->skip_pager;
731     self->skip_taskbar = self->session->skip_taskbar;
732     self->fullscreen = self->session->fullscreen;
733     self->above = self->session->above;
734     self->below = self->session->below;
735     self->max_horz = self->session->max_horz;
736     self->max_vert = self->session->max_vert;
737 }
738
739 static void client_restore_session_stacking(ObClient *self)
740 {
741     GList *it;
742
743     if (!self->session) return;
744
745     it = g_list_find(session_saved_state, self->session);
746     for (it = g_list_previous(it); it; it = g_list_previous(it)) {
747         GList *cit;
748
749         for (cit = client_list; cit; cit = g_list_next(cit))
750             if (session_state_cmp(it->data, cit->data))
751                 break;
752         if (cit) {
753             client_calc_layer(self);
754             stacking_below(CLIENT_AS_WINDOW(self),
755                            CLIENT_AS_WINDOW(cit->data));
756             break;
757         }
758     }
759 }
760
761 void client_move_onscreen(ObClient *self, gboolean rude)
762 {
763     gint x = self->area.x;
764     gint y = self->area.y;
765     if (client_find_onscreen(self, &x, &y,
766                              self->frame->area.width,
767                              self->frame->area.height, rude)) {
768         client_move(self, x, y);
769     }
770 }
771
772 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
773                               gboolean rude)
774 {
775     Rect *a;
776     gint ox = *x, oy = *y;
777
778     frame_client_gravity(self->frame, x, y); /* get where the frame
779                                                 would be */
780
781     /* XXX watch for xinerama dead areas */
782     /* This makes sure windows aren't entirely outside of the screen so you
783        can't see them at all.
784        It makes sure 10% of the window is on the screen at least. At don't let
785        it move itself off the top of the screen, which would hide the titlebar
786        on you. (The user can still do this if they want too, it's only limiting
787        the application.
788     */
789     if (client_normal(self)) {
790         a = screen_area(self->desktop);
791         if (!self->strut.right &&
792             *x + self->frame->area.width/10 >= a->x + a->width - 1)
793             *x = a->x + a->width - self->frame->area.width/10;
794         if (!self->strut.bottom &&
795             *y + self->frame->area.height/10 >= a->y + a->height - 1)
796             *y = a->y + a->height - self->frame->area.height/10;
797         if (!self->strut.left && *x + self->frame->area.width*9/10 - 1 < a->x)
798             *x = a->x - self->frame->area.width*9/10;
799         if (!self->strut.top && *y + self->frame->area.height*9/10 - 1 < a->y)
800             *y = a->y - self->frame->area.width*9/10;
801     }
802
803     /* This here doesn't let windows even a pixel outside the screen,
804      * when called from client_manage, programs placing themselves are
805      * forced completely onscreen, while things like
806      * xterm -geometry resolution-width/2 will work fine. Trying to
807      * place it completely offscreen will be handled in the above code.
808      * Sorry for this confused comment, i am tired. */
809     if (rude) {
810         /* avoid the xinerama monitor divide while we're at it,
811          * remember to fix the placement stuff to avoid it also and
812          * then remove this XXX */
813         a = screen_area_monitor(self->desktop, client_monitor(self));
814         /* dont let windows map into the strut unless they
815            are bigger than the available area */
816         if (w <= a->width) {
817             if (!self->strut.left && *x < a->x) *x = a->x;
818             if (!self->strut.right && *x + w > a->x + a->width)
819                 *x = a->x + a->width - w;
820         }
821         if (h <= a->height) {
822             if (!self->strut.top && *y < a->y) *y = a->y;
823             if (!self->strut.bottom && *y + h > a->y + a->height)
824                 *y = a->y + a->height - h;
825         }
826     }
827
828     frame_frame_gravity(self->frame, x, y); /* get where the client
829                                                should be */
830
831     return ox != *x || oy != *y;
832 }
833
834 static void client_toggle_border(ObClient *self, gboolean show)
835 {
836     /* adjust our idea of where the client is, based on its border. When the
837        border is removed, the client should now be considered to be in a
838        different position.
839        when re-adding the border to the client, the same operation needs to be
840        reversed. */
841     gint oldx = self->area.x, oldy = self->area.y;
842     gint x = oldx, y = oldy;
843     switch(self->gravity) {
844     default:
845     case NorthWestGravity:
846     case WestGravity:
847     case SouthWestGravity:
848         break;
849     case NorthEastGravity:
850     case EastGravity:
851     case SouthEastGravity:
852         if (show) x -= self->border_width * 2;
853         else      x += self->border_width * 2;
854         break;
855     case NorthGravity:
856     case SouthGravity:
857     case CenterGravity:
858     case ForgetGravity:
859     case StaticGravity:
860         if (show) x -= self->border_width;
861         else      x += self->border_width;
862         break;
863     }
864     switch(self->gravity) {
865     default:
866     case NorthWestGravity:
867     case NorthGravity:
868     case NorthEastGravity:
869         break;
870     case SouthWestGravity:
871     case SouthGravity:
872     case SouthEastGravity:
873         if (show) y -= self->border_width * 2;
874         else      y += self->border_width * 2;
875         break;
876     case WestGravity:
877     case EastGravity:
878     case CenterGravity:
879     case ForgetGravity:
880     case StaticGravity:
881         if (show) y -= self->border_width;
882         else      y += self->border_width;
883         break;
884     }
885     self->area.x = x;
886     self->area.y = y;
887
888     if (show) {
889         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
890
891         /* move the client so it is back it the right spot _with_ its
892            border! */
893         if (x != oldx || y != oldy)
894             XMoveWindow(ob_display, self->window, x, y);
895     } else
896         XSetWindowBorderWidth(ob_display, self->window, 0);
897 }
898
899
900 static void client_get_all(ObClient *self)
901 {
902     client_get_area(self);
903     client_get_mwm_hints(self);
904
905     /* The transient hint is used to pick a type, but the type can also affect
906        transiency (dialogs are always made transients of their group if they
907        have one). This is Havoc's idea, but it is needed to make some apps
908        work right (eg tsclient). */
909     client_update_transient_for(self);
910     client_get_type(self);/* this can change the mwmhints for special cases */
911     client_get_state(self);
912     client_update_transient_for(self);
913
914     client_update_wmhints(self);
915     client_get_startup_id(self);
916     client_get_desktop(self);/* uses transient data/group/startup id if a
917                                 desktop is not specified */
918     client_get_shaped(self);
919
920     client_get_layer(self); /* if layer hasn't been specified, get it from
921                                other sources if possible */
922
923     {
924         /* a couple type-based defaults for new windows */
925
926         /* this makes sure that these windows appear on all desktops */
927         if (self->type == OB_CLIENT_TYPE_DESKTOP)
928             self->desktop = DESKTOP_ALL;
929     }
930
931     client_update_protocols(self);
932
933     client_get_gravity(self); /* get the attribute gravity */
934     client_update_normal_hints(self); /* this may override the attribute
935                                          gravity */
936
937     /* got the type, the mwmhints, the protocols, and the normal hints
938        (min/max sizes), so we're ready to set up the decorations/functions */
939     client_setup_decor_and_functions(self);
940   
941     client_update_title(self);
942     client_update_class(self);
943     client_update_sm_client_id(self);
944     client_update_strut(self);
945     client_update_icons(self);
946     client_update_user_time(self, FALSE);
947 }
948
949 static void client_get_startup_id(ObClient *self)
950 {
951     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
952         if (self->group)
953             PROP_GETS(self->group->leader,
954                       net_startup_id, utf8, &self->startup_id);
955 }
956
957 static void client_get_area(ObClient *self)
958 {
959     XWindowAttributes wattrib;
960     Status ret;
961   
962     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
963     g_assert(ret != BadWindow);
964
965     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
966     self->border_width = wattrib.border_width;
967
968     ob_debug("client area: %d %d  %d %d\n", wattrib.x, wattrib.y,
969              wattrib.width, wattrib.height);
970 }
971
972 static void client_get_desktop(ObClient *self)
973 {
974     guint32 d = screen_num_desktops; /* an always-invalid value */
975
976     if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
977         if (d >= screen_num_desktops && d != DESKTOP_ALL)
978             self->desktop = screen_num_desktops - 1;
979         else
980             self->desktop = d;
981     } else {
982         gboolean trdesk = FALSE;
983
984         if (self->transient_for) {
985             if (self->transient_for != OB_TRAN_GROUP) {
986                 self->desktop = self->transient_for->desktop;
987                 trdesk = TRUE;
988             } else {
989                 GSList *it;
990
991                 for (it = self->group->members; it; it = g_slist_next(it))
992                     if (it->data != self &&
993                         !((ObClient*)it->data)->transient_for) {
994                         self->desktop = ((ObClient*)it->data)->desktop;
995                         trdesk = TRUE;
996                         break;
997                     }
998             }
999         }
1000         if (!trdesk) {
1001             /* try get from the startup-notification protocol */
1002             if (sn_get_desktop(self->startup_id, &self->desktop)) {
1003                 if (self->desktop >= screen_num_desktops &&
1004                     self->desktop != DESKTOP_ALL)
1005                     self->desktop = screen_num_desktops - 1;
1006             } else
1007                 /* defaults to the current desktop */
1008                 self->desktop = screen_desktop;
1009         }
1010     }
1011     if (self->desktop != d) {
1012         /* set the desktop hint, to make sure that it always exists */
1013         PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
1014     }
1015 }
1016
1017 static void client_get_layer(ObClient *self)
1018 {
1019     if (!(self->above || self->below)) {
1020         if (self->group) {
1021             /* apply stuff from the group */
1022             GSList *it;
1023             gint layer = -2;
1024
1025             for (it = self->group->members; it; it = g_slist_next(it)) {
1026                 ObClient *c = it->data;
1027                 if (c != self && !client_search_transient(self, c) &&
1028                     client_normal(self) && client_normal(c))
1029                 {
1030                     layer = MAX(layer,
1031                                 (c->above ? 1 : (c->below ? -1 : 0)));
1032                 }
1033             }
1034             switch (layer) {
1035             case -1:
1036                 self->below = TRUE;
1037                 break;
1038             case -2:
1039             case 0:
1040                 break;
1041             case 1:
1042                 self->above = TRUE;
1043                 break;
1044             default:
1045                 g_assert_not_reached();
1046                 break;
1047             }
1048         }
1049     }
1050 }
1051
1052 static void client_get_state(ObClient *self)
1053 {
1054     guint32 *state;
1055     guint num;
1056   
1057     if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
1058         gulong i;
1059         for (i = 0; i < num; ++i) {
1060             if (state[i] == prop_atoms.net_wm_state_modal)
1061                 self->modal = TRUE;
1062             else if (state[i] == prop_atoms.net_wm_state_shaded)
1063                 self->shaded = TRUE;
1064             else if (state[i] == prop_atoms.net_wm_state_hidden)
1065                 self->iconic = TRUE;
1066             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
1067                 self->skip_taskbar = TRUE;
1068             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
1069                 self->skip_pager = TRUE;
1070             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
1071                 self->fullscreen = TRUE;
1072             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
1073                 self->max_vert = TRUE;
1074             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
1075                 self->max_horz = TRUE;
1076             else if (state[i] == prop_atoms.net_wm_state_above)
1077                 self->above = TRUE;
1078             else if (state[i] == prop_atoms.net_wm_state_below)
1079                 self->below = TRUE;
1080             else if (state[i] == prop_atoms.net_wm_state_demands_attention)
1081                 self->demands_attention = TRUE;
1082             else if (state[i] == prop_atoms.ob_wm_state_undecorated)
1083                 self->undecorated = TRUE;
1084         }
1085
1086         g_free(state);
1087     }
1088 }
1089
1090 static void client_get_shaped(ObClient *self)
1091 {
1092     self->shaped = FALSE;
1093 #ifdef   SHAPE
1094     if (extensions_shape) {
1095         gint foo;
1096         guint ufoo;
1097         gint s;
1098
1099         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
1100
1101         XShapeQueryExtents(ob_display, self->window, &s, &foo,
1102                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1103                            &ufoo);
1104         self->shaped = (s != 0);
1105     }
1106 #endif
1107 }
1108
1109 void client_update_transient_for(ObClient *self)
1110 {
1111     Window t = None;
1112     ObClient *target = NULL;
1113
1114     if (XGetTransientForHint(ob_display, self->window, &t)) {
1115         self->transient = TRUE;
1116         if (t != self->window) { /* cant be transient to itself! */
1117             target = g_hash_table_lookup(window_map, &t);
1118             /* if this happens then we need to check for it*/
1119             g_assert(target != self);
1120             if (target && !WINDOW_IS_CLIENT(target)) {
1121                 /* this can happen when a dialog is a child of
1122                    a dockapp, for example */
1123                 target = NULL;
1124             }
1125
1126             /* THIS IS SO ANNOYING ! ! ! ! Let me explain.... have a seat..
1127
1128                Setting the transient_for to Root is actually illegal, however
1129                applications from time have done this to specify transient for
1130                their group.
1131
1132                Now you can do that by being a TYPE_DIALOG and not setting
1133                the transient_for hint at all on your window. But people still
1134                use Root, and Kwin is very strange in this regard.
1135
1136                KWin 3.0 will not consider windows with transient_for set to
1137                Root as transient for their group *UNLESS* they are also modal.
1138                In that case, it will make them transient for the group. This
1139                leads to all sorts of weird behavior from KDE apps which are
1140                only tested in KWin. I'd like to follow their behavior just to
1141                make this work right with KDE stuff, but that seems wrong.
1142             */
1143             if (!target && self->group) {
1144                 /* not transient to a client, see if it is transient for a
1145                    group */
1146                 if (t == RootWindow(ob_display, ob_screen)) {
1147                     /* window is a transient for its group! */
1148                     target = OB_TRAN_GROUP;
1149                 }
1150             }
1151         }
1152     } else if (self->group) {
1153         if (self->type == OB_CLIENT_TYPE_DIALOG ||
1154             self->type == OB_CLIENT_TYPE_TOOLBAR ||
1155             self->type == OB_CLIENT_TYPE_MENU ||
1156             self->type == OB_CLIENT_TYPE_UTILITY)
1157         {
1158             self->transient = TRUE;
1159             target = OB_TRAN_GROUP;
1160         }
1161     } else
1162         self->transient = FALSE;
1163
1164     /* if anything has changed... */
1165     if (target != self->transient_for) {
1166         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
1167             GSList *it;
1168
1169             /* remove from old parents */
1170             for (it = self->group->members; it; it = g_slist_next(it)) {
1171                 ObClient *c = it->data;
1172                 if (c != self && !c->transient_for)
1173                     c->transients = g_slist_remove(c->transients, self);
1174             }
1175         } else if (self->transient_for != NULL) { /* transient of window */
1176             /* remove from old parent */
1177             self->transient_for->transients =
1178                 g_slist_remove(self->transient_for->transients, self);
1179         }
1180         self->transient_for = target;
1181         if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
1182             GSList *it;
1183
1184             /* add to new parents */
1185             for (it = self->group->members; it; it = g_slist_next(it)) {
1186                 ObClient *c = it->data;
1187                 if (c != self && !c->transient_for)
1188                     c->transients = g_slist_append(c->transients, self);
1189             }
1190
1191             /* remove all transients which are in the group, that causes
1192                circlular pointer hell of doom */
1193             for (it = self->group->members; it; it = g_slist_next(it)) {
1194                 GSList *sit, *next;
1195                 for (sit = self->transients; sit; sit = next) {
1196                     next = g_slist_next(sit);
1197                     if (sit->data == it->data)
1198                         self->transients =
1199                             g_slist_delete_link(self->transients, sit);
1200                 }
1201             }
1202         } else if (self->transient_for != NULL) { /* transient of window */
1203             /* add to new parent */
1204             self->transient_for->transients =
1205                 g_slist_append(self->transient_for->transients, self);
1206         }
1207     }
1208 }
1209
1210 static void client_get_mwm_hints(ObClient *self)
1211 {
1212     guint num;
1213     guint32 *hints;
1214
1215     self->mwmhints.flags = 0; /* default to none */
1216
1217     if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1218                     &hints, &num)) {
1219         if (num >= OB_MWM_ELEMENTS) {
1220             self->mwmhints.flags = hints[0];
1221             self->mwmhints.functions = hints[1];
1222             self->mwmhints.decorations = hints[2];
1223         }
1224         g_free(hints);
1225     }
1226 }
1227
1228 void client_get_type(ObClient *self)
1229 {
1230     guint num, i;
1231     guint32 *val;
1232
1233     self->type = -1;
1234   
1235     if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1236         /* use the first value that we know about in the array */
1237         for (i = 0; i < num; ++i) {
1238             if (val[i] == prop_atoms.net_wm_window_type_desktop)
1239                 self->type = OB_CLIENT_TYPE_DESKTOP;
1240             else if (val[i] == prop_atoms.net_wm_window_type_dock)
1241                 self->type = OB_CLIENT_TYPE_DOCK;
1242             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1243                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1244             else if (val[i] == prop_atoms.net_wm_window_type_menu)
1245                 self->type = OB_CLIENT_TYPE_MENU;
1246             else if (val[i] == prop_atoms.net_wm_window_type_utility)
1247                 self->type = OB_CLIENT_TYPE_UTILITY;
1248             else if (val[i] == prop_atoms.net_wm_window_type_splash)
1249                 self->type = OB_CLIENT_TYPE_SPLASH;
1250             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1251                 self->type = OB_CLIENT_TYPE_DIALOG;
1252             else if (val[i] == prop_atoms.net_wm_window_type_normal)
1253                 self->type = OB_CLIENT_TYPE_NORMAL;
1254             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1255                 /* prevent this window from getting any decor or
1256                    functionality */
1257                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1258                                          OB_MWM_FLAG_DECORATIONS);
1259                 self->mwmhints.decorations = 0;
1260                 self->mwmhints.functions = 0;
1261             }
1262             if (self->type != (ObClientType) -1)
1263                 break; /* grab the first legit type */
1264         }
1265         g_free(val);
1266     }
1267     
1268     if (self->type == (ObClientType) -1) {
1269         /*the window type hint was not set, which means we either classify
1270           ourself as a normal window or a dialog, depending on if we are a
1271           transient. */
1272         if (self->transient)
1273             self->type = OB_CLIENT_TYPE_DIALOG;
1274         else
1275             self->type = OB_CLIENT_TYPE_NORMAL;
1276     }
1277 }
1278
1279 void client_update_protocols(ObClient *self)
1280 {
1281     guint32 *proto;
1282     guint num_return, i;
1283
1284     self->focus_notify = FALSE;
1285     self->delete_window = FALSE;
1286
1287     if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1288         for (i = 0; i < num_return; ++i) {
1289             if (proto[i] == prop_atoms.wm_delete_window) {
1290                 /* this means we can request the window to close */
1291                 self->delete_window = TRUE;
1292             } else if (proto[i] == prop_atoms.wm_take_focus)
1293                 /* if this protocol is requested, then the window will be
1294                    notified whenever we want it to receive focus */
1295                 self->focus_notify = TRUE;
1296         }
1297         g_free(proto);
1298     }
1299 }
1300
1301 static void client_get_gravity(ObClient *self)
1302 {
1303     XWindowAttributes wattrib;
1304     Status ret;
1305
1306     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1307     g_assert(ret != BadWindow);
1308     self->gravity = wattrib.win_gravity;
1309 }
1310
1311 void client_update_normal_hints(ObClient *self)
1312 {
1313     XSizeHints size;
1314     glong ret;
1315     gint oldgravity = self->gravity;
1316
1317     /* defaults */
1318     self->min_ratio = 0.0f;
1319     self->max_ratio = 0.0f;
1320     SIZE_SET(self->size_inc, 1, 1);
1321     SIZE_SET(self->base_size, 0, 0);
1322     SIZE_SET(self->min_size, 0, 0);
1323     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1324
1325     /* get the hints from the window */
1326     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1327         /* normal windows can't request placement! har har
1328         if (!client_normal(self))
1329         */
1330         self->positioned = (size.flags & (PPosition|USPosition));
1331
1332         if (size.flags & PWinGravity) {
1333             self->gravity = size.win_gravity;
1334       
1335             /* if the client has a frame, i.e. has already been mapped and
1336                is changing its gravity */
1337             if (self->frame && self->gravity != oldgravity) {
1338                 /* move our idea of the client's position based on its new
1339                    gravity */
1340                 self->area.x = self->frame->area.x;
1341                 self->area.y = self->frame->area.y;
1342                 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
1343             }
1344         }
1345
1346         if (size.flags & PAspect) {
1347             if (size.min_aspect.y)
1348                 self->min_ratio =
1349                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1350             if (size.max_aspect.y)
1351                 self->max_ratio =
1352                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1353         }
1354
1355         if (size.flags & PMinSize)
1356             SIZE_SET(self->min_size, size.min_width, size.min_height);
1357     
1358         if (size.flags & PMaxSize)
1359             SIZE_SET(self->max_size, size.max_width, size.max_height);
1360     
1361         if (size.flags & PBaseSize)
1362             SIZE_SET(self->base_size, size.base_width, size.base_height);
1363     
1364         if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1365             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1366     }
1367 }
1368
1369 void client_setup_decor_and_functions(ObClient *self)
1370 {
1371     /* start with everything (cept fullscreen) */
1372     self->decorations =
1373         (OB_FRAME_DECOR_TITLEBAR |
1374          OB_FRAME_DECOR_HANDLE |
1375          OB_FRAME_DECOR_GRIPS |
1376          OB_FRAME_DECOR_BORDER |
1377          OB_FRAME_DECOR_ICON |
1378          OB_FRAME_DECOR_ALLDESKTOPS |
1379          OB_FRAME_DECOR_ICONIFY |
1380          OB_FRAME_DECOR_MAXIMIZE |
1381          OB_FRAME_DECOR_SHADE |
1382          OB_FRAME_DECOR_CLOSE);
1383     self->functions =
1384         (OB_CLIENT_FUNC_RESIZE |
1385          OB_CLIENT_FUNC_MOVE |
1386          OB_CLIENT_FUNC_ICONIFY |
1387          OB_CLIENT_FUNC_MAXIMIZE |
1388          OB_CLIENT_FUNC_SHADE |
1389          OB_CLIENT_FUNC_CLOSE);
1390
1391     if (!(self->min_size.width < self->max_size.width ||
1392           self->min_size.height < self->max_size.height))
1393         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1394
1395     switch (self->type) {
1396     case OB_CLIENT_TYPE_NORMAL:
1397         /* normal windows retain all of the possible decorations and
1398            functionality, and are the only windows that you can fullscreen */
1399         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1400         break;
1401
1402     case OB_CLIENT_TYPE_DIALOG:
1403     case OB_CLIENT_TYPE_UTILITY:
1404         /* these windows cannot be maximized */
1405         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1406         break;
1407
1408     case OB_CLIENT_TYPE_MENU:
1409     case OB_CLIENT_TYPE_TOOLBAR:
1410         /* these windows get less functionality */
1411         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE);
1412         break;
1413
1414     case OB_CLIENT_TYPE_DESKTOP:
1415     case OB_CLIENT_TYPE_DOCK:
1416     case OB_CLIENT_TYPE_SPLASH:
1417         /* none of these windows are manipulated by the window manager */
1418         self->decorations = 0;
1419         self->functions = 0;
1420         break;
1421     }
1422
1423     /* Mwm Hints are applied subtractively to what has already been chosen for
1424        decor and functionality */
1425     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1426         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1427             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1428                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1429             {
1430                 /* if the mwm hints request no handle or title, then all
1431                    decorations are disabled, but keep the border if that's
1432                    specified */
1433                 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1434                     self->decorations = OB_FRAME_DECOR_BORDER;
1435                 else
1436                     self->decorations = 0;
1437             }
1438         }
1439     }
1440
1441     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1442         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1443             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1444                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1445             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1446                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1447             /* dont let mwm hints kill any buttons
1448                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1449                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1450                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1451                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1452             */
1453             /* dont let mwm hints kill the close button
1454                if (! (self->mwmhints.functions & MwmFunc_Close))
1455                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1456         }
1457     }
1458
1459     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1460         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1461     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1462         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1463     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1464         self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1465
1466     /* can't maximize without moving/resizing */
1467     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1468           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1469           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1470         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1471         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1472     }
1473
1474     /* kill the handle on fully maxed windows */
1475     if (self->max_vert && self->max_horz)
1476         self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1477
1478     /* finally, the user can have requested no decorations, which overrides
1479        everything (but doesnt give it a border if it doesnt have one) */
1480     if (self->undecorated) {
1481         if (config_theme_keepborder)
1482             self->decorations &= OB_FRAME_DECOR_BORDER;
1483         else
1484             self->decorations = 0;
1485     }
1486
1487     /* if we don't have a titlebar, then we cannot shade! */
1488     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1489         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1490
1491     /* now we need to check against rules for the client's current state */
1492     if (self->fullscreen) {
1493         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1494                             OB_CLIENT_FUNC_FULLSCREEN |
1495                             OB_CLIENT_FUNC_ICONIFY);
1496         self->decorations = 0;
1497     }
1498
1499     client_change_allowed_actions(self);
1500
1501     if (self->frame) {
1502         /* adjust the client's decorations, etc. */
1503         client_reconfigure(self);
1504     }
1505 }
1506
1507 static void client_change_allowed_actions(ObClient *self)
1508 {
1509     gulong actions[9];
1510     gint num = 0;
1511
1512     /* desktop windows are kept on all desktops */
1513     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1514         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1515
1516     if (self->functions & OB_CLIENT_FUNC_SHADE)
1517         actions[num++] = prop_atoms.net_wm_action_shade;
1518     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1519         actions[num++] = prop_atoms.net_wm_action_close;
1520     if (self->functions & OB_CLIENT_FUNC_MOVE)
1521         actions[num++] = prop_atoms.net_wm_action_move;
1522     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1523         actions[num++] = prop_atoms.net_wm_action_minimize;
1524     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1525         actions[num++] = prop_atoms.net_wm_action_resize;
1526     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1527         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1528     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1529         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1530         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1531     }
1532
1533     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1534
1535     /* make sure the window isn't breaking any rules now */
1536
1537     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1538         if (self->frame) client_shade(self, FALSE);
1539         else self->shaded = FALSE;
1540     }
1541     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1542         if (self->frame) client_iconify(self, FALSE, TRUE);
1543         else self->iconic = FALSE;
1544     }
1545     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1546         if (self->frame) client_fullscreen(self, FALSE);
1547         else self->fullscreen = FALSE;
1548     }
1549     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1550                                                          self->max_vert)) {
1551         if (self->frame) client_maximize(self, FALSE, 0);
1552         else self->max_vert = self->max_horz = FALSE;
1553     }
1554 }
1555
1556 void client_reconfigure(ObClient *self)
1557 {
1558     /* by making this pass FALSE for user, we avoid the emacs event storm where
1559        every configurenotify causes an update in its normal hints, i think this
1560        is generally what we want anyways... */
1561     client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
1562                      self->area.width, self->area.height, FALSE, TRUE);
1563 }
1564
1565 void client_update_wmhints(ObClient *self)
1566 {
1567     XWMHints *hints;
1568     GSList *it;
1569
1570     /* assume a window takes input if it doesnt specify */
1571     self->can_focus = TRUE;
1572   
1573     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1574         if (hints->flags & InputHint)
1575             self->can_focus = hints->input;
1576
1577         /* only do this when first managing the window *AND* when we aren't
1578            starting up! */
1579         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1580             if (hints->flags & StateHint)
1581                 self->iconic = hints->initial_state == IconicState;
1582
1583         if (!(hints->flags & WindowGroupHint))
1584             hints->window_group = None;
1585
1586         /* did the group state change? */
1587         if (hints->window_group !=
1588             (self->group ? self->group->leader : None)) {
1589             /* remove from the old group if there was one */
1590             if (self->group != NULL) {
1591                 /* remove transients of the group */
1592                 for (it = self->group->members; it; it = g_slist_next(it))
1593                     self->transients = g_slist_remove(self->transients,
1594                                                       it->data);
1595
1596                 /* remove myself from parents in the group */
1597                 if (self->transient_for == OB_TRAN_GROUP) {
1598                     for (it = self->group->members; it;
1599                          it = g_slist_next(it))
1600                     {
1601                         ObClient *c = it->data;
1602
1603                         if (c != self && !c->transient_for)
1604                             c->transients = g_slist_remove(c->transients,
1605                                                            self);
1606                     }
1607                 }
1608
1609                 group_remove(self->group, self);
1610                 self->group = NULL;
1611             }
1612             if (hints->window_group != None) {
1613                 self->group = group_add(hints->window_group, self);
1614
1615                 /* i can only have transients from the group if i am not
1616                    transient myself */
1617                 if (!self->transient_for) {
1618                     /* add other transients of the group that are already
1619                        set up */
1620                     for (it = self->group->members; it;
1621                          it = g_slist_next(it))
1622                     {
1623                         ObClient *c = it->data;
1624                         if (c != self && c->transient_for == OB_TRAN_GROUP)
1625                             self->transients =
1626                                 g_slist_append(self->transients, c);
1627                     }
1628                 }
1629             }
1630
1631             /* because the self->transient flag wont change from this call,
1632                we don't need to update the window's type and such, only its
1633                transient_for, and the transients lists of other windows in
1634                the group may be affected */
1635             client_update_transient_for(self);
1636         }
1637
1638         /* the WM_HINTS can contain an icon */
1639         client_update_icons(self);
1640
1641         XFree(hints);
1642     }
1643 }
1644
1645 void client_update_title(ObClient *self)
1646 {
1647     GList *it;
1648     guint32 nums;
1649     guint i;
1650     gchar *data = NULL;
1651     gboolean read_title;
1652     gchar *old_title;
1653
1654     old_title = self->title;
1655      
1656     /* try netwm */
1657     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1658         /* try old x stuff */
1659         if (!(PROP_GETS(self->window, wm_name, locale, &data)
1660               || PROP_GETS(self->window, wm_name, utf8, &data))) {
1661             // http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1662             if (self->transient) {
1663                 data = g_strdup("");
1664                 goto no_number;
1665             } else
1666                 data = g_strdup("Unnamed Window");
1667         }
1668     }
1669
1670     if (config_title_number) {
1671
1672         /* did the title change? then reset the title_count */
1673         if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1674             self->title_count = 1;
1675
1676         /* look for duplicates and append a number */
1677         nums = 0;
1678         for (it = client_list; it; it = g_list_next(it))
1679             if (it->data != self) {
1680                 ObClient *c = it->data;
1681
1682                 if (c->title_count == 1) {
1683                     if (!strcmp(c->title, data))
1684                         nums |= 1 << c->title_count;
1685                 } else {
1686                     size_t len;
1687                     gchar *end;
1688
1689                     /* find the beginning of our " - [%u]", this relies on
1690                      that syntax being used */
1691                     end = strrchr(c->title, '-') - 1; 
1692                     len = end - c->title;
1693                     if (!strncmp(c->title, data, len))
1694                         nums |= 1 << c->title_count;
1695                 }
1696             }
1697         /* find first free number */
1698         for (i = 1; i <= 32; ++i)
1699             if (!(nums & (1 << i))) {
1700                 if (self->title_count == 1 || i == 1)
1701                     self->title_count = i;
1702                 break;
1703             }
1704         /* dont display the number for the first window */
1705         if (self->title_count > 1) {
1706             gchar *ndata;
1707             ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1708             g_free(data);
1709             data = ndata;
1710         }
1711     } else
1712         self->title_count = 1;
1713
1714 no_number:
1715     PROP_SETS(self->window, net_wm_visible_name, data);
1716     self->title = data;
1717
1718     if (self->frame)
1719         frame_adjust_title(self->frame);
1720
1721     g_free(old_title);
1722
1723     /* update the icon title */
1724     data = NULL;
1725     g_free(self->icon_title);
1726
1727     read_title = TRUE;
1728     /* try netwm */
1729     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1730         /* try old x stuff */
1731         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data)
1732               || PROP_GETS(self->window, wm_icon_name, utf8, &data))) {
1733             data = g_strdup(self->title);
1734             read_title = FALSE;
1735         }
1736
1737     /* append the title count, dont display the number for the first window.
1738      * We don't need to check for config_title_number here since title_count
1739      * is not set above 1 then. */
1740     if (read_title && self->title_count > 1) {
1741         gchar *newdata;
1742         newdata = g_strdup_printf("%s - [%u]", data, self->title_count);
1743         g_free(data);
1744         data = newdata;
1745     }
1746
1747     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1748
1749     self->icon_title = data;
1750 }
1751
1752 void client_update_class(ObClient *self)
1753 {
1754     gchar **data;
1755     gchar *s;
1756
1757     if (self->name) g_free(self->name);
1758     if (self->class) g_free(self->class);
1759     if (self->role) g_free(self->role);
1760
1761     self->name = self->class = self->role = NULL;
1762
1763     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1764         if (data[0]) {
1765             self->name = g_strdup(data[0]);
1766             if (data[1])
1767                 self->class = g_strdup(data[1]);
1768         }
1769         g_strfreev(data);     
1770     }
1771
1772     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1773         self->role = s;
1774
1775     if (self->name == NULL) self->name = g_strdup("");
1776     if (self->class == NULL) self->class = g_strdup("");
1777     if (self->role == NULL) self->role = g_strdup("");
1778 }
1779
1780 void client_update_strut(ObClient *self)
1781 {
1782     guint num;
1783     guint32 *data;
1784     gboolean got = FALSE;
1785     StrutPartial strut;
1786
1787     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1788                     &data, &num)) {
1789         if (num == 12) {
1790             got = TRUE;
1791             STRUT_PARTIAL_SET(strut,
1792                               data[0], data[2], data[1], data[3],
1793                               data[4], data[5], data[8], data[9],
1794                               data[6], data[7], data[10], data[11]);
1795         }
1796         g_free(data);
1797     }
1798
1799     if (!got &&
1800         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1801         if (num == 4) {
1802             const Rect *a;
1803
1804             got = TRUE;
1805
1806             /* use the screen's width/height */
1807             a = screen_physical_area();
1808
1809             STRUT_PARTIAL_SET(strut,
1810                               data[0], data[2], data[1], data[3],
1811                               a->y, a->y + a->height - 1,
1812                               a->x, a->x + a->width - 1,
1813                               a->y, a->y + a->height - 1,
1814                               a->x, a->x + a->width - 1);
1815         }
1816         g_free(data);
1817     }
1818
1819     if (!got)
1820         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1821                           0, 0, 0, 0, 0, 0, 0, 0);
1822
1823     if (!STRUT_EQUAL(strut, self->strut)) {
1824         self->strut = strut;
1825
1826         /* updating here is pointless while we're being mapped cuz we're not in
1827            the client list yet */
1828         if (self->frame)
1829             screen_update_areas();
1830     }
1831 }
1832
1833 void client_update_icons(ObClient *self)
1834 {
1835     guint num;
1836     guint32 *data;
1837     guint w, h, i, j;
1838
1839     for (i = 0; i < self->nicons; ++i)
1840         g_free(self->icons[i].data);
1841     if (self->nicons > 0)
1842         g_free(self->icons);
1843     self->nicons = 0;
1844
1845     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1846         /* figure out how many valid icons are in here */
1847         i = 0;
1848         while (num - i > 2) {
1849             w = data[i++];
1850             h = data[i++];
1851             i += w * h;
1852             if (i > num || w*h == 0) break;
1853             ++self->nicons;
1854         }
1855
1856         self->icons = g_new(ObClientIcon, self->nicons);
1857     
1858         /* store the icons */
1859         i = 0;
1860         for (j = 0; j < self->nicons; ++j) {
1861             guint x, y, t;
1862
1863             w = self->icons[j].width = data[i++];
1864             h = self->icons[j].height = data[i++];
1865
1866             if (w*h == 0) continue;
1867
1868             self->icons[j].data = g_new(RrPixel32, w * h);
1869             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1870                 if (x >= w) {
1871                     x = 0;
1872                     ++y;
1873                 }
1874                 self->icons[j].data[t] =
1875                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1876                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1877                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1878                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1879             }
1880             g_assert(i <= num);
1881         }
1882
1883         g_free(data);
1884     } else {
1885         XWMHints *hints;
1886
1887         if ((hints = XGetWMHints(ob_display, self->window))) {
1888             if (hints->flags & IconPixmapHint) {
1889                 self->nicons++;
1890                 self->icons = g_new(ObClientIcon, self->nicons);
1891                 xerror_set_ignore(TRUE);
1892                 if (!RrPixmapToRGBA(ob_rr_inst,
1893                                     hints->icon_pixmap,
1894                                     (hints->flags & IconMaskHint ?
1895                                      hints->icon_mask : None),
1896                                     &self->icons[self->nicons-1].width,
1897                                     &self->icons[self->nicons-1].height,
1898                                     &self->icons[self->nicons-1].data)){
1899                     g_free(&self->icons[self->nicons-1]);
1900                     self->nicons--;
1901                 }
1902                 xerror_set_ignore(FALSE);
1903             }
1904             XFree(hints);
1905         }
1906     }
1907
1908     if (self->frame)
1909         frame_adjust_icon(self->frame);
1910 }
1911
1912 void client_update_user_time(ObClient *self, gboolean new_event)
1913 {
1914     guint32 time;
1915
1916     if (PROP_GET32(self->window, net_wm_user_time, cardinal, &time)) {
1917         self->user_time = time;
1918         /* we set this every time, not just when it grows, because in practice
1919            sometimes time goes backwards! (ntpdate.. yay....) so.. if it goes
1920            backward we don't want all windows to stop focusing. we'll just
1921            assume noone is setting times older than the last one, cuz that
1922            would be pretty stupid anyways
1923            However! This is called when a window is mapped to get its user time
1924            but it's an old number, it's not changing it from new user
1925            interaction, so in that case, don't change the last user time.
1926         */
1927         if (new_event)
1928             client_last_user_time = time;
1929
1930         /*ob_debug("window 0x%x user time %u\n", self->window, time);*/
1931     }
1932 }
1933
1934 static void client_change_wm_state(ObClient *self)
1935 {
1936     gulong state[2];
1937     glong old;
1938
1939     old = self->wmstate;
1940
1941     if (self->shaded || self->iconic || !self->frame->visible)
1942         self->wmstate = IconicState;
1943     else
1944         self->wmstate = NormalState;
1945
1946     if (old != self->wmstate) {
1947         PROP_MSG(self->window, kde_wm_change_state,
1948                  self->wmstate, 1, 0, 0);
1949
1950         state[0] = self->wmstate;
1951         state[1] = None;
1952         PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1953     }
1954 }
1955
1956 static void client_change_state(ObClient *self)
1957 {
1958     gulong netstate[11];
1959     guint num;
1960
1961     num = 0;
1962     if (self->modal)
1963         netstate[num++] = prop_atoms.net_wm_state_modal;
1964     if (self->shaded)
1965         netstate[num++] = prop_atoms.net_wm_state_shaded;
1966     if (self->iconic)
1967         netstate[num++] = prop_atoms.net_wm_state_hidden;
1968     if (self->skip_taskbar)
1969         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1970     if (self->skip_pager)
1971         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1972     if (self->fullscreen)
1973         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1974     if (self->max_vert)
1975         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1976     if (self->max_horz)
1977         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1978     if (self->above)
1979         netstate[num++] = prop_atoms.net_wm_state_above;
1980     if (self->below)
1981         netstate[num++] = prop_atoms.net_wm_state_below;
1982     if (self->demands_attention)
1983         netstate[num++] = prop_atoms.net_wm_state_demands_attention;
1984     if (self->undecorated)
1985         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1986     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1987
1988     if (self->frame)
1989         frame_adjust_state(self->frame);
1990 }
1991
1992 ObClient *client_search_focus_tree(ObClient *self)
1993 {
1994     GSList *it;
1995     ObClient *ret;
1996
1997     for (it = self->transients; it; it = g_slist_next(it)) {
1998         if (client_focused(it->data)) return it->data;
1999         if ((ret = client_search_focus_tree(it->data))) return ret;
2000     }
2001     return NULL;
2002 }
2003
2004 ObClient *client_search_focus_tree_full(ObClient *self)
2005 {
2006     if (self->transient_for) {
2007         if (self->transient_for != OB_TRAN_GROUP) {
2008             return client_search_focus_tree_full(self->transient_for);
2009         } else {
2010             GSList *it;
2011             gboolean recursed = FALSE;
2012         
2013             for (it = self->group->members; it; it = g_slist_next(it))
2014                 if (!((ObClient*)it->data)->transient_for) {
2015                     ObClient *c;
2016                     if ((c = client_search_focus_tree_full(it->data)))
2017                         return c;
2018                     recursed = TRUE;
2019                 }
2020             if (recursed)
2021                 return NULL;
2022         }
2023     }
2024
2025     /* this function checks the whole tree, the client_search_focus_tree~
2026        does not, so we need to check this window */
2027     if (client_focused(self))
2028         return self;
2029     return client_search_focus_tree(self);
2030 }
2031
2032 static ObStackingLayer calc_layer(ObClient *self)
2033 {
2034     ObStackingLayer l;
2035
2036     if (self->fullscreen &&
2037         (client_focused(self) || client_search_focus_tree(self)))
2038         l = OB_STACKING_LAYER_FULLSCREEN;
2039     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
2040         l = OB_STACKING_LAYER_DESKTOP;
2041     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2042         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2043         else l = OB_STACKING_LAYER_ABOVE;
2044     }
2045     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2046     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2047     else l = OB_STACKING_LAYER_NORMAL;
2048
2049     return l;
2050 }
2051
2052 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2053                                         ObStackingLayer min, gboolean raised)
2054 {
2055     ObStackingLayer old, own;
2056     GSList *it;
2057
2058     old = self->layer;
2059     own = calc_layer(self);
2060     self->layer = MAX(own, min);
2061
2062     for (it = self->transients; it; it = g_slist_next(it))
2063         client_calc_layer_recursive(it->data, orig,
2064                                     self->layer,
2065                                     raised ? raised : self->layer != old);
2066
2067     if (!raised && self->layer != old)
2068         if (orig->frame) { /* only restack if the original window is managed */
2069             stacking_remove(CLIENT_AS_WINDOW(self));
2070             stacking_add(CLIENT_AS_WINDOW(self));
2071         }
2072 }
2073
2074 void client_calc_layer(ObClient *self)
2075 {
2076     ObClient *orig;
2077     GSList *it;
2078
2079     orig = self;
2080
2081     /* transients take on the layer of their parents */
2082     it = client_search_all_top_parents(self);
2083
2084     for (; it; it = g_slist_next(it))
2085         client_calc_layer_recursive(it->data, orig, 0, FALSE);
2086 }
2087
2088 gboolean client_should_show(ObClient *self)
2089 {
2090     if (self->iconic)
2091         return FALSE;
2092     if (client_normal(self) && screen_showing_desktop)
2093         return FALSE;
2094     /*
2095     if (self->transient_for) {
2096         if (self->transient_for != OB_TRAN_GROUP)
2097             return client_should_show(self->transient_for);
2098         else {
2099             GSList *it;
2100
2101             for (it = self->group->members; it; it = g_slist_next(it)) {
2102                 ObClient *c = it->data;
2103                 if (c != self && !c->transient_for) {
2104                     if (client_should_show(c))
2105                         return TRUE;
2106                 }
2107             }
2108         }
2109     }
2110     */
2111     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2112         return TRUE;
2113     
2114     return FALSE;
2115 }
2116
2117 void client_showhide(ObClient *self)
2118 {
2119
2120     if (client_should_show(self)) {
2121         frame_show(self->frame);
2122     }
2123     else {
2124         frame_hide(self->frame);
2125
2126         /* Fall back focus since we're disappearing */
2127         if (focus_client == self)
2128             client_unfocus(self);
2129     }
2130
2131     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2132        needs to be in IconicState. This includes when it is on another
2133        desktop!
2134     */
2135     client_change_wm_state(self);
2136 }
2137
2138 gboolean client_normal(ObClient *self) {
2139     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2140               self->type == OB_CLIENT_TYPE_DOCK ||
2141               self->type == OB_CLIENT_TYPE_SPLASH);
2142 }
2143
2144 static void client_apply_startup_state(ObClient *self, gint x, gint y)
2145 {
2146     gboolean pos = FALSE; /* has the window's position been configured? */
2147     gint ox, oy;
2148
2149     /* save the position, and set self->area for these to use */
2150     ox = self->area.x;
2151     oy = self->area.y;
2152     self->area.x = x;
2153     self->area.y = y;
2154
2155     /* these are in a carefully crafted order.. */
2156
2157     if (self->iconic) {
2158         self->iconic = FALSE;
2159         client_iconify(self, TRUE, FALSE);
2160     }
2161     if (self->fullscreen) {
2162         self->fullscreen = FALSE;
2163         client_fullscreen(self, TRUE);
2164         pos = TRUE;
2165     }
2166     if (self->undecorated) {
2167         self->undecorated = FALSE;
2168         client_set_undecorated(self, TRUE);
2169     }
2170     if (self->shaded) {
2171         self->shaded = FALSE;
2172         client_shade(self, TRUE);
2173     }
2174     if (self->demands_attention) {
2175         self->demands_attention = FALSE;
2176         client_hilite(self, TRUE);
2177     }
2178   
2179     if (self->max_vert && self->max_horz) {
2180         self->max_vert = self->max_horz = FALSE;
2181         client_maximize(self, TRUE, 0);
2182         pos = TRUE;
2183     } else if (self->max_vert) {
2184         self->max_vert = FALSE;
2185         client_maximize(self, TRUE, 2);
2186         pos = TRUE;
2187     } else if (self->max_horz) {
2188         self->max_horz = FALSE;
2189         client_maximize(self, TRUE, 1);
2190         pos = TRUE;
2191     }
2192
2193     /* if the client didn't get positioned yet, then do so now */
2194     if (!pos && (ox != x || oy != y)) {
2195         /* use the saved position */
2196         self->area.x = ox;
2197         self->area.y = oy;
2198         client_move(self, x, y);
2199     }
2200
2201     /* nothing to do for the other states:
2202        skip_taskbar
2203        skip_pager
2204        modal
2205        above
2206        below
2207     */
2208 }
2209
2210 void client_try_configure(ObClient *self, ObCorner anchor,
2211                           gint *x, gint *y, gint *w, gint *h,
2212                           gint *logicalw, gint *logicalh,
2213                           gboolean user)
2214 {
2215     Rect desired_area = {*x, *y, *w, *h};
2216
2217     /* make the frame recalculate its dimentions n shit without changing
2218        anything visible for real, this way the constraints below can work with
2219        the updated frame dimensions. */
2220     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2221
2222     /* gets the frame's position */
2223     frame_client_gravity(self->frame, x, y);
2224
2225     /* these positions are frame positions, not client positions */
2226
2227     /* set the size and position if fullscreen */
2228     if (self->fullscreen) {
2229         Rect *a;
2230         guint i;
2231
2232         i = screen_find_monitor(&desired_area);
2233         a = screen_physical_area_monitor(i);
2234
2235         *x = a->x;
2236         *y = a->y;
2237         *w = a->width;
2238         *h = a->height;
2239
2240         user = FALSE; /* ignore that increment etc shit when in fullscreen */
2241     } else {
2242         Rect *a;
2243         guint i;
2244
2245         i = screen_find_monitor(&desired_area);
2246         a = screen_area_monitor(self->desktop, i);
2247
2248         /* set the size and position if maximized */
2249         if (self->max_horz) {
2250             *x = a->x;
2251             *w = a->width - self->frame->size.left - self->frame->size.right;
2252         }
2253         if (self->max_vert) {
2254             *y = a->y;
2255             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2256         }
2257     }
2258
2259     /* gets the client's position */
2260     frame_frame_gravity(self->frame, x, y);
2261
2262     /* these override the above states! if you cant move you can't move! */
2263     if (user) {
2264         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2265             *x = self->area.x;
2266             *y = self->area.y;
2267         }
2268         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2269             *w = self->area.width;
2270             *h = self->area.height;
2271         }
2272     }
2273
2274     if (!(*w == self->area.width && *h == self->area.height)) {
2275         gint basew, baseh, minw, minh;
2276
2277         /* base size is substituted with min size if not specified */
2278         if (self->base_size.width || self->base_size.height) {
2279             basew = self->base_size.width;
2280             baseh = self->base_size.height;
2281         } else {
2282             basew = self->min_size.width;
2283             baseh = self->min_size.height;
2284         }
2285         /* min size is substituted with base size if not specified */
2286         if (self->min_size.width || self->min_size.height) {
2287             minw = self->min_size.width;
2288             minh = self->min_size.height;
2289         } else {
2290             minw = self->base_size.width;
2291             minh = self->base_size.height;
2292         }
2293
2294         /* if this is a user-requested resize, then check against min/max
2295            sizes */
2296
2297         /* smaller than min size or bigger than max size? */
2298         if (*w > self->max_size.width) *w = self->max_size.width;
2299         if (*w < minw) *w = minw;
2300         if (*h > self->max_size.height) *h = self->max_size.height;
2301         if (*h < minh) *h = minh;
2302
2303         *w -= basew;
2304         *h -= baseh;
2305
2306         /* keep to the increments */
2307         *w /= self->size_inc.width;
2308         *h /= self->size_inc.height;
2309
2310         /* you cannot resize to nothing */
2311         if (basew + *w < 1) *w = 1 - basew;
2312         if (baseh + *h < 1) *h = 1 - baseh;
2313   
2314         /* save the logical size */
2315         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2316         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2317
2318         *w *= self->size_inc.width;
2319         *h *= self->size_inc.height;
2320
2321         *w += basew;
2322         *h += baseh;
2323
2324         /* adjust the height to match the width for the aspect ratios.
2325            for this, min size is not substituted for base size ever. */
2326         *w -= self->base_size.width;
2327         *h -= self->base_size.height;
2328
2329         if (!self->fullscreen) {
2330             if (self->min_ratio)
2331                 if (*h * self->min_ratio > *w) {
2332                     *h = (gint)(*w / self->min_ratio);
2333
2334                     /* you cannot resize to nothing */
2335                     if (*h < 1) {
2336                         *h = 1;
2337                         *w = (gint)(*h * self->min_ratio);
2338                     }
2339                 }
2340             if (self->max_ratio)
2341                 if (*h * self->max_ratio < *w) {
2342                     *h = (gint)(*w / self->max_ratio);
2343
2344                     /* you cannot resize to nothing */
2345                     if (*h < 1) {
2346                         *h = 1;
2347                         *w = (gint)(*h * self->min_ratio);
2348                     }
2349                 }
2350         }
2351
2352         *w += self->base_size.width;
2353         *h += self->base_size.height;
2354     }
2355
2356     g_assert(*w > 0);
2357     g_assert(*h > 0);
2358
2359     switch (anchor) {
2360     case OB_CORNER_TOPLEFT:
2361         break;
2362     case OB_CORNER_TOPRIGHT:
2363         *x -= *w - self->area.width;
2364         break;
2365     case OB_CORNER_BOTTOMLEFT:
2366         *y -= *h - self->area.height;
2367         break;
2368     case OB_CORNER_BOTTOMRIGHT:
2369         *x -= *w - self->area.width;
2370         *y -= *h - self->area.height;
2371         break;
2372     }
2373 }
2374
2375
2376 void client_configure_full(ObClient *self, ObCorner anchor,
2377                            gint x, gint y, gint w, gint h,
2378                            gboolean user, gboolean final,
2379                            gboolean force_reply)
2380 {
2381     gint oldw, oldh;
2382     gboolean send_resize_client;
2383     gboolean moved = FALSE, resized = FALSE;
2384     guint fdecor = self->frame->decorations;
2385     gboolean fhorz = self->frame->max_horz;
2386     gint logicalw, logicalh;
2387
2388     /* find the new x, y, width, and height (and logical size) */
2389     client_try_configure(self, anchor, &x, &y, &w, &h,
2390                          &logicalw, &logicalh, user);
2391
2392     /* set the logical size if things changed */
2393     if (!(w == self->area.width && h == self->area.height))
2394         SIZE_SET(self->logical_size, logicalw, logicalh);
2395
2396     /* figure out if we moved or resized or what */
2397     moved = x != self->area.x || y != self->area.y;
2398     resized = w != self->area.width || h != self->area.height;
2399
2400     oldw = self->area.width;
2401     oldh = self->area.height;
2402     RECT_SET(self->area, x, y, w, h);
2403
2404     /* for app-requested resizes, always resize if 'resized' is true.
2405        for user-requested ones, only resize if final is true, or when
2406        resizing in redraw mode */
2407     send_resize_client = ((!user && resized) ||
2408                           (user && (final ||
2409                                     (resized && config_resize_redraw))));
2410
2411     /* if the client is enlarging, then resize the client before the frame */
2412     if (send_resize_client && user && (w > oldw || h > oldh))
2413         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2414
2415     /* move/resize the frame to match the request */
2416     if (self->frame) {
2417         if (self->decorations != fdecor || self->max_horz != fhorz)
2418             moved = resized = TRUE;
2419
2420         if (moved || resized)
2421             frame_adjust_area(self->frame, moved, resized, FALSE);
2422
2423         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2424         {
2425             XEvent event;
2426             event.type = ConfigureNotify;
2427             event.xconfigure.display = ob_display;
2428             event.xconfigure.event = self->window;
2429             event.xconfigure.window = self->window;
2430
2431             /* root window real coords */
2432             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2433                 self->border_width;
2434             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2435                 self->border_width;
2436             event.xconfigure.width = w;
2437             event.xconfigure.height = h;
2438             event.xconfigure.border_width = 0;
2439             event.xconfigure.above = self->frame->plate;
2440             event.xconfigure.override_redirect = FALSE;
2441             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2442                        FALSE, StructureNotifyMask, &event);
2443         }
2444     }
2445
2446     /* if the client is shrinking, then resize the frame before the client */
2447     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2448         XResizeWindow(ob_display, self->window, w, h);
2449
2450     XFlush(ob_display);
2451 }
2452
2453 void client_fullscreen(ObClient *self, gboolean fs)
2454 {
2455     gint x, y, w, h;
2456
2457     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2458         self->fullscreen == fs) return;                   /* already done */
2459
2460     self->fullscreen = fs;
2461     client_change_state(self); /* change the state hints on the client */
2462     client_calc_layer(self);   /* and adjust out layer/stacking */
2463
2464     if (fs) {
2465         self->pre_fullscreen_area = self->area;
2466         /* if the window is maximized, its area isn't all that meaningful.
2467            save it's premax area instead. */
2468         if (self->max_horz) {
2469             self->pre_fullscreen_area.x = self->pre_max_area.x;
2470             self->pre_fullscreen_area.width = self->pre_max_area.width;
2471         }
2472         if (self->max_vert) {
2473             self->pre_fullscreen_area.y = self->pre_max_area.y;
2474             self->pre_fullscreen_area.height = self->pre_max_area.height;
2475         }
2476
2477         /* these are not actually used cuz client_configure will set them
2478            as appropriate when the window is fullscreened */
2479         x = y = w = h = 0;
2480     } else {
2481         Rect *a;
2482
2483         if (self->pre_fullscreen_area.width > 0 &&
2484             self->pre_fullscreen_area.height > 0)
2485         {
2486             x = self->pre_fullscreen_area.x;
2487             y = self->pre_fullscreen_area.y;
2488             w = self->pre_fullscreen_area.width;
2489             h = self->pre_fullscreen_area.height;
2490             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2491         } else {
2492             /* pick some fallbacks... */
2493             a = screen_area_monitor(self->desktop, 0);
2494             x = a->x + a->width / 4;
2495             y = a->y + a->height / 4;
2496             w = a->width / 2;
2497             h = a->height / 2;
2498         }
2499     }
2500
2501     client_setup_decor_and_functions(self);
2502
2503     client_move_resize(self, x, y, w, h);
2504
2505     /* try focus us when we go into fullscreen mode */
2506     client_focus(self);
2507 }
2508
2509 static void client_iconify_recursive(ObClient *self,
2510                                      gboolean iconic, gboolean curdesk)
2511 {
2512     GSList *it;
2513     gboolean changed = FALSE;
2514
2515
2516     if (self->iconic != iconic) {
2517         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2518                  self->window);
2519
2520         if (iconic) {
2521             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2522                 self->iconic = iconic;
2523
2524                 /* update the focus lists.. iconic windows go to the bottom of
2525                    the list, put the new iconic window at the 'top of the
2526                    bottom'. */
2527                 focus_order_to_top(self);
2528
2529                 changed = TRUE;
2530             }
2531         } else {
2532             self->iconic = iconic;
2533
2534             if (curdesk)
2535                 client_set_desktop(self, screen_desktop, FALSE);
2536
2537             /* this puts it after the current focused window */
2538             focus_order_remove(self);
2539             focus_order_add_new(self);
2540
2541             changed = TRUE;
2542         }
2543     }
2544
2545     if (changed) {
2546         client_change_state(self);
2547         client_showhide(self);
2548         if (STRUT_EXISTS(self->strut))
2549             screen_update_areas();
2550     }
2551
2552     /* iconify all direct transients */
2553     for (it = self->transients; it; it = g_slist_next(it))
2554         if (it->data != self)
2555             if (client_is_direct_child(self, it->data))
2556                 client_iconify_recursive(it->data, iconic, curdesk);
2557 }
2558
2559 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2560 {
2561     /* move up the transient chain as far as possible first */
2562     self = client_search_top_parent(self);
2563     client_iconify_recursive(self, iconic, curdesk);
2564 }
2565
2566 void client_maximize(ObClient *self, gboolean max, gint dir)
2567 {
2568     gint x, y, w, h;
2569      
2570     g_assert(dir == 0 || dir == 1 || dir == 2);
2571     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2572
2573     /* check if already done */
2574     if (max) {
2575         if (dir == 0 && self->max_horz && self->max_vert) return;
2576         if (dir == 1 && self->max_horz) return;
2577         if (dir == 2 && self->max_vert) return;
2578     } else {
2579         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2580         if (dir == 1 && !self->max_horz) return;
2581         if (dir == 2 && !self->max_vert) return;
2582     }
2583
2584     /* we just tell it to configure in the same place and client_configure
2585        worries about filling the screen with the window */
2586     x = self->area.x;
2587     y = self->area.y;
2588     w = self->area.width;
2589     h = self->area.height;
2590
2591     if (max) {
2592         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2593             RECT_SET(self->pre_max_area,
2594                      self->area.x, self->pre_max_area.y,
2595                      self->area.width, self->pre_max_area.height);
2596         }
2597         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2598             RECT_SET(self->pre_max_area,
2599                      self->pre_max_area.x, self->area.y,
2600                      self->pre_max_area.width, self->area.height);
2601         }
2602     } else {
2603         Rect *a;
2604
2605         a = screen_area_monitor(self->desktop, 0);
2606         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2607             if (self->pre_max_area.width > 0) {
2608                 x = self->pre_max_area.x;
2609                 w = self->pre_max_area.width;
2610
2611                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2612                          0, self->pre_max_area.height);
2613             } else {
2614                 /* pick some fallbacks... */
2615                 x = a->x + a->width / 4;
2616                 w = a->width / 2;
2617             }
2618         }
2619         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2620             if (self->pre_max_area.height > 0) {
2621                 y = self->pre_max_area.y;
2622                 h = self->pre_max_area.height;
2623
2624                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2625                          self->pre_max_area.width, 0);
2626             } else {
2627                 /* pick some fallbacks... */
2628                 y = a->y + a->height / 4;
2629                 h = a->height / 2;
2630             }
2631         }
2632     }
2633
2634     if (dir == 0 || dir == 1) /* horz */
2635         self->max_horz = max;
2636     if (dir == 0 || dir == 2) /* vert */
2637         self->max_vert = max;
2638
2639     client_change_state(self); /* change the state hints on the client */
2640
2641     client_setup_decor_and_functions(self);
2642
2643     client_move_resize(self, x, y, w, h);
2644 }
2645
2646 void client_shade(ObClient *self, gboolean shade)
2647 {
2648     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2649          shade) ||                         /* can't shade */
2650         self->shaded == shade) return;     /* already done */
2651
2652     self->shaded = shade;
2653     client_change_state(self);
2654     client_change_wm_state(self); /* the window is being hidden/shown */
2655     /* resize the frame to just the titlebar */
2656     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2657 }
2658
2659 void client_close(ObClient *self)
2660 {
2661     XEvent ce;
2662
2663     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2664
2665     /* in the case that the client provides no means to requesting that it
2666        close, we just kill it */
2667     if (!self->delete_window)
2668         client_kill(self);
2669     
2670     /*
2671       XXX: itd be cool to do timeouts and shit here for killing the client's
2672       process off
2673       like... if the window is around after 5 seconds, then the close button
2674       turns a nice red, and if this function is called again, the client is
2675       explicitly killed.
2676     */
2677
2678     ce.xclient.type = ClientMessage;
2679     ce.xclient.message_type =  prop_atoms.wm_protocols;
2680     ce.xclient.display = ob_display;
2681     ce.xclient.window = self->window;
2682     ce.xclient.format = 32;
2683     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2684     ce.xclient.data.l[1] = event_curtime;
2685     ce.xclient.data.l[2] = 0l;
2686     ce.xclient.data.l[3] = 0l;
2687     ce.xclient.data.l[4] = 0l;
2688     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2689 }
2690
2691 void client_kill(ObClient *self)
2692 {
2693     XKillClient(ob_display, self->window);
2694 }
2695
2696 void client_hilite(ObClient *self, gboolean hilite)
2697 {
2698     if (self->demands_attention == hilite)
2699         return; /* no change */
2700
2701     /* don't allow focused windows to hilite */
2702     self->demands_attention = hilite && !client_focused(self);
2703     if (self->demands_attention)
2704         frame_flash_start(self->frame);
2705     else
2706         frame_flash_stop(self->frame);
2707     client_change_state(self);
2708 }
2709
2710 void client_set_desktop_recursive(ObClient *self,
2711                                   guint target, gboolean donthide)
2712 {
2713     guint old;
2714     GSList *it;
2715
2716     if (target != self->desktop) {
2717
2718         ob_debug("Setting desktop %u\n", target+1);
2719
2720         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2721
2722         /* remove from the old desktop(s) */
2723         focus_order_remove(self);
2724
2725         old = self->desktop;
2726         self->desktop = target;
2727         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2728         /* the frame can display the current desktop state */
2729         frame_adjust_state(self->frame);
2730         /* 'move' the window to the new desktop */
2731         if (!donthide)
2732             client_showhide(self);
2733         /* raise if it was not already on the desktop */
2734         if (old != DESKTOP_ALL)
2735             client_raise(self);
2736         if (STRUT_EXISTS(self->strut))
2737             screen_update_areas();
2738
2739         /* add to the new desktop(s) */
2740         if (config_focus_new)
2741             focus_order_to_top(self);
2742         else
2743             focus_order_to_bottom(self);
2744     }
2745
2746     /* move all transients */
2747     for (it = self->transients; it; it = g_slist_next(it))
2748         if (it->data != self)
2749             if (client_is_direct_child(self, it->data))
2750                 client_set_desktop_recursive(it->data, target, donthide);
2751 }
2752
2753 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2754 {
2755     self = client_search_top_parent(self);
2756     client_set_desktop_recursive(self, target, donthide);
2757 }
2758
2759 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
2760 {
2761     while (child != parent &&
2762            child->transient_for && child->transient_for != OB_TRAN_GROUP)
2763         child = child->transient_for;
2764     return child == parent;
2765 }
2766
2767 ObClient *client_search_modal_child(ObClient *self)
2768 {
2769     GSList *it;
2770     ObClient *ret;
2771   
2772     for (it = self->transients; it; it = g_slist_next(it)) {
2773         ObClient *c = it->data;
2774         if ((ret = client_search_modal_child(c))) return ret;
2775         if (c->modal) return c;
2776     }
2777     return NULL;
2778 }
2779
2780 gboolean client_validate(ObClient *self)
2781 {
2782     XEvent e; 
2783
2784     XSync(ob_display, FALSE); /* get all events on the server */
2785
2786     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2787         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2788         XPutBackEvent(ob_display, &e);
2789         return FALSE;
2790     }
2791
2792     return TRUE;
2793 }
2794
2795 void client_set_wm_state(ObClient *self, glong state)
2796 {
2797     if (state == self->wmstate) return; /* no change */
2798   
2799     switch (state) {
2800     case IconicState:
2801         client_iconify(self, TRUE, TRUE);
2802         break;
2803     case NormalState:
2804         client_iconify(self, FALSE, TRUE);
2805         break;
2806     }
2807 }
2808
2809 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2810 {
2811     gboolean shaded = self->shaded;
2812     gboolean fullscreen = self->fullscreen;
2813     gboolean undecorated = self->undecorated;
2814     gboolean max_horz = self->max_horz;
2815     gboolean max_vert = self->max_vert;
2816     gboolean modal = self->modal;
2817     gboolean iconic = self->iconic;
2818     gboolean demands_attention = self->demands_attention;
2819     gint i;
2820
2821     if (!(action == prop_atoms.net_wm_state_add ||
2822           action == prop_atoms.net_wm_state_remove ||
2823           action == prop_atoms.net_wm_state_toggle))
2824         /* an invalid action was passed to the client message, ignore it */
2825         return; 
2826
2827     for (i = 0; i < 2; ++i) {
2828         Atom state = i == 0 ? data1 : data2;
2829     
2830         if (!state) continue;
2831
2832         /* if toggling, then pick whether we're adding or removing */
2833         if (action == prop_atoms.net_wm_state_toggle) {
2834             if (state == prop_atoms.net_wm_state_modal)
2835                 action = modal ? prop_atoms.net_wm_state_remove :
2836                     prop_atoms.net_wm_state_add;
2837             else if (state == prop_atoms.net_wm_state_maximized_vert)
2838                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2839                     prop_atoms.net_wm_state_add;
2840             else if (state == prop_atoms.net_wm_state_maximized_horz)
2841                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2842                     prop_atoms.net_wm_state_add;
2843             else if (state == prop_atoms.net_wm_state_shaded)
2844                 action = shaded ? prop_atoms.net_wm_state_remove :
2845                     prop_atoms.net_wm_state_add;
2846             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2847                 action = self->skip_taskbar ?
2848                     prop_atoms.net_wm_state_remove :
2849                     prop_atoms.net_wm_state_add;
2850             else if (state == prop_atoms.net_wm_state_skip_pager)
2851                 action = self->skip_pager ?
2852                     prop_atoms.net_wm_state_remove :
2853                     prop_atoms.net_wm_state_add;
2854             else if (state == prop_atoms.net_wm_state_hidden)
2855                 action = self->iconic ?
2856                     prop_atoms.net_wm_state_remove :
2857                     prop_atoms.net_wm_state_add;
2858             else if (state == prop_atoms.net_wm_state_fullscreen)
2859                 action = fullscreen ?
2860                     prop_atoms.net_wm_state_remove :
2861                     prop_atoms.net_wm_state_add;
2862             else if (state == prop_atoms.net_wm_state_above)
2863                 action = self->above ? prop_atoms.net_wm_state_remove :
2864                     prop_atoms.net_wm_state_add;
2865             else if (state == prop_atoms.net_wm_state_below)
2866                 action = self->below ? prop_atoms.net_wm_state_remove :
2867                     prop_atoms.net_wm_state_add;
2868             else if (state == prop_atoms.net_wm_state_demands_attention)
2869                 action = self->demands_attention ?
2870                     prop_atoms.net_wm_state_remove :
2871                     prop_atoms.net_wm_state_add;
2872             else if (state == prop_atoms.ob_wm_state_undecorated)
2873                 action = undecorated ? prop_atoms.net_wm_state_remove :
2874                     prop_atoms.net_wm_state_add;
2875         }
2876     
2877         if (action == prop_atoms.net_wm_state_add) {
2878             if (state == prop_atoms.net_wm_state_modal) {
2879                 modal = TRUE;
2880             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2881                 max_vert = TRUE;
2882             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2883                 max_horz = TRUE;
2884             } else if (state == prop_atoms.net_wm_state_shaded) {
2885                 shaded = TRUE;
2886             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2887                 self->skip_taskbar = TRUE;
2888             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2889                 self->skip_pager = TRUE;
2890             } else if (state == prop_atoms.net_wm_state_hidden) {
2891                 iconic = TRUE;
2892             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2893                 fullscreen = TRUE;
2894             } else if (state == prop_atoms.net_wm_state_above) {
2895                 self->above = TRUE;
2896                 self->below = FALSE;
2897             } else if (state == prop_atoms.net_wm_state_below) {
2898                 self->above = FALSE;
2899                 self->below = TRUE;
2900             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2901                 demands_attention = TRUE;
2902             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2903                 undecorated = TRUE;
2904             }
2905
2906         } else { /* action == prop_atoms.net_wm_state_remove */
2907             if (state == prop_atoms.net_wm_state_modal) {
2908                 modal = FALSE;
2909             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2910                 max_vert = FALSE;
2911             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2912                 max_horz = FALSE;
2913             } else if (state == prop_atoms.net_wm_state_shaded) {
2914                 shaded = FALSE;
2915             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2916                 self->skip_taskbar = FALSE;
2917             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2918                 self->skip_pager = FALSE;
2919             } else if (state == prop_atoms.net_wm_state_hidden) {
2920                 iconic = FALSE;
2921             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2922                 fullscreen = FALSE;
2923             } else if (state == prop_atoms.net_wm_state_above) {
2924                 self->above = FALSE;
2925             } else if (state == prop_atoms.net_wm_state_below) {
2926                 self->below = FALSE;
2927             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2928                 demands_attention = FALSE;
2929             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2930                 undecorated = FALSE;
2931             }
2932         }
2933     }
2934     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2935         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2936             /* toggling both */
2937             if (max_horz == max_vert) { /* both going the same way */
2938                 client_maximize(self, max_horz, 0);
2939             } else {
2940                 client_maximize(self, max_horz, 1);
2941                 client_maximize(self, max_vert, 2);
2942             }
2943         } else {
2944             /* toggling one */
2945             if (max_horz != self->max_horz)
2946                 client_maximize(self, max_horz, 1);
2947             else
2948                 client_maximize(self, max_vert, 2);
2949         }
2950     }
2951     /* change fullscreen state before shading, as it will affect if the window
2952        can shade or not */
2953     if (fullscreen != self->fullscreen)
2954         client_fullscreen(self, fullscreen);
2955     if (shaded != self->shaded)
2956         client_shade(self, shaded);
2957     if (undecorated != self->undecorated)
2958         client_set_undecorated(self, undecorated);
2959     if (modal != self->modal) {
2960         self->modal = modal;
2961         /* when a window changes modality, then its stacking order with its
2962            transients needs to change */
2963         client_raise(self);
2964     }
2965     if (iconic != self->iconic)
2966         client_iconify(self, iconic, FALSE);
2967
2968     if (demands_attention != self->demands_attention)
2969         client_hilite(self, demands_attention);
2970
2971     client_change_state(self); /* change the hint to reflect these changes */
2972 }
2973
2974 ObClient *client_focus_target(ObClient *self)
2975 {
2976     ObClient *child = NULL;
2977
2978     child = client_search_modal_child(self);
2979     if (child) return child;
2980     return self;
2981 }
2982
2983 gboolean client_can_focus(ObClient *self)
2984 {
2985     XEvent ev;
2986
2987     /* choose the correct target */
2988     self = client_focus_target(self);
2989
2990     if (!self->frame->visible)
2991         return FALSE;
2992
2993     if (!(self->can_focus || self->focus_notify))
2994         return FALSE;
2995
2996     /* do a check to see if the window has already been unmapped or destroyed
2997        do this intelligently while watching out for unmaps we've generated
2998        (ignore_unmaps > 0) */
2999     if (XCheckTypedWindowEvent(ob_display, self->window,
3000                                DestroyNotify, &ev)) {
3001         XPutBackEvent(ob_display, &ev);
3002         return FALSE;
3003     }
3004     while (XCheckTypedWindowEvent(ob_display, self->window,
3005                                   UnmapNotify, &ev)) {
3006         if (self->ignore_unmaps) {
3007             self->ignore_unmaps--;
3008         } else {
3009             XPutBackEvent(ob_display, &ev);
3010             return FALSE;
3011         }
3012     }
3013
3014     return TRUE;
3015 }
3016
3017 gboolean client_focus(ObClient *self)
3018 {
3019     /* choose the correct target */
3020     self = client_focus_target(self);
3021
3022 #if 0
3023     if (!client_validate(self))
3024         return FALSE;
3025 #endif
3026
3027     if (!client_can_focus(self)) {
3028         if (!self->frame->visible) {
3029             /* update the focus lists */
3030             focus_order_to_top(self);
3031         }
3032         return FALSE;
3033     }
3034
3035     ob_debug("Focusing client \"%s\" at time %u\n", self->title, event_curtime);
3036
3037     if (self->can_focus) {
3038         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3039                        event_curtime);
3040     }
3041
3042     if (self->focus_notify) {
3043         XEvent ce;
3044         ce.xclient.type = ClientMessage;
3045         ce.xclient.message_type = prop_atoms.wm_protocols;
3046         ce.xclient.display = ob_display;
3047         ce.xclient.window = self->window;
3048         ce.xclient.format = 32;
3049         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3050         ce.xclient.data.l[1] = event_curtime;
3051         ce.xclient.data.l[2] = 0l;
3052         ce.xclient.data.l[3] = 0l;
3053         ce.xclient.data.l[4] = 0l;
3054         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3055     }
3056
3057 #ifdef DEBUG_FOCUS
3058     ob_debug("%sively focusing %lx at %d\n",
3059              (self->can_focus ? "act" : "pass"),
3060              self->window, (gint) event_curtime);
3061 #endif
3062
3063     /* Cause the FocusIn to come back to us. Important for desktop switches,
3064        since otherwise we'll have no FocusIn on the queue and send it off to
3065        the focus_backup. */
3066     XSync(ob_display, FALSE);
3067     return TRUE;
3068 }
3069
3070 /* Used when the current client is closed or otherwise hidden, focus_last will
3071    then prevent focus from going to the mouse pointer
3072 */
3073 static void client_unfocus(ObClient *self)
3074 {
3075     if (focus_client == self) {
3076 #ifdef DEBUG_FOCUS
3077         ob_debug("client_unfocus for %lx\n", self->window);
3078 #endif
3079         focus_fallback(FALSE);
3080     }
3081 }
3082
3083 void client_activate(ObClient *self, gboolean here, gboolean user, Time time)
3084 {
3085     /* XXX do some stuff here if user is false to determine if we really want
3086        to activate it or not (a parent or group member is currently
3087        active)?
3088     */
3089     ob_debug("Want to activate window 0x%x with time %u (last time %u), "
3090              "source=%s\n",
3091              self->window, time, client_last_user_time,
3092              (user ? "user" : "application"));
3093     if (!user && time && time < client_last_user_time)
3094         client_hilite(self, TRUE);
3095     else {
3096         if (client_normal(self) && screen_showing_desktop)
3097             screen_show_desktop(FALSE);
3098         if (self->iconic)
3099             client_iconify(self, FALSE, here);
3100         if (self->desktop != DESKTOP_ALL &&
3101             self->desktop != screen_desktop) {
3102             if (here)
3103                 client_set_desktop(self, screen_desktop, FALSE);
3104             else
3105                 screen_set_desktop(self->desktop);
3106         } else if (!self->frame->visible)
3107             /* if its not visible for other reasons, then don't mess
3108                with it */
3109             return;
3110         if (self->shaded)
3111             client_shade(self, FALSE);
3112
3113         client_focus(self);
3114
3115         /* we do this an action here. this is rather important. this is because
3116            we want the results from the focus change to take place BEFORE we go
3117            about raising the window. when a fullscreen window loses focus, we
3118            need this or else the raise wont be able to raise above the
3119            to-lose-focus fullscreen window. */
3120         client_raise(self);
3121     }
3122 }
3123
3124 void client_raise(ObClient *self)
3125 {
3126     action_run_string("Raise", self, CurrentTime);
3127 }
3128
3129 void client_lower(ObClient *self)
3130 {
3131     action_run_string("Lower", self, CurrentTime);
3132 }
3133
3134 gboolean client_focused(ObClient *self)
3135 {
3136     return self == focus_client;
3137 }
3138
3139 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3140 {
3141     guint i;
3142     /* si is the smallest image >= req */
3143     /* li is the largest image < req */
3144     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3145
3146     if (!self->nicons) {
3147         ObClientIcon *parent = NULL;
3148
3149         if (self->transient_for) {
3150             if (self->transient_for != OB_TRAN_GROUP)
3151                 parent = client_icon_recursive(self->transient_for, w, h);
3152             else {
3153                 GSList *it;
3154                 for (it = self->group->members; it; it = g_slist_next(it)) {
3155                     ObClient *c = it->data;
3156                     if (c != self && !c->transient_for) {
3157                         if ((parent = client_icon_recursive(c, w, h)))
3158                             break;
3159                     }
3160                 }
3161             }
3162         }
3163         
3164         return parent;
3165     }
3166
3167     for (i = 0; i < self->nicons; ++i) {
3168         size = self->icons[i].width * self->icons[i].height;
3169         if (size < smallest && size >= (unsigned)(w * h)) {
3170             smallest = size;
3171             si = i;
3172         }
3173         if (size > largest && size <= (unsigned)(w * h)) {
3174             largest = size;
3175             li = i;
3176         }
3177     }
3178     if (largest == 0) /* didnt find one smaller than the requested size */
3179         return &self->icons[si];
3180     return &self->icons[li];
3181 }
3182
3183 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3184 {
3185     ObClientIcon *ret;
3186     static ObClientIcon deficon;
3187
3188     if (!(ret = client_icon_recursive(self, w, h))) {
3189         deficon.width = deficon.height = 48;
3190         deficon.data = ob_rr_theme->def_win_icon;
3191         ret = &deficon;
3192     }
3193     return ret;
3194 }
3195
3196 /* this be mostly ripped from fvwm */
3197 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
3198 {
3199     gint my_cx, my_cy, his_cx, his_cy;
3200     gint offset = 0;
3201     gint distance = 0;
3202     gint score, best_score;
3203     ObClient *best_client, *cur;
3204     GList *it;
3205
3206     if(!client_list)
3207         return NULL;
3208
3209     /* first, find the centre coords of the currently focused window */
3210     my_cx = c->frame->area.x + c->frame->area.width / 2;
3211     my_cy = c->frame->area.y + c->frame->area.height / 2;
3212
3213     best_score = -1;
3214     best_client = NULL;
3215
3216     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
3217         cur = it->data;
3218
3219         /* the currently selected window isn't interesting */
3220         if(cur == c)
3221             continue;
3222         if (!client_normal(cur))
3223             continue;
3224         /* using c->desktop instead of screen_desktop doesn't work if the
3225          * current window was omnipresent, hope this doesn't have any other
3226          * side effects */
3227         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3228             continue;
3229         if(cur->iconic)
3230             continue;
3231         if(!(client_focus_target(cur) == cur &&
3232              client_can_focus(cur)))
3233             continue;
3234
3235         /* find the centre coords of this window, from the
3236          * currently focused window's point of view */
3237         his_cx = (cur->frame->area.x - my_cx)
3238             + cur->frame->area.width / 2;
3239         his_cy = (cur->frame->area.y - my_cy)
3240             + cur->frame->area.height / 2;
3241
3242         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3243            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3244             gint tx;
3245             /* Rotate the diagonals 45 degrees counterclockwise.
3246              * To do this, multiply the matrix /+h +h\ with the
3247              * vector (x y).                   \-h +h/
3248              * h = sqrt(0.5). We can set h := 1 since absolute
3249              * distance doesn't matter here. */
3250             tx = his_cx + his_cy;
3251             his_cy = -his_cx + his_cy;
3252             his_cx = tx;
3253         }
3254
3255         switch(dir) {
3256         case OB_DIRECTION_NORTH:
3257         case OB_DIRECTION_SOUTH:
3258         case OB_DIRECTION_NORTHEAST:
3259         case OB_DIRECTION_SOUTHWEST:
3260             offset = (his_cx < 0) ? -his_cx : his_cx;
3261             distance = ((dir == OB_DIRECTION_NORTH ||
3262                          dir == OB_DIRECTION_NORTHEAST) ?
3263                         -his_cy : his_cy);
3264             break;
3265         case OB_DIRECTION_EAST:
3266         case OB_DIRECTION_WEST:
3267         case OB_DIRECTION_SOUTHEAST:
3268         case OB_DIRECTION_NORTHWEST:
3269             offset = (his_cy < 0) ? -his_cy : his_cy;
3270             distance = ((dir == OB_DIRECTION_WEST ||
3271                          dir == OB_DIRECTION_NORTHWEST) ?
3272                         -his_cx : his_cx);
3273             break;
3274         }
3275
3276         /* the target must be in the requested direction */
3277         if(distance <= 0)
3278             continue;
3279
3280         /* Calculate score for this window.  The smaller the better. */
3281         score = distance + offset;
3282
3283         /* windows more than 45 degrees off the direction are
3284          * heavily penalized and will only be chosen if nothing
3285          * else within a million pixels */
3286         if(offset > distance)
3287             score += 1000000;
3288
3289         if(best_score == -1 || score < best_score)
3290             best_client = cur,
3291                 best_score = score;
3292     }
3293
3294     return best_client;
3295 }
3296
3297 void client_set_layer(ObClient *self, gint layer)
3298 {
3299     if (layer < 0) {
3300         self->below = TRUE;
3301         self->above = FALSE;
3302     } else if (layer == 0) {
3303         self->below = self->above = FALSE;
3304     } else {
3305         self->below = FALSE;
3306         self->above = TRUE;
3307     }
3308     client_calc_layer(self);
3309     client_change_state(self); /* reflect this in the state hints */
3310 }
3311
3312 void client_set_undecorated(ObClient *self, gboolean undecorated)
3313 {
3314     if (self->undecorated != undecorated) {
3315         self->undecorated = undecorated;
3316         client_setup_decor_and_functions(self);
3317         /* Make sure the client knows it might have moved. Maybe there is a
3318          * better way of doing this so only one client_configure is sent, but
3319          * since 125 of these are sent per second when moving the window (with
3320          * user = FALSE) i doubt it matters much.
3321          */
3322         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3323                          self->area.width, self->area.height, TRUE, TRUE);
3324         client_change_state(self); /* reflect this in the state hints */
3325     }
3326 }
3327
3328 guint client_monitor(ObClient *self)
3329 {
3330     return screen_find_monitor(&self->frame->area);
3331 }
3332
3333 ObClient *client_search_top_parent(ObClient *self)
3334 {
3335     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3336         self = self->transient_for;
3337     return self;
3338 }
3339
3340 GSList *client_search_all_top_parents(ObClient *self)
3341 {
3342     GSList *ret = NULL;
3343
3344     /* move up the direct transient chain as far as possible */
3345     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3346         self = self->transient_for;
3347
3348     if (!self->transient_for)
3349         ret = g_slist_prepend(ret, self);
3350     else {
3351             GSList *it;
3352
3353             g_assert(self->group);
3354
3355             for (it = self->group->members; it; it = g_slist_next(it)) {
3356                 ObClient *c = it->data;
3357
3358                 if (!c->transient_for)
3359                     ret = g_slist_prepend(ret, c);
3360             }
3361
3362             if (ret == NULL) /* no group parents */
3363                 ret = g_slist_prepend(ret, self);
3364     }
3365
3366     return ret;
3367 }
3368
3369 ObClient *client_search_focus_parent(ObClient *self)
3370 {
3371     if (self->transient_for) {
3372         if (self->transient_for != OB_TRAN_GROUP) {
3373             if (client_focused(self->transient_for))
3374                 return self->transient_for;
3375         } else {
3376             GSList *it;
3377
3378             for (it = self->group->members; it; it = g_slist_next(it)) {
3379                 ObClient *c = it->data;
3380
3381                 /* checking transient_for prevents infinate loops! */
3382                 if (c != self && !c->transient_for)
3383                     if (client_focused(c))
3384                         return c;
3385             }
3386         }
3387     }
3388
3389     return NULL;
3390 }
3391
3392 ObClient *client_search_parent(ObClient *self, ObClient *search)
3393 {
3394     if (self->transient_for) {
3395         if (self->transient_for != OB_TRAN_GROUP) {
3396             if (self->transient_for == search)
3397                 return search;
3398         } else {
3399             GSList *it;
3400
3401             for (it = self->group->members; it; it = g_slist_next(it)) {
3402                 ObClient *c = it->data;
3403
3404                 /* checking transient_for prevents infinate loops! */
3405                 if (c != self && !c->transient_for)
3406                     if (c == search)
3407                         return search;
3408             }
3409         }
3410     }
3411
3412     return NULL;
3413 }
3414
3415 ObClient *client_search_transient(ObClient *self, ObClient *search)
3416 {
3417     GSList *sit;
3418
3419     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3420         if (sit->data == search)
3421             return search;
3422         if (client_search_transient(sit->data, search))
3423             return search;
3424     }
3425     return NULL;
3426 }
3427
3428 void client_update_sm_client_id(ObClient *self)
3429 {
3430     g_free(self->sm_client_id);
3431     self->sm_client_id = NULL;
3432
3433     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3434         self->group)
3435         PROP_GETS(self->group->leader, sm_client_id, locale,
3436                   &self->sm_client_id);
3437 }
3438
3439 #define WANT_EDGE(cur, c) \
3440             if(cur == c)                                                      \
3441                 continue;                                                     \
3442             if(!client_normal(cur))                                   \
3443                 continue;                                                     \
3444             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3445                 continue;                                                     \
3446             if(cur->iconic)                                                   \
3447                 continue;                                                     \
3448             if(cur->layer < c->layer && !config_resist_layers_below)          \
3449                 continue;
3450
3451 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3452             if ((his_edge_start >= my_edge_start && \
3453                  his_edge_start <= my_edge_end) ||  \
3454                 (my_edge_start >= his_edge_start && \
3455                  my_edge_start <= his_edge_end))    \
3456                 dest = his_offset;
3457
3458 /* finds the nearest edge in the given direction from the current client
3459  * note to self: the edge is the -frame- edge (the actual one), not the
3460  * client edge.
3461  */
3462 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3463 {
3464     gint dest, monitor_dest;
3465     gint my_edge_start, my_edge_end, my_offset;
3466     GList *it;
3467     Rect *a, *monitor;
3468     
3469     if(!client_list)
3470         return -1;
3471
3472     a = screen_area(c->desktop);
3473     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3474
3475     switch(dir) {
3476     case OB_DIRECTION_NORTH:
3477         my_edge_start = c->frame->area.x;
3478         my_edge_end = c->frame->area.x + c->frame->area.width;
3479         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3480         
3481         /* default: top of screen */
3482         dest = a->y + (hang ? c->frame->area.height : 0);
3483         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3484         /* if the monitor edge comes before the screen edge, */
3485         /* use that as the destination instead. (For xinerama) */
3486         if (monitor_dest != dest && my_offset > monitor_dest)
3487             dest = monitor_dest; 
3488
3489         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3490             gint his_edge_start, his_edge_end, his_offset;
3491             ObClient *cur = it->data;
3492
3493             WANT_EDGE(cur, c)
3494
3495             his_edge_start = cur->frame->area.x;
3496             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3497             his_offset = cur->frame->area.y + 
3498                          (hang ? 0 : cur->frame->area.height);
3499
3500             if(his_offset + 1 > my_offset)
3501                 continue;
3502
3503             if(his_offset < dest)
3504                 continue;
3505
3506             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3507         }
3508         break;
3509     case OB_DIRECTION_SOUTH:
3510         my_edge_start = c->frame->area.x;
3511         my_edge_end = c->frame->area.x + c->frame->area.width;
3512         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3513
3514         /* default: bottom of screen */
3515         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3516         monitor_dest = monitor->y + monitor->height -
3517                        (hang ? c->frame->area.height : 0);
3518         /* if the monitor edge comes before the screen edge, */
3519         /* use that as the destination instead. (For xinerama) */
3520         if (monitor_dest != dest && my_offset < monitor_dest)
3521             dest = monitor_dest; 
3522
3523         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3524             gint his_edge_start, his_edge_end, his_offset;
3525             ObClient *cur = it->data;
3526