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