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