]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
fully maximize windows all the time. size increments are preferred sizes not required...
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43
44 #include <glib.h>
45 #include <X11/Xutil.h>
46
47 /*! The event mask to grab on client windows */
48 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask)
49
50 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
51                                 ButtonMotionMask)
52
53 typedef struct
54 {
55     ObClientDestructor func;
56     gpointer data;
57 } Destructor;
58
59 GList         *client_list           = NULL;
60
61 static GSList *client_destructors    = NULL;
62 static Time    client_last_user_time = CurrentTime;
63
64 static void client_get_all(ObClient *self);
65 static void client_toggle_border(ObClient *self, gboolean show);
66 static void client_get_startup_id(ObClient *self);
67 static void client_get_area(ObClient *self);
68 static void client_get_desktop(ObClient *self);
69 static void client_get_state(ObClient *self);
70 static void client_get_layer(ObClient *self);
71 static void client_get_shaped(ObClient *self);
72 static void client_get_mwm_hints(ObClient *self);
73 static void client_get_gravity(ObClient *self);
74 static void client_change_allowed_actions(ObClient *self);
75 static void client_change_state(ObClient *self);
76 static void client_change_wm_state(ObClient *self);
77 static void client_apply_startup_state(ObClient *self, gint x, gint y);
78 static void client_restore_session_state(ObClient *self);
79 static void client_restore_session_stacking(ObClient *self);
80 static ObAppSettings *client_get_settings_state(ObClient *self);
81 static void client_unfocus(ObClient *self);
82
83 void client_startup(gboolean reconfig)
84 {
85     if (reconfig) return;
86
87     client_set_list();
88 }
89
90 void client_shutdown(gboolean reconfig)
91 {
92 }
93
94 void client_add_destructor(ObClientDestructor func, gpointer data)
95 {
96     Destructor *d = g_new(Destructor, 1);
97     d->func = func;
98     d->data = data;
99     client_destructors = g_slist_prepend(client_destructors, d);
100 }
101
102 void client_remove_destructor(ObClientDestructor func)
103 {
104     GSList *it;
105
106     for (it = client_destructors; it; it = g_slist_next(it)) {
107         Destructor *d = it->data;
108         if (d->func == func) {
109             g_free(d);
110             client_destructors = g_slist_delete_link(client_destructors, it);
111             break;
112         }
113     }
114 }
115
116 void client_set_list()
117 {
118     Window *windows, *win_it;
119     GList *it;
120     guint size = g_list_length(client_list);
121
122     /* create an array of the window ids */
123     if (size > 0) {
124         windows = g_new(Window, size);
125         win_it = windows;
126         for (it = client_list; it; it = g_list_next(it), ++win_it)
127             *win_it = ((ObClient*)it->data)->window;
128     } else
129         windows = NULL;
130
131     PROP_SETA32(RootWindow(ob_display, ob_screen),
132                 net_client_list, window, (gulong*)windows, size);
133
134     if (windows)
135         g_free(windows);
136
137     stacking_set_list();
138 }
139
140 /*
141   void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
142   {
143   GSList *it;
144
145   for (it = self->transients; it; it = g_slist_next(it)) {
146   if (!func(it->data, data)) return;
147   client_foreach_transient(it->data, func, data);
148   }
149   }
150
151   void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
152   {
153   if (self->transient_for) {
154   if (self->transient_for != OB_TRAN_GROUP) {
155   if (!func(self->transient_for, data)) return;
156   client_foreach_ancestor(self->transient_for, func, data);
157   } else {
158   GSList *it;
159
160   for (it = self->group->members; it; it = g_slist_next(it))
161   if (it->data != self &&
162   !((ObClient*)it->data)->transient_for) {
163   if (!func(it->data, data)) return;
164   client_foreach_ancestor(it->data, func, data);
165   }
166   }
167   }
168   }
169 */
170
171 void client_manage_all()
172 {
173     guint i, j, nchild;
174     Window w, *children;
175     XWMHints *wmhints;
176     XWindowAttributes attrib;
177
178     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
179                &w, &w, &children, &nchild);
180
181     /* remove all icon windows from the list */
182     for (i = 0; i < nchild; i++) {
183         if (children[i] == None) continue;
184         wmhints = XGetWMHints(ob_display, children[i]);
185         if (wmhints) {
186             if ((wmhints->flags & IconWindowHint) &&
187                 (wmhints->icon_window != children[i]))
188                 for (j = 0; j < nchild; j++)
189                     if (children[j] == wmhints->icon_window) {
190                         children[j] = None;
191                         break;
192                     }
193             XFree(wmhints);
194         }
195     }
196
197     for (i = 0; i < nchild; ++i) {
198         if (children[i] == None)
199             continue;
200         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
201             if (attrib.override_redirect) continue;
202
203             if (attrib.map_state != IsUnmapped)
204                 client_manage(children[i]);
205         }
206     }
207     XFree(children);
208 }
209
210 void client_manage(Window window)
211 {
212     ObClient *self;
213     XEvent e;
214     XWindowAttributes attrib;
215     XSetWindowAttributes attrib_set;
216     XWMHints *wmhint;
217     gboolean activate = FALSE;
218     ObAppSettings *settings;
219     gint newx, newy;
220
221     grab_server(TRUE);
222
223     /* check if it has already been unmapped by the time we started mapping.
224        the grab does a sync so we don't have to here */
225     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
226         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e))
227     {
228         XPutBackEvent(ob_display, &e);
229
230         ob_debug("Trying to manage unmapped window. Aborting that.\n");
231         grab_server(FALSE);
232         return; /* don't manage it */
233     }
234
235     /* make sure it isn't an override-redirect window */
236     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
237         attrib.override_redirect)
238     {
239         grab_server(FALSE);
240         return; /* don't manage it */
241     }
242   
243     /* is the window a docking app */
244     if ((wmhint = XGetWMHints(ob_display, window))) {
245         if ((wmhint->flags & StateHint) &&
246             wmhint->initial_state == WithdrawnState)
247         {
248             dock_add(window, wmhint);
249             grab_server(FALSE);
250             XFree(wmhint);
251             return;
252         }
253         XFree(wmhint);
254     }
255
256     ob_debug("Managing window: %lx\n", window);
257
258     /* choose the events we want to receive on the CLIENT window */
259     attrib_set.event_mask = CLIENT_EVENTMASK;
260     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
261     XChangeWindowAttributes(ob_display, window,
262                             CWEventMask|CWDontPropagate, &attrib_set);
263
264
265     /* create the ObClient struct, and populate it from the hints on the
266        window */
267     self = g_new0(ObClient, 1);
268     self->obwin.type = Window_Client;
269     self->window = window;
270
271     /* non-zero defaults */
272     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
273     self->layer = -1;
274     self->desktop = screen_num_desktops; /* always an invalid value */
275     self->user_time = client_last_user_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     /* work within the prefered sizes given by the window */
2167     if (!(*w == self->area.width && *h == self->area.height)) {
2168         gint basew, baseh, minw, minh;
2169
2170         /* base size is substituted with min size if not specified */
2171         if (self->base_size.width || self->base_size.height) {
2172             basew = self->base_size.width;
2173             baseh = self->base_size.height;
2174         } else {
2175             basew = self->min_size.width;
2176             baseh = self->min_size.height;
2177         }
2178         /* min size is substituted with base size if not specified */
2179         if (self->min_size.width || self->min_size.height) {
2180             minw = self->min_size.width;
2181             minh = self->min_size.height;
2182         } else {
2183             minw = self->base_size.width;
2184             minh = self->base_size.height;
2185         }
2186
2187         /* if this is a user-requested resize, then check against min/max
2188            sizes */
2189
2190         /* smaller than min size or bigger than max size? */
2191         if (*w > self->max_size.width) *w = self->max_size.width;
2192         if (*w < minw) *w = minw;
2193         if (*h > self->max_size.height) *h = self->max_size.height;
2194         if (*h < minh) *h = minh;
2195
2196         *w -= basew;
2197         *h -= baseh;
2198
2199         /* keep to the increments */
2200         *w /= self->size_inc.width;
2201         *h /= self->size_inc.height;
2202
2203         /* you cannot resize to nothing */
2204         if (basew + *w < 1) *w = 1 - basew;
2205         if (baseh + *h < 1) *h = 1 - baseh;
2206   
2207         /* save the logical size */
2208         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2209         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2210
2211         *w *= self->size_inc.width;
2212         *h *= self->size_inc.height;
2213
2214         *w += basew;
2215         *h += baseh;
2216
2217         /* adjust the height to match the width for the aspect ratios.
2218            for this, min size is not substituted for base size ever. */
2219         *w -= self->base_size.width;
2220         *h -= self->base_size.height;
2221
2222         if (!self->fullscreen) {
2223             if (self->min_ratio)
2224                 if (*h * self->min_ratio > *w) {
2225                     *h = (gint)(*w / self->min_ratio);
2226
2227                     /* you cannot resize to nothing */
2228                     if (*h < 1) {
2229                         *h = 1;
2230                         *w = (gint)(*h * self->min_ratio);
2231                     }
2232                 }
2233             if (self->max_ratio)
2234                 if (*h * self->max_ratio < *w) {
2235                     *h = (gint)(*w / self->max_ratio);
2236
2237                     /* you cannot resize to nothing */
2238                     if (*h < 1) {
2239                         *h = 1;
2240                         *w = (gint)(*h * self->min_ratio);
2241                     }
2242                 }
2243         }
2244
2245         *w += self->base_size.width;
2246         *h += self->base_size.height;
2247     }
2248
2249     /* gets the frame's position */
2250     frame_client_gravity(self->frame, x, y);
2251
2252     /* these positions are frame positions, not client positions */
2253
2254     /* set the size and position if fullscreen */
2255     if (self->fullscreen) {
2256         Rect *a;
2257         guint i;
2258
2259         i = screen_find_monitor(&desired_area);
2260         a = screen_physical_area_monitor(i);
2261
2262         *x = a->x;
2263         *y = a->y;
2264         *w = a->width;
2265         *h = a->height;
2266
2267         user = FALSE; /* ignore if the client can't be moved/resized when it
2268                          is entering fullscreen */
2269     } else if (self->max_horz || self->max_vert) {
2270         Rect *a;
2271         guint i;
2272
2273         i = screen_find_monitor(&desired_area);
2274         a = screen_area_monitor(self->desktop, i);
2275
2276         /* set the size and position if maximized */
2277         if (self->max_horz) {
2278             *x = a->x;
2279             *w = a->width - self->frame->size.left - self->frame->size.right;
2280         }
2281         if (self->max_vert) {
2282             *y = a->y;
2283             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2284         }
2285
2286         /* maximizing is not allowed if the user can't move+resize the window
2287          */
2288     }
2289
2290     /* gets the client's position */
2291     frame_frame_gravity(self->frame, x, y);
2292
2293     /* these override the above states! if you cant move you can't move! */
2294     if (user) {
2295         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2296             *x = self->area.x;
2297             *y = self->area.y;
2298         }
2299         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2300             *w = self->area.width;
2301             *h = self->area.height;
2302         }
2303     }
2304
2305     g_assert(*w > 0);
2306     g_assert(*h > 0);
2307
2308     switch (anchor) {
2309     case OB_CORNER_TOPLEFT:
2310         break;
2311     case OB_CORNER_TOPRIGHT:
2312         *x -= *w - self->area.width;
2313         break;
2314     case OB_CORNER_BOTTOMLEFT:
2315         *y -= *h - self->area.height;
2316         break;
2317     case OB_CORNER_BOTTOMRIGHT:
2318         *x -= *w - self->area.width;
2319         *y -= *h - self->area.height;
2320         break;
2321     }
2322 }
2323
2324
2325 void client_configure_full(ObClient *self, ObCorner anchor,
2326                            gint x, gint y, gint w, gint h,
2327                            gboolean user, gboolean final,
2328                            gboolean force_reply)
2329 {
2330     gint oldw, oldh;
2331     gboolean send_resize_client;
2332     gboolean moved = FALSE, resized = FALSE;
2333     guint fdecor = self->frame->decorations;
2334     gboolean fhorz = self->frame->max_horz;
2335     gint logicalw, logicalh;
2336
2337     /* find the new x, y, width, and height (and logical size) */
2338     client_try_configure(self, anchor, &x, &y, &w, &h,
2339                          &logicalw, &logicalh, user);
2340
2341     /* set the logical size if things changed */
2342     if (!(w == self->area.width && h == self->area.height))
2343         SIZE_SET(self->logical_size, logicalw, logicalh);
2344
2345     /* figure out if we moved or resized or what */
2346     moved = x != self->area.x || y != self->area.y;
2347     resized = w != self->area.width || h != self->area.height;
2348
2349     oldw = self->area.width;
2350     oldh = self->area.height;
2351     RECT_SET(self->area, x, y, w, h);
2352
2353     /* for app-requested resizes, always resize if 'resized' is true.
2354        for user-requested ones, only resize if final is true, or when
2355        resizing in redraw mode */
2356     send_resize_client = ((!user && resized) ||
2357                           (user && (final ||
2358                                     (resized && config_resize_redraw))));
2359
2360     /* if the client is enlarging, then resize the client before the frame */
2361     if (send_resize_client && user && (w > oldw || h > oldh))
2362         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2363
2364     /* move/resize the frame to match the request */
2365     if (self->frame) {
2366         if (self->decorations != fdecor || self->max_horz != fhorz)
2367             moved = resized = TRUE;
2368
2369         if (moved || resized)
2370             frame_adjust_area(self->frame, moved, resized, FALSE);
2371
2372         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2373         {
2374             XEvent event;
2375             event.type = ConfigureNotify;
2376             event.xconfigure.display = ob_display;
2377             event.xconfigure.event = self->window;
2378             event.xconfigure.window = self->window;
2379
2380             /* root window real coords */
2381             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2382                 self->border_width;
2383             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2384                 self->border_width;
2385             event.xconfigure.width = w;
2386             event.xconfigure.height = h;
2387             event.xconfigure.border_width = 0;
2388             event.xconfigure.above = self->frame->plate;
2389             event.xconfigure.override_redirect = FALSE;
2390             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2391                        FALSE, StructureNotifyMask, &event);
2392         }
2393     }
2394
2395     /* if the client is shrinking, then resize the frame before the client */
2396     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2397         XResizeWindow(ob_display, self->window, w, h);
2398
2399     XFlush(ob_display);
2400 }
2401
2402 void client_fullscreen(ObClient *self, gboolean fs)
2403 {
2404     gint x, y, w, h;
2405
2406     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2407         self->fullscreen == fs) return;                   /* already done */
2408
2409     self->fullscreen = fs;
2410     client_change_state(self); /* change the state hints on the client */
2411     client_calc_layer(self);   /* and adjust out layer/stacking */
2412
2413     if (fs) {
2414         self->pre_fullscreen_area = self->area;
2415         /* if the window is maximized, its area isn't all that meaningful.
2416            save it's premax area instead. */
2417         if (self->max_horz) {
2418             self->pre_fullscreen_area.x = self->pre_max_area.x;
2419             self->pre_fullscreen_area.width = self->pre_max_area.width;
2420         }
2421         if (self->max_vert) {
2422             self->pre_fullscreen_area.y = self->pre_max_area.y;
2423             self->pre_fullscreen_area.height = self->pre_max_area.height;
2424         }
2425
2426         /* these are not actually used cuz client_configure will set them
2427            as appropriate when the window is fullscreened */
2428         x = y = w = h = 0;
2429     } else {
2430         Rect *a;
2431
2432         if (self->pre_fullscreen_area.width > 0 &&
2433             self->pre_fullscreen_area.height > 0)
2434         {
2435             x = self->pre_fullscreen_area.x;
2436             y = self->pre_fullscreen_area.y;
2437             w = self->pre_fullscreen_area.width;
2438             h = self->pre_fullscreen_area.height;
2439             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2440         } else {
2441             /* pick some fallbacks... */
2442             a = screen_area_monitor(self->desktop, 0);
2443             x = a->x + a->width / 4;
2444             y = a->y + a->height / 4;
2445             w = a->width / 2;
2446             h = a->height / 2;
2447         }
2448     }
2449
2450     client_setup_decor_and_functions(self);
2451
2452     client_move_resize(self, x, y, w, h);
2453
2454     /* try focus us when we go into fullscreen mode */
2455     client_focus(self);
2456 }
2457
2458 static void client_iconify_recursive(ObClient *self,
2459                                      gboolean iconic, gboolean curdesk)
2460 {
2461     GSList *it;
2462     gboolean changed = FALSE;
2463
2464
2465     if (self->iconic != iconic) {
2466         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2467                  self->window);
2468
2469         if (iconic) {
2470             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2471                 self->iconic = iconic;
2472
2473                 /* update the focus lists.. iconic windows go to the bottom of
2474                    the list, put the new iconic window at the 'top of the
2475                    bottom'. */
2476                 focus_order_to_top(self);
2477
2478                 changed = TRUE;
2479             }
2480         } else {
2481             self->iconic = iconic;
2482
2483             if (curdesk)
2484                 client_set_desktop(self, screen_desktop, FALSE);
2485
2486             /* this puts it after the current focused window */
2487             focus_order_remove(self);
2488             focus_order_add_new(self);
2489
2490             changed = TRUE;
2491         }
2492     }
2493
2494     if (changed) {
2495         client_change_state(self);
2496         client_showhide(self);
2497         if (STRUT_EXISTS(self->strut))
2498             screen_update_areas();
2499     }
2500
2501     /* iconify all direct transients */
2502     for (it = self->transients; it; it = g_slist_next(it))
2503         if (it->data != self)
2504             if (client_is_direct_child(self, it->data))
2505                 client_iconify_recursive(it->data, iconic, curdesk);
2506 }
2507
2508 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2509 {
2510     /* move up the transient chain as far as possible first */
2511     self = client_search_top_parent(self);
2512     client_iconify_recursive(self, iconic, curdesk);
2513 }
2514
2515 void client_maximize(ObClient *self, gboolean max, gint dir)
2516 {
2517     gint x, y, w, h;
2518      
2519     g_assert(dir == 0 || dir == 1 || dir == 2);
2520     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2521
2522     /* check if already done */
2523     if (max) {
2524         if (dir == 0 && self->max_horz && self->max_vert) return;
2525         if (dir == 1 && self->max_horz) return;
2526         if (dir == 2 && self->max_vert) return;
2527     } else {
2528         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2529         if (dir == 1 && !self->max_horz) return;
2530         if (dir == 2 && !self->max_vert) return;
2531     }
2532
2533     /* we just tell it to configure in the same place and client_configure
2534        worries about filling the screen with the window */
2535     x = self->area.x;
2536     y = self->area.y;
2537     w = self->area.width;
2538     h = self->area.height;
2539
2540     if (max) {
2541         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2542             RECT_SET(self->pre_max_area,
2543                      self->area.x, self->pre_max_area.y,
2544                      self->area.width, self->pre_max_area.height);
2545         }
2546         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2547             RECT_SET(self->pre_max_area,
2548                      self->pre_max_area.x, self->area.y,
2549                      self->pre_max_area.width, self->area.height);
2550         }
2551     } else {
2552         Rect *a;
2553
2554         a = screen_area_monitor(self->desktop, 0);
2555         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2556             if (self->pre_max_area.width > 0) {
2557                 x = self->pre_max_area.x;
2558                 w = self->pre_max_area.width;
2559
2560                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2561                          0, self->pre_max_area.height);
2562             } else {
2563                 /* pick some fallbacks... */
2564                 x = a->x + a->width / 4;
2565                 w = a->width / 2;
2566             }
2567         }
2568         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2569             if (self->pre_max_area.height > 0) {
2570                 y = self->pre_max_area.y;
2571                 h = self->pre_max_area.height;
2572
2573                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2574                          self->pre_max_area.width, 0);
2575             } else {
2576                 /* pick some fallbacks... */
2577                 y = a->y + a->height / 4;
2578                 h = a->height / 2;
2579             }
2580         }
2581     }
2582
2583     if (dir == 0 || dir == 1) /* horz */
2584         self->max_horz = max;
2585     if (dir == 0 || dir == 2) /* vert */
2586         self->max_vert = max;
2587
2588     client_change_state(self); /* change the state hints on the client */
2589
2590     client_setup_decor_and_functions(self);
2591
2592     client_move_resize(self, x, y, w, h);
2593 }
2594
2595 void client_shade(ObClient *self, gboolean shade)
2596 {
2597     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2598          shade) ||                         /* can't shade */
2599         self->shaded == shade) return;     /* already done */
2600
2601     self->shaded = shade;
2602     client_change_state(self);
2603     client_change_wm_state(self); /* the window is being hidden/shown */
2604     /* resize the frame to just the titlebar */
2605     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2606 }
2607
2608 void client_close(ObClient *self)
2609 {
2610     XEvent ce;
2611
2612     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2613
2614     /* in the case that the client provides no means to requesting that it
2615        close, we just kill it */
2616     if (!self->delete_window)
2617         client_kill(self);
2618     
2619     /*
2620       XXX: itd be cool to do timeouts and shit here for killing the client's
2621       process off
2622       like... if the window is around after 5 seconds, then the close button
2623       turns a nice red, and if this function is called again, the client is
2624       explicitly killed.
2625     */
2626
2627     ce.xclient.type = ClientMessage;
2628     ce.xclient.message_type =  prop_atoms.wm_protocols;
2629     ce.xclient.display = ob_display;
2630     ce.xclient.window = self->window;
2631     ce.xclient.format = 32;
2632     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2633     ce.xclient.data.l[1] = event_curtime;
2634     ce.xclient.data.l[2] = 0l;
2635     ce.xclient.data.l[3] = 0l;
2636     ce.xclient.data.l[4] = 0l;
2637     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2638 }
2639
2640 void client_kill(ObClient *self)
2641 {
2642     XKillClient(ob_display, self->window);
2643 }
2644
2645 void client_hilite(ObClient *self, gboolean hilite)
2646 {
2647     if (self->demands_attention == hilite)
2648         return; /* no change */
2649
2650     /* don't allow focused windows to hilite */
2651     self->demands_attention = hilite && !client_focused(self);
2652     if (self->demands_attention)
2653         frame_flash_start(self->frame);
2654     else
2655         frame_flash_stop(self->frame);
2656     client_change_state(self);
2657 }
2658
2659 void client_set_desktop_recursive(ObClient *self,
2660                                   guint target, gboolean donthide)
2661 {
2662     guint old;
2663     GSList *it;
2664
2665     if (target != self->desktop) {
2666
2667         ob_debug("Setting desktop %u\n", target+1);
2668
2669         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2670
2671         /* remove from the old desktop(s) */
2672         focus_order_remove(self);
2673
2674         old = self->desktop;
2675         self->desktop = target;
2676         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2677         /* the frame can display the current desktop state */
2678         frame_adjust_state(self->frame);
2679         /* 'move' the window to the new desktop */
2680         if (!donthide)
2681             client_showhide(self);
2682         /* raise if it was not already on the desktop */
2683         if (old != DESKTOP_ALL)
2684             client_raise(self);
2685         if (STRUT_EXISTS(self->strut))
2686             screen_update_areas();
2687
2688         /* add to the new desktop(s) */
2689         if (config_focus_new)
2690             focus_order_to_top(self);
2691         else
2692             focus_order_to_bottom(self);
2693     }
2694
2695     /* move all transients */
2696     for (it = self->transients; it; it = g_slist_next(it))
2697         if (it->data != self)
2698             if (client_is_direct_child(self, it->data))
2699                 client_set_desktop_recursive(it->data, target, donthide);
2700 }
2701
2702 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2703 {
2704     self = client_search_top_parent(self);
2705     client_set_desktop_recursive(self, target, donthide);
2706 }
2707
2708 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
2709 {
2710     while (child != parent &&
2711            child->transient_for && child->transient_for != OB_TRAN_GROUP)
2712         child = child->transient_for;
2713     return child == parent;
2714 }
2715
2716 ObClient *client_search_modal_child(ObClient *self)
2717 {
2718     GSList *it;
2719     ObClient *ret;
2720   
2721     for (it = self->transients; it; it = g_slist_next(it)) {
2722         ObClient *c = it->data;
2723         if ((ret = client_search_modal_child(c))) return ret;
2724         if (c->modal) return c;
2725     }
2726     return NULL;
2727 }
2728
2729 gboolean client_validate(ObClient *self)
2730 {
2731     XEvent e; 
2732
2733     XSync(ob_display, FALSE); /* get all events on the server */
2734
2735     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2736         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2737         XPutBackEvent(ob_display, &e);
2738         return FALSE;
2739     }
2740
2741     return TRUE;
2742 }
2743
2744 void client_set_wm_state(ObClient *self, glong state)
2745 {
2746     if (state == self->wmstate) return; /* no change */
2747   
2748     switch (state) {
2749     case IconicState:
2750         client_iconify(self, TRUE, TRUE);
2751         break;
2752     case NormalState:
2753         client_iconify(self, FALSE, TRUE);
2754         break;
2755     }
2756 }
2757
2758 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2759 {
2760     gboolean shaded = self->shaded;
2761     gboolean fullscreen = self->fullscreen;
2762     gboolean undecorated = self->undecorated;
2763     gboolean max_horz = self->max_horz;
2764     gboolean max_vert = self->max_vert;
2765     gboolean modal = self->modal;
2766     gboolean iconic = self->iconic;
2767     gboolean demands_attention = self->demands_attention;
2768     gint i;
2769
2770     if (!(action == prop_atoms.net_wm_state_add ||
2771           action == prop_atoms.net_wm_state_remove ||
2772           action == prop_atoms.net_wm_state_toggle))
2773         /* an invalid action was passed to the client message, ignore it */
2774         return; 
2775
2776     for (i = 0; i < 2; ++i) {
2777         Atom state = i == 0 ? data1 : data2;
2778     
2779         if (!state) continue;
2780
2781         /* if toggling, then pick whether we're adding or removing */
2782         if (action == prop_atoms.net_wm_state_toggle) {
2783             if (state == prop_atoms.net_wm_state_modal)
2784                 action = modal ? prop_atoms.net_wm_state_remove :
2785                     prop_atoms.net_wm_state_add;
2786             else if (state == prop_atoms.net_wm_state_maximized_vert)
2787                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2788                     prop_atoms.net_wm_state_add;
2789             else if (state == prop_atoms.net_wm_state_maximized_horz)
2790                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2791                     prop_atoms.net_wm_state_add;
2792             else if (state == prop_atoms.net_wm_state_shaded)
2793                 action = shaded ? prop_atoms.net_wm_state_remove :
2794                     prop_atoms.net_wm_state_add;
2795             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2796                 action = self->skip_taskbar ?
2797                     prop_atoms.net_wm_state_remove :
2798                     prop_atoms.net_wm_state_add;
2799             else if (state == prop_atoms.net_wm_state_skip_pager)
2800                 action = self->skip_pager ?
2801                     prop_atoms.net_wm_state_remove :
2802                     prop_atoms.net_wm_state_add;
2803             else if (state == prop_atoms.net_wm_state_hidden)
2804                 action = self->iconic ?
2805                     prop_atoms.net_wm_state_remove :
2806                     prop_atoms.net_wm_state_add;
2807             else if (state == prop_atoms.net_wm_state_fullscreen)
2808                 action = fullscreen ?
2809                     prop_atoms.net_wm_state_remove :
2810                     prop_atoms.net_wm_state_add;
2811             else if (state == prop_atoms.net_wm_state_above)
2812                 action = self->above ? prop_atoms.net_wm_state_remove :
2813                     prop_atoms.net_wm_state_add;
2814             else if (state == prop_atoms.net_wm_state_below)
2815                 action = self->below ? prop_atoms.net_wm_state_remove :
2816                     prop_atoms.net_wm_state_add;
2817             else if (state == prop_atoms.net_wm_state_demands_attention)
2818                 action = self->demands_attention ?
2819                     prop_atoms.net_wm_state_remove :
2820                     prop_atoms.net_wm_state_add;
2821             else if (state == prop_atoms.ob_wm_state_undecorated)
2822                 action = undecorated ? prop_atoms.net_wm_state_remove :
2823                     prop_atoms.net_wm_state_add;
2824         }
2825     
2826         if (action == prop_atoms.net_wm_state_add) {
2827             if (state == prop_atoms.net_wm_state_modal) {
2828                 modal = TRUE;
2829             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2830                 max_vert = TRUE;
2831             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2832                 max_horz = TRUE;
2833             } else if (state == prop_atoms.net_wm_state_shaded) {
2834                 shaded = TRUE;
2835             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2836                 self->skip_taskbar = TRUE;
2837             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2838                 self->skip_pager = TRUE;
2839             } else if (state == prop_atoms.net_wm_state_hidden) {
2840                 iconic = TRUE;
2841             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2842                 fullscreen = TRUE;
2843             } else if (state == prop_atoms.net_wm_state_above) {
2844                 self->above = TRUE;
2845                 self->below = FALSE;
2846             } else if (state == prop_atoms.net_wm_state_below) {
2847                 self->above = FALSE;
2848                 self->below = TRUE;
2849             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2850                 demands_attention = TRUE;
2851             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2852                 undecorated = TRUE;
2853             }
2854
2855         } else { /* action == prop_atoms.net_wm_state_remove */
2856             if (state == prop_atoms.net_wm_state_modal) {
2857                 modal = FALSE;
2858             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2859                 max_vert = FALSE;
2860             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2861                 max_horz = FALSE;
2862             } else if (state == prop_atoms.net_wm_state_shaded) {
2863                 shaded = FALSE;
2864             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2865                 self->skip_taskbar = FALSE;
2866             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2867                 self->skip_pager = FALSE;
2868             } else if (state == prop_atoms.net_wm_state_hidden) {
2869                 iconic = FALSE;
2870             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2871                 fullscreen = FALSE;
2872             } else if (state == prop_atoms.net_wm_state_above) {
2873                 self->above = FALSE;
2874             } else if (state == prop_atoms.net_wm_state_below) {
2875                 self->below = FALSE;
2876             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2877                 demands_attention = FALSE;
2878             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2879                 undecorated = FALSE;
2880             }
2881         }
2882     }
2883     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2884         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2885             /* toggling both */
2886             if (max_horz == max_vert) { /* both going the same way */
2887                 client_maximize(self, max_horz, 0);
2888             } else {
2889                 client_maximize(self, max_horz, 1);
2890                 client_maximize(self, max_vert, 2);
2891             }
2892         } else {
2893             /* toggling one */
2894             if (max_horz != self->max_horz)
2895                 client_maximize(self, max_horz, 1);
2896             else
2897                 client_maximize(self, max_vert, 2);
2898         }
2899     }
2900     /* change fullscreen state before shading, as it will affect if the window
2901        can shade or not */
2902     if (fullscreen != self->fullscreen)
2903         client_fullscreen(self, fullscreen);
2904     if (shaded != self->shaded)
2905         client_shade(self, shaded);
2906     if (undecorated != self->undecorated)
2907         client_set_undecorated(self, undecorated);
2908     if (modal != self->modal) {
2909         self->modal = modal;
2910         /* when a window changes modality, then its stacking order with its
2911            transients needs to change */
2912         client_raise(self);
2913     }
2914     if (iconic != self->iconic)
2915         client_iconify(self, iconic, FALSE);
2916
2917     if (demands_attention != self->demands_attention)
2918         client_hilite(self, demands_attention);
2919
2920     client_change_state(self); /* change the hint to reflect these changes */
2921 }
2922
2923 ObClient *client_focus_target(ObClient *self)
2924 {
2925     ObClient *child = NULL;
2926
2927     child = client_search_modal_child(self);
2928     if (child) return child;
2929     return self;
2930 }
2931
2932 gboolean client_can_focus(ObClient *self)
2933 {
2934     XEvent ev;
2935
2936     /* choose the correct target */
2937     self = client_focus_target(self);
2938
2939     if (!self->frame->visible)
2940         return FALSE;
2941
2942     if (!(self->can_focus || self->focus_notify))
2943         return FALSE;
2944
2945     /* do a check to see if the window has already been unmapped or destroyed
2946        do this intelligently while watching out for unmaps we've generated
2947        (ignore_unmaps > 0) */
2948     if (XCheckTypedWindowEvent(ob_display, self->window,
2949                                DestroyNotify, &ev)) {
2950         XPutBackEvent(ob_display, &ev);
2951         return FALSE;
2952     }
2953     while (XCheckTypedWindowEvent(ob_display, self->window,
2954                                   UnmapNotify, &ev)) {
2955         if (self->ignore_unmaps) {
2956             self->ignore_unmaps--;
2957         } else {
2958             XPutBackEvent(ob_display, &ev);
2959             return FALSE;
2960         }
2961     }
2962
2963     return TRUE;
2964 }
2965
2966 gboolean client_focus(ObClient *self)
2967 {
2968     /* choose the correct target */
2969     self = client_focus_target(self);
2970
2971     if (!client_can_focus(self)) {
2972         if (!self->frame->visible) {
2973             /* update the focus lists */
2974             focus_order_to_top(self);
2975         }
2976         return FALSE;
2977     }
2978
2979     ob_debug("Focusing client \"%s\" at time %u\n", self->title, event_curtime);
2980
2981     if (self->can_focus) {
2982         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
2983                        event_curtime);
2984     }
2985
2986     if (self->focus_notify) {
2987         XEvent ce;
2988         ce.xclient.type = ClientMessage;
2989         ce.xclient.message_type = prop_atoms.wm_protocols;
2990         ce.xclient.display = ob_display;
2991         ce.xclient.window = self->window;
2992         ce.xclient.format = 32;
2993         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2994         ce.xclient.data.l[1] = event_curtime;
2995         ce.xclient.data.l[2] = 0l;
2996         ce.xclient.data.l[3] = 0l;
2997         ce.xclient.data.l[4] = 0l;
2998         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2999     }
3000
3001 #ifdef DEBUG_FOCUS
3002     ob_debug("%sively focusing %lx at %d\n",
3003              (self->can_focus ? "act" : "pass"),
3004              self->window, (gint) event_curtime);
3005 #endif
3006
3007     /* Cause the FocusIn to come back to us. Important for desktop switches,
3008        since otherwise we'll have no FocusIn on the queue and send it off to
3009        the focus_backup. */
3010     XSync(ob_display, FALSE);
3011     return TRUE;
3012 }
3013
3014 /* Used when the current client is closed or otherwise hidden, focus_last will
3015    then prevent focus from going to the mouse pointer
3016 */
3017 static void client_unfocus(ObClient *self)
3018 {
3019     if (focus_client == self) {
3020 #ifdef DEBUG_FOCUS
3021         ob_debug("client_unfocus for %lx\n", self->window);
3022 #endif
3023         focus_fallback(FALSE);
3024     }
3025 }
3026
3027 void client_activate(ObClient *self, gboolean here, gboolean user)
3028 {
3029     /* XXX do some stuff here if user is false to determine if we really want
3030        to activate it or not (a parent or group member is currently
3031        active)?
3032     */
3033     ob_debug("Want to activate window 0x%x with time %u (last time %u), "
3034              "source=%s\n",
3035              self->window, event_curtime, client_last_user_time,
3036              (user ? "user" : "application"));
3037     if (!user && event_curtime &&
3038         !event_time_after(event_curtime, client_last_user_time))
3039     {
3040         client_hilite(self, TRUE);
3041     } else {
3042         if (client_normal(self) && screen_showing_desktop)
3043             screen_show_desktop(FALSE);
3044         if (self->iconic)
3045             client_iconify(self, FALSE, here);
3046         if (self->desktop != DESKTOP_ALL &&
3047             self->desktop != screen_desktop) {
3048             if (here)
3049                 client_set_desktop(self, screen_desktop, FALSE);
3050             else
3051                 screen_set_desktop(self->desktop);
3052         } else if (!self->frame->visible)
3053             /* if its not visible for other reasons, then don't mess
3054                with it */
3055             return;
3056         if (self->shaded)
3057             client_shade(self, FALSE);
3058
3059         client_focus(self);
3060
3061         /* we do this an action here. this is rather important. this is because
3062            we want the results from the focus change to take place BEFORE we go
3063            about raising the window. when a fullscreen window loses focus, we
3064            need this or else the raise wont be able to raise above the
3065            to-lose-focus fullscreen window. */
3066         client_raise(self);
3067     }
3068 }
3069
3070 void client_raise(ObClient *self)
3071 {
3072     action_run_string("Raise", self, CurrentTime);
3073 }
3074
3075 void client_lower(ObClient *self)
3076 {
3077     action_run_string("Lower", self, CurrentTime);
3078 }
3079
3080 gboolean client_focused(ObClient *self)
3081 {
3082     return self == focus_client;
3083 }
3084
3085 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3086 {
3087     guint i;
3088     /* si is the smallest image >= req */
3089     /* li is the largest image < req */
3090     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3091
3092     if (!self->nicons) {
3093         ObClientIcon *parent = NULL;
3094
3095         if (self->transient_for) {
3096             if (self->transient_for != OB_TRAN_GROUP)
3097                 parent = client_icon_recursive(self->transient_for, w, h);
3098             else {
3099                 GSList *it;
3100                 for (it = self->group->members; it; it = g_slist_next(it)) {
3101                     ObClient *c = it->data;
3102                     if (c != self && !c->transient_for) {
3103                         if ((parent = client_icon_recursive(c, w, h)))
3104                             break;
3105                     }
3106                 }
3107             }
3108         }
3109         
3110         return parent;
3111     }
3112
3113     for (i = 0; i < self->nicons; ++i) {
3114         size = self->icons[i].width * self->icons[i].height;
3115         if (size < smallest && size >= (unsigned)(w * h)) {
3116             smallest = size;
3117             si = i;
3118         }
3119         if (size > largest && size <= (unsigned)(w * h)) {
3120             largest = size;
3121             li = i;
3122         }
3123     }
3124     if (largest == 0) /* didnt find one smaller than the requested size */
3125         return &self->icons[si];
3126     return &self->icons[li];
3127 }
3128
3129 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3130 {
3131     ObClientIcon *ret;
3132     static ObClientIcon deficon;
3133
3134     if (!(ret = client_icon_recursive(self, w, h))) {
3135         deficon.width = deficon.height = 48;
3136         deficon.data = ob_rr_theme->def_win_icon;
3137         ret = &deficon;
3138     }
3139     return ret;
3140 }
3141
3142 /* this be mostly ripped from fvwm */
3143 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
3144 {
3145     gint my_cx, my_cy, his_cx, his_cy;
3146     gint offset = 0;
3147     gint distance = 0;
3148     gint score, best_score;
3149     ObClient *best_client, *cur;
3150     GList *it;
3151
3152     if(!client_list)
3153         return NULL;
3154
3155     /* first, find the centre coords of the currently focused window */
3156     my_cx = c->frame->area.x + c->frame->area.width / 2;
3157     my_cy = c->frame->area.y + c->frame->area.height / 2;
3158
3159     best_score = -1;
3160     best_client = NULL;
3161
3162     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
3163         cur = it->data;
3164
3165         /* the currently selected window isn't interesting */
3166         if(cur == c)
3167             continue;
3168         if (!client_normal(cur))
3169             continue;
3170         /* using c->desktop instead of screen_desktop doesn't work if the
3171          * current window was omnipresent, hope this doesn't have any other
3172          * side effects */
3173         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3174             continue;
3175         if(cur->iconic)
3176             continue;
3177         if(!(client_focus_target(cur) == cur &&
3178              client_can_focus(cur)))
3179             continue;
3180
3181         /* find the centre coords of this window, from the
3182          * currently focused window's point of view */
3183         his_cx = (cur->frame->area.x - my_cx)
3184             + cur->frame->area.width / 2;
3185         his_cy = (cur->frame->area.y - my_cy)
3186             + cur->frame->area.height / 2;
3187
3188         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3189            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3190             gint tx;
3191             /* Rotate the diagonals 45 degrees counterclockwise.
3192              * To do this, multiply the matrix /+h +h\ with the
3193              * vector (x y).                   \-h +h/
3194              * h = sqrt(0.5). We can set h := 1 since absolute
3195              * distance doesn't matter here. */
3196             tx = his_cx + his_cy;
3197             his_cy = -his_cx + his_cy;
3198             his_cx = tx;
3199         }
3200
3201         switch(dir) {
3202         case OB_DIRECTION_NORTH:
3203         case OB_DIRECTION_SOUTH:
3204         case OB_DIRECTION_NORTHEAST:
3205         case OB_DIRECTION_SOUTHWEST:
3206             offset = (his_cx < 0) ? -his_cx : his_cx;
3207             distance = ((dir == OB_DIRECTION_NORTH ||
3208                          dir == OB_DIRECTION_NORTHEAST) ?
3209                         -his_cy : his_cy);
3210             break;
3211         case OB_DIRECTION_EAST:
3212         case OB_DIRECTION_WEST:
3213         case OB_DIRECTION_SOUTHEAST:
3214         case OB_DIRECTION_NORTHWEST:
3215             offset = (his_cy < 0) ? -his_cy : his_cy;
3216             distance = ((dir == OB_DIRECTION_WEST ||
3217                          dir == OB_DIRECTION_NORTHWEST) ?
3218                         -his_cx : his_cx);
3219             break;
3220         }
3221
3222         /* the target must be in the requested direction */
3223         if(distance <= 0)
3224             continue;
3225
3226         /* Calculate score for this window.  The smaller the better. */
3227         score = distance + offset;
3228
3229         /* windows more than 45 degrees off the direction are
3230          * heavily penalized and will only be chosen if nothing
3231          * else within a million pixels */
3232         if(offset > distance)
3233             score += 1000000;
3234
3235         if(best_score == -1 || score < best_score)
3236             best_client = cur,
3237                 best_score = score;
3238     }
3239
3240     return best_client;
3241 }
3242
3243 void client_set_layer(ObClient *self, gint layer)
3244 {
3245     if (layer < 0) {
3246         self->below = TRUE;
3247         self->above = FALSE;
3248     } else if (layer == 0) {
3249         self->below = self->above = FALSE;
3250     } else {
3251         self->below = FALSE;
3252         self->above = TRUE;
3253     }
3254     client_calc_layer(self);
3255     client_change_state(self); /* reflect this in the state hints */
3256 }
3257
3258 void client_set_undecorated(ObClient *self, gboolean undecorated)
3259 {
3260     if (self->undecorated != undecorated) {
3261         self->undecorated = undecorated;
3262         client_setup_decor_and_functions(self);
3263         /* Make sure the client knows it might have moved. Maybe there is a
3264          * better way of doing this so only one client_configure is sent, but
3265          * since 125 of these are sent per second when moving the window (with
3266          * user = FALSE) i doubt it matters much.
3267          */
3268         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3269                          self->area.width, self->area.height, TRUE, TRUE);
3270         client_change_state(self); /* reflect this in the state hints */
3271     }
3272 }
3273
3274 guint client_monitor(ObClient *self)
3275 {
3276     return screen_find_monitor(&self->frame->area);
3277 }
3278
3279 ObClient *client_search_top_parent(ObClient *self)
3280 {
3281     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3282         self = self->transient_for;
3283     return self;
3284 }
3285
3286 GSList *client_search_all_top_parents(ObClient *self)
3287 {
3288     GSList *ret = NULL;
3289
3290     /* move up the direct transient chain as far as possible */
3291     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3292         self = self->transient_for;
3293
3294     if (!self->transient_for)
3295         ret = g_slist_prepend(ret, self);
3296     else {
3297             GSList *it;
3298
3299             g_assert(self->group);
3300
3301             for (it = self->group->members; it; it = g_slist_next(it)) {
3302                 ObClient *c = it->data;
3303
3304                 if (!c->transient_for)
3305                     ret = g_slist_prepend(ret, c);
3306             }
3307
3308             if (ret == NULL) /* no group parents */
3309                 ret = g_slist_prepend(ret, self);
3310     }
3311
3312     return ret;
3313 }
3314
3315 ObClient *client_search_focus_parent(ObClient *self)
3316 {
3317     if (self->transient_for) {
3318         if (self->transient_for != OB_TRAN_GROUP) {
3319             if (client_focused(self->transient_for))
3320                 return self->transient_for;
3321         } else {
3322             GSList *it;
3323
3324             for (it = self->group->members; it; it = g_slist_next(it)) {
3325                 ObClient *c = it->data;
3326
3327                 /* checking transient_for prevents infinate loops! */
3328                 if (c != self && !c->transient_for)
3329                     if (client_focused(c))
3330                         return c;
3331             }
3332         }
3333     }
3334
3335     return NULL;
3336 }
3337
3338 ObClient *client_search_parent(ObClient *self, ObClient *search)
3339 {
3340     if (self->transient_for) {
3341         if (self->transient_for != OB_TRAN_GROUP) {
3342             if (self->transient_for == search)
3343                 return search;
3344         } else {
3345             GSList *it;
3346
3347             for (it = self->group->members; it; it = g_slist_next(it)) {
3348                 ObClient *c = it->data;
3349
3350                 /* checking transient_for prevents infinate loops! */
3351                 if (c != self && !c->transient_for)
3352                     if (c == search)
3353                         return search;
3354             }
3355         }
3356     }
3357
3358     return NULL;
3359 }
3360
3361 ObClient *client_search_transient(ObClient *self, ObClient *search)
3362 {
3363     GSList *sit;
3364
3365     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3366         if (sit->data == search)
3367             return search;
3368         if (client_search_transient(sit->data, search))
3369             return search;
3370     }
3371     return NULL;
3372 }
3373
3374 void client_update_sm_client_id(ObClient *self)
3375 {
3376     g_free(self->sm_client_id);
3377     self->sm_client_id = NULL;
3378
3379     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3380         self->group)
3381         PROP_GETS(self->group->leader, sm_client_id, locale,
3382                   &self->sm_client_id);
3383 }
3384
3385 #define WANT_EDGE(cur, c) \
3386             if(cur == c)                                                      \
3387                 continue;                                                     \
3388             if(!client_normal(cur))                                   \
3389                 continue;                                                     \
3390             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3391                 continue;                                                     \
3392             if(cur->iconic)                                                   \
3393                 continue;                                                     \
3394             if(cur->layer < c->layer && !config_resist_layers_below)          \
3395                 continue;
3396
3397 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3398             if ((his_edge_start >= my_edge_start && \
3399                  his_edge_start <= my_edge_end) ||  \
3400                 (my_edge_start >= his_edge_start && \
3401                  my_edge_start <= his_edge_end))    \
3402                 dest = his_offset;
3403
3404 /* finds the nearest edge in the given direction from the current client
3405  * note to self: the edge is the -frame- edge (the actual one), not the
3406  * client edge.
3407  */
3408 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3409 {
3410     gint dest, monitor_dest;
3411     gint my_edge_start, my_edge_end, my_offset;
3412     GList *it;
3413     Rect *a, *monitor;
3414     
3415     if(!client_list)
3416         return -1;
3417
3418     a = screen_area(c->desktop);
3419     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3420
3421     switch(dir) {
3422     case OB_DIRECTION_NORTH:
3423         my_edge_start = c->frame->area.x;
3424         my_edge_end = c->frame->area.x + c->frame->area.width;
3425         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3426         
3427         /* default: top of screen */
3428         dest = a->y + (hang ? c->frame->area.height : 0);
3429         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3430         /* if the monitor edge comes before the screen edge, */
3431         /* use that as the destination instead. (For xinerama) */
3432         if (monitor_dest != dest && my_offset > monitor_dest)
3433             dest = monitor_dest; 
3434
3435         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3436             gint his_edge_start, his_edge_end, his_offset;
3437             ObClient *cur = it->data;
3438
3439             WANT_EDGE(cur, c)
3440
3441             his_edge_start = cur->frame->area.x;
3442             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3443             his_offset = cur->frame->area.y + 
3444                          (hang ? 0 : cur->frame->area.height);
3445
3446             if(his_offset + 1 > my_offset)
3447                 continue;
3448
3449             if(his_offset < dest)
3450                 continue;
3451
3452             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3453         }
3454         break;
3455     case OB_DIRECTION_SOUTH:
3456         my_edge_start = c->frame->area.x;
3457         my_edge_end = c->frame->area.x + c->frame->area.width;
3458         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3459
3460         /* default: bottom of screen */
3461         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3462         monitor_dest = monitor->y + monitor->height -
3463                        (hang ? c->frame->area.height : 0);
3464         /* if the monitor edge comes before the screen edge, */
3465         /* use that as the destination instead. (For xinerama) */
3466         if (monitor_dest != dest && my_offset < monitor_dest)
3467             dest = monitor_dest; 
3468
3469         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3470             gint his_edge_start, his_edge_end, his_offset;
3471             ObClient *cur = it->data;
3472
3473             WANT_EDGE(cur, c)
3474
3475             his_edge_start = cur->frame->area.x;
3476             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3477             his_offset = cur->frame->area.y +
3478                          (hang ? cur->frame->area.height : 0);
3479
3480
3481             if(his_offset - 1 < my_offset)
3482                 continue;
3483             
3484             if(his_offset > dest)
3485                 continue;
3486
3487             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3488         }
3489         break;
3490     case OB_DIRECTION_WEST:
3491         my_edge_start = c->frame->area.y;
3492         my_edge_end = c->frame->area.y + c->frame->area.height;
3493         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3494
3495         /* default: leftmost egde of screen */
3496         dest = a->x + (hang ? c->frame->area.width : 0);
3497         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3498         /* if the monitor edge comes before the screen edge, */
3499         /* use that as the destination instead. (For xinerama) */
3500         if (monitor_dest != dest && my_offset > monitor_dest)
3501             dest = monitor_dest;            
3502
3503         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3504             gint his_edge_start, his_edge_end, his_offset;
3505             ObClient *cur = it->data;
3506
3507             WANT_EDGE(cur, c)
3508
3509             his_edge_start = cur->frame->area.y;
3510             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3511             his_offset = cur->frame->area.x +
3512                          (hang ? 0 : cur->frame->area.width);
3513
3514             if(his_offset + 1 > my_offset)
3515                 continue;
3516
3517             if(his_offset < dest)
3518                 continue;
3519
3520             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3521         }
3522        break;
3523     case OB_DIRECTION_EAST:
3524         my_edge_start = c->frame->area.y;
3525         my_edge_end = c->frame->area.y + c->frame->area.height;
3526         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3527         
3528         /* default: rightmost edge of screen */
3529         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3530         monitor_dest = monitor->x + monitor->width -
3531                        (hang ? c->frame->area.width : 0);
3532         /* if the monitor edge comes before the screen edge, */
3533         /* use that as the destination instead. (For xinerama) */
3534         if (monitor_dest != dest && my_offset < monitor_dest)
3535             dest = monitor_dest;            
3536
3537         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3538             gint his_edge_start, his_edge_end, his_offset;
3539             ObClient *cur = it->data;
3540
3541             WANT_EDGE(cur, c)
3542
3543             his_edge_start = cur->frame->area.y;
3544             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3545             his_offset = cur->frame->area.x +
3546                          (hang ? cur->frame->area.width : 0);
3547
3548             if(his_offset - 1 < my_offset)
3549                 continue;
3550             
3551             if(his_offset > dest)
3552                 continue;
3553
3554             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3555         }
3556         break;
3557     case OB_DIRECTION_NORTHEAST:
3558     case OB_DIRECTION_SOUTHEAST:
3559     case OB_DIRECTION_NORTHWEST:
3560     case OB_DIRECTION_SOUTHWEST:
3561         /* not implemented */
3562     default:
3563         g_assert_not_reached();
3564         dest = 0; /* suppress warning */
3565     }
3566     return dest;
3567 }
3568
3569 ObClient* client_under_pointer()
3570 {
3571     gint x, y;
3572     GList *it;
3573     ObClient *ret = NULL;
3574
3575     if (screen_pointer_pos(&x, &y)) {
3576         for (it = stacking_list; it; it = g_list_next(it)) {
3577             if (WINDOW_IS_CLIENT(it->data)) {
3578                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3579                 if (c->frame->visible &&
3580                     RECT_CONTAINS(c->frame->area, x, y)) {
3581                     ret = c;
3582                     break;
3583                 }
3584             }
3585         }
3586     }
3587     return ret;
3588 }
3589
3590 gboolean client_has_group_siblings(ObClient *self)
3591 {
3592     return self->group && self->group->members->next;
3593 }