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