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