]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
stacking has been made more reliable with groups and group transients.
[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
2109     /* make the frame recalculate its dimentions n shit without changing
2110        anything visible for real, this way the constraints below can work with
2111        the updated frame dimensions. */
2112     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2113
2114     /* gets the frame's position */
2115     frame_client_gravity(self->frame, &x, &y);
2116
2117     /* these positions are frame positions, not client positions */
2118
2119     /* set the size and position if fullscreen */
2120     if (self->fullscreen) {
2121         Rect *a;
2122         guint i;
2123
2124         i = client_monitor(self);
2125         a = screen_physical_area_monitor(i);
2126
2127         x = a->x;
2128         y = a->y;
2129         w = a->width;
2130         h = a->height;
2131
2132         user = FALSE; /* ignore that increment etc shit when in fullscreen */
2133     } else {
2134         Rect *a;
2135
2136         a = screen_area_monitor(self->desktop, client_monitor(self));
2137
2138         /* set the size and position if maximized */
2139         if (self->max_horz) {
2140             x = a->x;
2141             w = a->width - self->frame->size.left - self->frame->size.right;
2142         }
2143         if (self->max_vert) {
2144             y = a->y;
2145             h = a->height - self->frame->size.top - self->frame->size.bottom;
2146         }
2147     }
2148
2149     /* gets the client's position */
2150     frame_frame_gravity(self->frame, &x, &y);
2151
2152     /* these override the above states! if you cant move you can't move! */
2153     if (user) {
2154         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2155             x = self->area.x;
2156             y = self->area.y;
2157         }
2158         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2159             w = self->area.width;
2160             h = self->area.height;
2161         }
2162     }
2163
2164     if (!(w == self->area.width && h == self->area.height)) {
2165         gint basew, baseh, minw, minh;
2166
2167         /* base size is substituted with min size if not specified */
2168         if (self->base_size.width || self->base_size.height) {
2169             basew = self->base_size.width;
2170             baseh = self->base_size.height;
2171         } else {
2172             basew = self->min_size.width;
2173             baseh = self->min_size.height;
2174         }
2175         /* min size is substituted with base size if not specified */
2176         if (self->min_size.width || self->min_size.height) {
2177             minw = self->min_size.width;
2178             minh = self->min_size.height;
2179         } else {
2180             minw = self->base_size.width;
2181             minh = self->base_size.height;
2182         }
2183
2184         /* if this is a user-requested resize, then check against min/max
2185            sizes */
2186
2187         /* smaller than min size or bigger than max size? */
2188         if (w > self->max_size.width) w = self->max_size.width;
2189         if (w < minw) w = minw;
2190         if (h > self->max_size.height) h = self->max_size.height;
2191         if (h < minh) h = minh;
2192
2193         w -= basew;
2194         h -= baseh;
2195
2196         /* keep to the increments */
2197         w /= self->size_inc.width;
2198         h /= self->size_inc.height;
2199
2200         /* you cannot resize to nothing */
2201         if (basew + w < 1) w = 1 - basew;
2202         if (baseh + h < 1) h = 1 - baseh;
2203   
2204         /* store the logical size */
2205         SIZE_SET(self->logical_size,
2206                  self->size_inc.width > 1 ? w : w + basew,
2207                  self->size_inc.height > 1 ? h : h + baseh);
2208
2209         w *= self->size_inc.width;
2210         h *= self->size_inc.height;
2211
2212         w += basew;
2213         h += baseh;
2214
2215         /* adjust the height to match the width for the aspect ratios.
2216            for this, min size is not substituted for base size ever. */
2217         w -= self->base_size.width;
2218         h -= self->base_size.height;
2219
2220         if (!self->fullscreen) {
2221             if (self->min_ratio)
2222                 if (h * self->min_ratio > w) {
2223                     h = (gint)(w / self->min_ratio);
2224
2225                     /* you cannot resize to nothing */
2226                     if (h < 1) {
2227                         h = 1;
2228                         w = (gint)(h * self->min_ratio);
2229                     }
2230                 }
2231             if (self->max_ratio)
2232                 if (h * self->max_ratio < w) {
2233                     h = (gint)(w / self->max_ratio);
2234
2235                     /* you cannot resize to nothing */
2236                     if (h < 1) {
2237                         h = 1;
2238                         w = (gint)(h * self->min_ratio);
2239                     }
2240                 }
2241         }
2242
2243         w += self->base_size.width;
2244         h += self->base_size.height;
2245     }
2246
2247     g_assert(w > 0);
2248     g_assert(h > 0);
2249
2250     switch (anchor) {
2251     case OB_CORNER_TOPLEFT:
2252         break;
2253     case OB_CORNER_TOPRIGHT:
2254         x -= w - self->area.width;
2255         break;
2256     case OB_CORNER_BOTTOMLEFT:
2257         y -= h - self->area.height;
2258         break;
2259     case OB_CORNER_BOTTOMRIGHT:
2260         x -= w - self->area.width;
2261         y -= h - self->area.height;
2262         break;
2263     }
2264
2265     moved = x != self->area.x || y != self->area.y;
2266     resized = w != self->area.width || h != self->area.height;
2267
2268     oldw = self->area.width;
2269     oldh = self->area.height;
2270     RECT_SET(self->area, x, y, w, h);
2271
2272     /* for app-requested resizes, always resize if 'resized' is true.
2273        for user-requested ones, only resize if final is true, or when
2274        resizing in redraw mode */
2275     send_resize_client = ((!user && resized) ||
2276                           (user && (final ||
2277                                     (resized && config_resize_redraw))));
2278
2279     /* if the client is enlarging, then resize the client before the frame */
2280     if (send_resize_client && user && (w > oldw || h > oldh))
2281         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2282
2283     /* move/resize the frame to match the request */
2284     if (self->frame) {
2285         if (self->decorations != fdecor || self->max_horz != fhorz)
2286             moved = resized = TRUE;
2287
2288         if (moved || resized)
2289             frame_adjust_area(self->frame, moved, resized, FALSE);
2290
2291         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2292         {
2293             XEvent event;
2294             event.type = ConfigureNotify;
2295             event.xconfigure.display = ob_display;
2296             event.xconfigure.event = self->window;
2297             event.xconfigure.window = self->window;
2298
2299             /* root window real coords */
2300             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2301                 self->border_width;
2302             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2303                 self->border_width;
2304             event.xconfigure.width = w;
2305             event.xconfigure.height = h;
2306             event.xconfigure.border_width = 0;
2307             event.xconfigure.above = self->frame->plate;
2308             event.xconfigure.override_redirect = FALSE;
2309             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2310                        FALSE, StructureNotifyMask, &event);
2311         }
2312     }
2313
2314     /* if the client is shrinking, then resize the frame before the client */
2315     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2316         XResizeWindow(ob_display, self->window, w, h);
2317
2318     XFlush(ob_display);
2319 }
2320
2321 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2322 {
2323     gint x, y, w, h;
2324
2325     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2326         self->fullscreen == fs) return;                   /* already done */
2327
2328     self->fullscreen = fs;
2329     client_change_state(self); /* change the state hints on the client */
2330     client_calc_layer(self);   /* and adjust out layer/stacking */
2331
2332     if (fs) {
2333         if (savearea)
2334             self->pre_fullscreen_area = self->area;
2335
2336         /* these are not actually used cuz client_configure will set them
2337            as appropriate when the window is fullscreened */
2338         x = y = w = h = 0;
2339     } else {
2340         Rect *a;
2341
2342         if (self->pre_fullscreen_area.width > 0 &&
2343             self->pre_fullscreen_area.height > 0)
2344         {
2345             x = self->pre_fullscreen_area.x;
2346             y = self->pre_fullscreen_area.y;
2347             w = self->pre_fullscreen_area.width;
2348             h = self->pre_fullscreen_area.height;
2349             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2350         } else {
2351             /* pick some fallbacks... */
2352             a = screen_area_monitor(self->desktop, 0);
2353             x = a->x + a->width / 4;
2354             y = a->y + a->height / 4;
2355             w = a->width / 2;
2356             h = a->height / 2;
2357         }
2358     }
2359
2360     client_setup_decor_and_functions(self);
2361
2362     client_move_resize(self, x, y, w, h);
2363
2364     /* try focus us when we go into fullscreen mode */
2365     client_focus(self);
2366 }
2367
2368 static void client_iconify_recursive(ObClient *self,
2369                                      gboolean iconic, gboolean curdesk)
2370 {
2371     GSList *it;
2372     gboolean changed = FALSE;
2373
2374
2375     if (self->iconic != iconic) {
2376         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2377                  self->window);
2378
2379         self->iconic = iconic;
2380
2381         if (iconic) {
2382             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2383                 glong old;
2384
2385                 old = self->wmstate;
2386                 self->wmstate = IconicState;
2387                 if (old != self->wmstate)
2388                     PROP_MSG(self->window, kde_wm_change_state,
2389                              self->wmstate, 1, 0, 0);
2390
2391                 /* update the focus lists.. iconic windows go to the bottom of
2392                    the list, put the new iconic window at the 'top of the
2393                    bottom'. */
2394                 focus_order_to_top(self);
2395
2396                 changed = TRUE;
2397             }
2398         } else {
2399             glong old;
2400
2401             if (curdesk)
2402                 client_set_desktop(self, screen_desktop, FALSE);
2403
2404             old = self->wmstate;
2405             self->wmstate = self->shaded ? IconicState : NormalState;
2406             if (old != self->wmstate)
2407                 PROP_MSG(self->window, kde_wm_change_state,
2408                          self->wmstate, 1, 0, 0);
2409
2410             /* this puts it after the current focused window */
2411             focus_order_remove(self);
2412             focus_order_add_new(self);
2413
2414             changed = TRUE;
2415         }
2416     }
2417
2418     if (changed) {
2419         client_change_state(self);
2420         client_showhide(self);
2421         if (STRUT_EXISTS(self->strut))
2422             screen_update_areas();
2423     }
2424
2425     /* iconify all transients */
2426     for (it = self->transients; it; it = g_slist_next(it))
2427         if (it->data != self) client_iconify_recursive(it->data,
2428                                                        iconic, curdesk);
2429 }
2430
2431 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2432 {
2433     GSList *it;
2434
2435     /* move up the transient chain as far as possible first */
2436     it = client_search_top_transients(self);
2437
2438     for (; it; it = g_slist_next(it))
2439     client_iconify_recursive(it->data, iconic, curdesk);
2440 }
2441
2442 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2443 {
2444     gint x, y, w, h;
2445      
2446     g_assert(dir == 0 || dir == 1 || dir == 2);
2447     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2448
2449     /* check if already done */
2450     if (max) {
2451         if (dir == 0 && self->max_horz && self->max_vert) return;
2452         if (dir == 1 && self->max_horz) return;
2453         if (dir == 2 && self->max_vert) return;
2454     } else {
2455         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2456         if (dir == 1 && !self->max_horz) return;
2457         if (dir == 2 && !self->max_vert) return;
2458     }
2459
2460     /* we just tell it to configure in the same place and client_configure
2461        worries about filling the screen with the window */
2462     x = self->area.x;
2463     y = self->area.y;
2464     w = self->area.width;
2465     h = self->area.height;
2466
2467     if (max) {
2468         if (savearea) {
2469             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2470                 RECT_SET(self->pre_max_area,
2471                          self->area.x, self->pre_max_area.y,
2472                          self->area.width, self->pre_max_area.height);
2473             }
2474             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2475                 RECT_SET(self->pre_max_area,
2476                          self->pre_max_area.x, self->area.y,
2477                          self->pre_max_area.width, self->area.height);
2478             }
2479         }
2480     } else {
2481         Rect *a;
2482
2483         a = screen_area_monitor(self->desktop, 0);
2484         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2485             if (self->pre_max_area.width > 0) {
2486                 x = self->pre_max_area.x;
2487                 w = self->pre_max_area.width;
2488
2489                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2490                          0, self->pre_max_area.height);
2491             } else {
2492                 /* pick some fallbacks... */
2493                 x = a->x + a->width / 4;
2494                 w = a->width / 2;
2495             }
2496         }
2497         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2498             if (self->pre_max_area.height > 0) {
2499                 y = self->pre_max_area.y;
2500                 h = self->pre_max_area.height;
2501
2502                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2503                          self->pre_max_area.width, 0);
2504             } else {
2505                 /* pick some fallbacks... */
2506                 y = a->y + a->height / 4;
2507                 h = a->height / 2;
2508             }
2509         }
2510     }
2511
2512     if (dir == 0 || dir == 1) /* horz */
2513         self->max_horz = max;
2514     if (dir == 0 || dir == 2) /* vert */
2515         self->max_vert = max;
2516
2517     client_change_state(self); /* change the state hints on the client */
2518
2519     client_setup_decor_and_functions(self);
2520
2521     client_move_resize(self, x, y, w, h);
2522 }
2523
2524 void client_shade(ObClient *self, gboolean shade)
2525 {
2526     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2527          shade) ||                         /* can't shade */
2528         self->shaded == shade) return;     /* already done */
2529
2530     /* when we're iconic, don't change the wmstate */
2531     if (!self->iconic) {
2532         glong old;
2533
2534         old = self->wmstate;
2535         self->wmstate = shade ? IconicState : NormalState;
2536         if (old != self->wmstate)
2537             PROP_MSG(self->window, kde_wm_change_state,
2538                      self->wmstate, 1, 0, 0);
2539     }
2540
2541     self->shaded = shade;
2542     client_change_state(self);
2543     /* resize the frame to just the titlebar */
2544     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2545 }
2546
2547 void client_close(ObClient *self)
2548 {
2549     XEvent ce;
2550
2551     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2552
2553     /* in the case that the client provides no means to requesting that it
2554        close, we just kill it */
2555     if (!self->delete_window)
2556         client_kill(self);
2557     
2558     /*
2559       XXX: itd be cool to do timeouts and shit here for killing the client's
2560       process off
2561       like... if the window is around after 5 seconds, then the close button
2562       turns a nice red, and if this function is called again, the client is
2563       explicitly killed.
2564     */
2565
2566     ce.xclient.type = ClientMessage;
2567     ce.xclient.message_type =  prop_atoms.wm_protocols;
2568     ce.xclient.display = ob_display;
2569     ce.xclient.window = self->window;
2570     ce.xclient.format = 32;
2571     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2572     ce.xclient.data.l[1] = event_curtime;
2573     ce.xclient.data.l[2] = 0l;
2574     ce.xclient.data.l[3] = 0l;
2575     ce.xclient.data.l[4] = 0l;
2576     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2577 }
2578
2579 void client_kill(ObClient *self)
2580 {
2581     XKillClient(ob_display, self->window);
2582 }
2583
2584 void client_hilite(ObClient *self, gboolean hilite)
2585 {
2586     /* don't allow focused windows to hilite */
2587     self->demands_attention = hilite && !client_focused(self);
2588     if (self->demands_attention)
2589         frame_flash_start(self->frame);
2590     else
2591         frame_flash_stop(self->frame);
2592     client_change_state(self);
2593 }
2594
2595 void client_set_desktop_recursive(ObClient *self,
2596                                   guint target, gboolean donthide)
2597 {
2598     guint old;
2599     GSList *it;
2600
2601     if (target != self->desktop) {
2602
2603         ob_debug("Setting desktop %u\n", target+1);
2604
2605         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2606
2607         /* remove from the old desktop(s) */
2608         focus_order_remove(self);
2609
2610         old = self->desktop;
2611         self->desktop = target;
2612         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2613         /* the frame can display the current desktop state */
2614         frame_adjust_state(self->frame);
2615         /* 'move' the window to the new desktop */
2616         if (!donthide)
2617             client_showhide(self);
2618         /* raise if it was not already on the desktop */
2619         if (old != DESKTOP_ALL)
2620             client_raise(self);
2621         if (STRUT_EXISTS(self->strut))
2622             screen_update_areas();
2623
2624         /* add to the new desktop(s) */
2625         if (config_focus_new)
2626             focus_order_to_top(self);
2627         else
2628             focus_order_to_bottom(self);
2629     }
2630
2631     /* move all transients */
2632     for (it = self->transients; it; it = g_slist_next(it))
2633         if (it->data != self) client_set_desktop_recursive(it->data,
2634                                                            target, donthide);
2635 }
2636
2637 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2638 {
2639     GSList *it;
2640
2641     it = client_search_top_transients(self);
2642
2643     for(; it; it = g_slist_next(it))
2644         client_set_desktop_recursive(it->data, target, donthide);
2645 }
2646
2647 ObClient *client_search_modal_child(ObClient *self)
2648 {
2649     GSList *it;
2650     ObClient *ret;
2651   
2652     for (it = self->transients; it; it = g_slist_next(it)) {
2653         ObClient *c = it->data;
2654         if ((ret = client_search_modal_child(c))) return ret;
2655         if (c->modal) return c;
2656     }
2657     return NULL;
2658 }
2659
2660 gboolean client_validate(ObClient *self)
2661 {
2662     XEvent e; 
2663
2664     XSync(ob_display, FALSE); /* get all events on the server */
2665
2666     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2667         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2668         XPutBackEvent(ob_display, &e);
2669         return FALSE;
2670     }
2671
2672     return TRUE;
2673 }
2674
2675 void client_set_wm_state(ObClient *self, glong state)
2676 {
2677     if (state == self->wmstate) return; /* no change */
2678   
2679     switch (state) {
2680     case IconicState:
2681         client_iconify(self, TRUE, TRUE);
2682         break;
2683     case NormalState:
2684         client_iconify(self, FALSE, TRUE);
2685         break;
2686     }
2687 }
2688
2689 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2690 {
2691     gboolean shaded = self->shaded;
2692     gboolean fullscreen = self->fullscreen;
2693     gboolean undecorated = self->undecorated;
2694     gboolean max_horz = self->max_horz;
2695     gboolean max_vert = self->max_vert;
2696     gboolean modal = self->modal;
2697     gboolean iconic = self->iconic;
2698     gboolean demands_attention = self->demands_attention;
2699     gint i;
2700
2701     if (!(action == prop_atoms.net_wm_state_add ||
2702           action == prop_atoms.net_wm_state_remove ||
2703           action == prop_atoms.net_wm_state_toggle))
2704         /* an invalid action was passed to the client message, ignore it */
2705         return; 
2706
2707     for (i = 0; i < 2; ++i) {
2708         Atom state = i == 0 ? data1 : data2;
2709     
2710         if (!state) continue;
2711
2712         /* if toggling, then pick whether we're adding or removing */
2713         if (action == prop_atoms.net_wm_state_toggle) {
2714             if (state == prop_atoms.net_wm_state_modal)
2715                 action = modal ? prop_atoms.net_wm_state_remove :
2716                     prop_atoms.net_wm_state_add;
2717             else if (state == prop_atoms.net_wm_state_maximized_vert)
2718                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2719                     prop_atoms.net_wm_state_add;
2720             else if (state == prop_atoms.net_wm_state_maximized_horz)
2721                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2722                     prop_atoms.net_wm_state_add;
2723             else if (state == prop_atoms.net_wm_state_shaded)
2724                 action = shaded ? prop_atoms.net_wm_state_remove :
2725                     prop_atoms.net_wm_state_add;
2726             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2727                 action = self->skip_taskbar ?
2728                     prop_atoms.net_wm_state_remove :
2729                     prop_atoms.net_wm_state_add;
2730             else if (state == prop_atoms.net_wm_state_skip_pager)
2731                 action = self->skip_pager ?
2732                     prop_atoms.net_wm_state_remove :
2733                     prop_atoms.net_wm_state_add;
2734             else if (state == prop_atoms.net_wm_state_hidden)
2735                 action = self->iconic ?
2736                     prop_atoms.net_wm_state_remove :
2737                     prop_atoms.net_wm_state_add;
2738             else if (state == prop_atoms.net_wm_state_fullscreen)
2739                 action = fullscreen ?
2740                     prop_atoms.net_wm_state_remove :
2741                     prop_atoms.net_wm_state_add;
2742             else if (state == prop_atoms.net_wm_state_above)
2743                 action = self->above ? prop_atoms.net_wm_state_remove :
2744                     prop_atoms.net_wm_state_add;
2745             else if (state == prop_atoms.net_wm_state_below)
2746                 action = self->below ? prop_atoms.net_wm_state_remove :
2747                     prop_atoms.net_wm_state_add;
2748             else if (state == prop_atoms.net_wm_state_demands_attention)
2749                 action = self->demands_attention ?
2750                     prop_atoms.net_wm_state_remove :
2751                     prop_atoms.net_wm_state_add;
2752             else if (state == prop_atoms.ob_wm_state_undecorated)
2753                 action = undecorated ? prop_atoms.net_wm_state_remove :
2754                     prop_atoms.net_wm_state_add;
2755         }
2756     
2757         if (action == prop_atoms.net_wm_state_add) {
2758             if (state == prop_atoms.net_wm_state_modal) {
2759                 modal = TRUE;
2760             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2761                 max_vert = TRUE;
2762             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2763                 max_horz = TRUE;
2764             } else if (state == prop_atoms.net_wm_state_shaded) {
2765                 shaded = TRUE;
2766             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2767                 self->skip_taskbar = TRUE;
2768             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2769                 self->skip_pager = TRUE;
2770             } else if (state == prop_atoms.net_wm_state_hidden) {
2771                 iconic = TRUE;
2772             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2773                 fullscreen = TRUE;
2774             } else if (state == prop_atoms.net_wm_state_above) {
2775                 self->above = TRUE;
2776                 self->below = FALSE;
2777             } else if (state == prop_atoms.net_wm_state_below) {
2778                 self->above = FALSE;
2779                 self->below = TRUE;
2780             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2781                 demands_attention = TRUE;
2782             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2783                 undecorated = TRUE;
2784             }
2785
2786         } else { /* action == prop_atoms.net_wm_state_remove */
2787             if (state == prop_atoms.net_wm_state_modal) {
2788                 modal = FALSE;
2789             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2790                 max_vert = FALSE;
2791             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2792                 max_horz = FALSE;
2793             } else if (state == prop_atoms.net_wm_state_shaded) {
2794                 shaded = FALSE;
2795             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2796                 self->skip_taskbar = FALSE;
2797             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2798                 self->skip_pager = FALSE;
2799             } else if (state == prop_atoms.net_wm_state_hidden) {
2800                 iconic = FALSE;
2801             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2802                 fullscreen = FALSE;
2803             } else if (state == prop_atoms.net_wm_state_above) {
2804                 self->above = FALSE;
2805             } else if (state == prop_atoms.net_wm_state_below) {
2806                 self->below = FALSE;
2807             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2808                 demands_attention = FALSE;
2809             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2810                 undecorated = FALSE;
2811             }
2812         }
2813     }
2814     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2815         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2816             /* toggling both */
2817             if (max_horz == max_vert) { /* both going the same way */
2818                 client_maximize(self, max_horz, 0, TRUE);
2819             } else {
2820                 client_maximize(self, max_horz, 1, TRUE);
2821                 client_maximize(self, max_vert, 2, TRUE);
2822             }
2823         } else {
2824             /* toggling one */
2825             if (max_horz != self->max_horz)
2826                 client_maximize(self, max_horz, 1, TRUE);
2827             else
2828                 client_maximize(self, max_vert, 2, TRUE);
2829         }
2830     }
2831     /* change fullscreen state before shading, as it will affect if the window
2832        can shade or not */
2833     if (fullscreen != self->fullscreen)
2834         client_fullscreen(self, fullscreen, TRUE);
2835     if (shaded != self->shaded)
2836         client_shade(self, shaded);
2837     if (undecorated != self->undecorated)
2838         client_set_undecorated(self, undecorated);
2839     if (modal != self->modal) {
2840         self->modal = modal;
2841         /* when a window changes modality, then its stacking order with its
2842            transients needs to change */
2843         client_raise(self);
2844     }
2845     if (iconic != self->iconic)
2846         client_iconify(self, iconic, FALSE);
2847
2848     if (demands_attention != self->demands_attention)
2849         client_hilite(self, demands_attention);
2850
2851     client_change_state(self); /* change the hint to reflect these changes */
2852 }
2853
2854 ObClient *client_focus_target(ObClient *self)
2855 {
2856     ObClient *child = NULL;
2857
2858     child = client_search_modal_child(self);
2859     if (child) return child;
2860     return self;
2861 }
2862
2863 gboolean client_can_focus(ObClient *self)
2864 {
2865     XEvent ev;
2866
2867     /* choose the correct target */
2868     self = client_focus_target(self);
2869
2870     if (!self->frame->visible)
2871         return FALSE;
2872
2873     if (!(self->can_focus || self->focus_notify))
2874         return FALSE;
2875
2876     /* do a check to see if the window has already been unmapped or destroyed
2877        do this intelligently while watching out for unmaps we've generated
2878        (ignore_unmaps > 0) */
2879     if (XCheckTypedWindowEvent(ob_display, self->window,
2880                                DestroyNotify, &ev)) {
2881         XPutBackEvent(ob_display, &ev);
2882         return FALSE;
2883     }
2884     while (XCheckTypedWindowEvent(ob_display, self->window,
2885                                   UnmapNotify, &ev)) {
2886         if (self->ignore_unmaps) {
2887             self->ignore_unmaps--;
2888         } else {
2889             XPutBackEvent(ob_display, &ev);
2890             return FALSE;
2891         }
2892     }
2893
2894     return TRUE;
2895 }
2896
2897 gboolean client_focus(ObClient *self)
2898 {
2899     /* choose the correct target */
2900     self = client_focus_target(self);
2901
2902     if (!client_can_focus(self)) {
2903         if (!self->frame->visible) {
2904             /* update the focus lists */
2905             focus_order_to_top(self);
2906         }
2907         return FALSE;
2908     }
2909
2910     if (self->can_focus) {
2911         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2912            I choose to use it always, hopefully to find errors quicker, if any
2913            are left. (I hate X. I hate focus events.)
2914            
2915            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2916            #799. So now it is RevertToNone again.
2917         */
2918         XSetInputFocus(ob_display, self->window, RevertToNone,
2919                        event_curtime);
2920     }
2921
2922     if (self->focus_notify) {
2923         XEvent ce;
2924         ce.xclient.type = ClientMessage;
2925         ce.xclient.message_type = prop_atoms.wm_protocols;
2926         ce.xclient.display = ob_display;
2927         ce.xclient.window = self->window;
2928         ce.xclient.format = 32;
2929         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2930         ce.xclient.data.l[1] = event_curtime;
2931         ce.xclient.data.l[2] = 0l;
2932         ce.xclient.data.l[3] = 0l;
2933         ce.xclient.data.l[4] = 0l;
2934         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2935     }
2936
2937 #ifdef DEBUG_FOCUS
2938     ob_debug("%sively focusing %lx at %d\n",
2939              (self->can_focus ? "act" : "pass"),
2940              self->window, (gint) event_curtime);
2941 #endif
2942
2943     /* Cause the FocusIn to come back to us. Important for desktop switches,
2944        since otherwise we'll have no FocusIn on the queue and send it off to
2945        the focus_backup. */
2946     XSync(ob_display, FALSE);
2947     return TRUE;
2948 }
2949
2950 /* Used when the current client is closed, focus_last will then prevent
2951  * focus from going to the mouse pointer */
2952 void client_unfocus(ObClient *self)
2953 {
2954     if (focus_client == self) {
2955 #ifdef DEBUG_FOCUS
2956         ob_debug("client_unfocus for %lx\n", self->window);
2957 #endif
2958         focus_fallback(OB_FOCUS_FALLBACK_CLOSED);
2959     }
2960 }
2961
2962 void client_activate(ObClient *self, gboolean here, gboolean user, Time time)
2963 {
2964     /* XXX do some stuff here if user is false to determine if we really want
2965        to activate it or not (a parent or group member is currently
2966        active)?
2967     */
2968     ob_debug("Want to activate window 0x%x with time %u (last time %u), "
2969              "source=%s\n",
2970              self->window, time, client_last_user_time,
2971              (user ? "user" : "application"));
2972     if (!user && time && time < client_last_user_time)
2973         client_hilite(self, TRUE);
2974     else {
2975         if (client_normal(self) && screen_showing_desktop)
2976             screen_show_desktop(FALSE);
2977         if (self->iconic)
2978             client_iconify(self, FALSE, here);
2979         if (self->desktop != DESKTOP_ALL &&
2980             self->desktop != screen_desktop) {
2981             if (here)
2982                 client_set_desktop(self, screen_desktop, FALSE);
2983             else
2984                 screen_set_desktop(self->desktop);
2985         } else if (!self->frame->visible)
2986             /* if its not visible for other reasons, then don't mess
2987                with it */
2988             return;
2989         if (self->shaded)
2990             client_shade(self, FALSE);
2991
2992         client_focus(self);
2993
2994         /* we do this an action here. this is rather important. this is because
2995            we want the results from the focus change to take place BEFORE we go
2996            about raising the window. when a fullscreen window loses focus, we
2997            need this or else the raise wont be able to raise above the
2998            to-lose-focus fullscreen window. */
2999         client_raise(self);
3000     }
3001 }
3002
3003 void client_raise(ObClient *self)
3004 {
3005     action_run_string("Raise", self, CurrentTime);
3006 }
3007
3008 void client_lower(ObClient *self)
3009 {
3010     action_run_string("Lower", self, CurrentTime);
3011 }
3012
3013 gboolean client_focused(ObClient *self)
3014 {
3015     return self == focus_client;
3016 }
3017
3018 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3019 {
3020     guint i;
3021     /* si is the smallest image >= req */
3022     /* li is the largest image < req */
3023     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3024
3025     if (!self->nicons) {
3026         ObClientIcon *parent = NULL;
3027
3028         if (self->transient_for) {
3029             if (self->transient_for != OB_TRAN_GROUP)
3030                 parent = client_icon_recursive(self->transient_for, w, h);
3031             else {
3032                 GSList *it;
3033                 for (it = self->group->members; it; it = g_slist_next(it)) {
3034                     ObClient *c = it->data;
3035                     if (c != self && !c->transient_for) {
3036                         if ((parent = client_icon_recursive(c, w, h)))
3037                             break;
3038                     }
3039                 }
3040             }
3041         }
3042         
3043         return parent;
3044     }
3045
3046     for (i = 0; i < self->nicons; ++i) {
3047         size = self->icons[i].width * self->icons[i].height;
3048         if (size < smallest && size >= (unsigned)(w * h)) {
3049             smallest = size;
3050             si = i;
3051         }
3052         if (size > largest && size <= (unsigned)(w * h)) {
3053             largest = size;
3054             li = i;
3055         }
3056     }
3057     if (largest == 0) /* didnt find one smaller than the requested size */
3058         return &self->icons[si];
3059     return &self->icons[li];
3060 }
3061
3062 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3063 {
3064     ObClientIcon *ret;
3065     static ObClientIcon deficon;
3066
3067     if (!(ret = client_icon_recursive(self, w, h))) {
3068         deficon.width = deficon.height = 48;
3069         deficon.data = ob_rr_theme->def_win_icon;
3070         ret = &deficon;
3071     }
3072     return ret;
3073 }
3074
3075 /* this be mostly ripped from fvwm */
3076 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
3077 {
3078     gint my_cx, my_cy, his_cx, his_cy;
3079     gint offset = 0;
3080     gint distance = 0;
3081     gint score, best_score;
3082     ObClient *best_client, *cur;
3083     GList *it;
3084
3085     if(!client_list)
3086         return NULL;
3087
3088     /* first, find the centre coords of the currently focused window */
3089     my_cx = c->frame->area.x + c->frame->area.width / 2;
3090     my_cy = c->frame->area.y + c->frame->area.height / 2;
3091
3092     best_score = -1;
3093     best_client = NULL;
3094
3095     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
3096         cur = it->data;
3097
3098         /* the currently selected window isn't interesting */
3099         if(cur == c)
3100             continue;
3101         if (!client_normal(cur))
3102             continue;
3103         /* using c->desktop instead of screen_desktop doesn't work if the
3104          * current window was omnipresent, hope this doesn't have any other
3105          * side effects */
3106         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3107             continue;
3108         if(cur->iconic)
3109             continue;
3110         if(!(client_focus_target(cur) == cur &&
3111              client_can_focus(cur)))
3112             continue;
3113
3114         /* find the centre coords of this window, from the
3115          * currently focused window's point of view */
3116         his_cx = (cur->frame->area.x - my_cx)
3117             + cur->frame->area.width / 2;
3118         his_cy = (cur->frame->area.y - my_cy)
3119             + cur->frame->area.height / 2;
3120
3121         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3122            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3123             gint tx;
3124             /* Rotate the diagonals 45 degrees counterclockwise.
3125              * To do this, multiply the matrix /+h +h\ with the
3126              * vector (x y).                   \-h +h/
3127              * h = sqrt(0.5). We can set h := 1 since absolute
3128              * distance doesn't matter here. */
3129             tx = his_cx + his_cy;
3130             his_cy = -his_cx + his_cy;
3131             his_cx = tx;
3132         }
3133
3134         switch(dir) {
3135         case OB_DIRECTION_NORTH:
3136         case OB_DIRECTION_SOUTH:
3137         case OB_DIRECTION_NORTHEAST:
3138         case OB_DIRECTION_SOUTHWEST:
3139             offset = (his_cx < 0) ? -his_cx : his_cx;
3140             distance = ((dir == OB_DIRECTION_NORTH ||
3141                          dir == OB_DIRECTION_NORTHEAST) ?
3142                         -his_cy : his_cy);
3143             break;
3144         case OB_DIRECTION_EAST:
3145         case OB_DIRECTION_WEST:
3146         case OB_DIRECTION_SOUTHEAST:
3147         case OB_DIRECTION_NORTHWEST:
3148             offset = (his_cy < 0) ? -his_cy : his_cy;
3149             distance = ((dir == OB_DIRECTION_WEST ||
3150                          dir == OB_DIRECTION_NORTHWEST) ?
3151                         -his_cx : his_cx);
3152             break;
3153         }
3154
3155         /* the target must be in the requested direction */
3156         if(distance <= 0)
3157             continue;
3158
3159         /* Calculate score for this window.  The smaller the better. */
3160         score = distance + offset;
3161
3162         /* windows more than 45 degrees off the direction are
3163          * heavily penalized and will only be chosen if nothing
3164          * else within a million pixels */
3165         if(offset > distance)
3166             score += 1000000;
3167
3168         if(best_score == -1 || score < best_score)
3169             best_client = cur,
3170                 best_score = score;
3171     }
3172
3173     return best_client;
3174 }
3175
3176 void client_set_layer(ObClient *self, gint layer)
3177 {
3178     if (layer < 0) {
3179         self->below = TRUE;
3180         self->above = FALSE;
3181     } else if (layer == 0) {
3182         self->below = self->above = FALSE;
3183     } else {
3184         self->below = FALSE;
3185         self->above = TRUE;
3186     }
3187     client_calc_layer(self);
3188     client_change_state(self); /* reflect this in the state hints */
3189 }
3190
3191 void client_set_undecorated(ObClient *self, gboolean undecorated)
3192 {
3193     if (self->undecorated != undecorated) {
3194         self->undecorated = undecorated;
3195         client_setup_decor_and_functions(self);
3196         /* Make sure the client knows it might have moved. Maybe there is a
3197          * better way of doing this so only one client_configure is sent, but
3198          * since 125 of these are sent per second when moving the window (with
3199          * user = FALSE) i doubt it matters much.
3200          */
3201         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3202                          self->area.width, self->area.height, TRUE, TRUE);
3203         client_change_state(self); /* reflect this in the state hints */
3204     }
3205 }
3206
3207 /* Determines which physical monitor a client is on by calculating the
3208    area of the part of the client on each monitor.  The number of the
3209    monitor containing the greatest area of the client is returned.*/
3210 guint client_monitor(ObClient *self)
3211 {
3212     guint i;
3213     guint most = 0;
3214     guint mostv = 0;
3215
3216     for (i = 0; i < screen_num_monitors; ++i) {
3217         Rect *area = screen_physical_area_monitor(i);
3218         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
3219             Rect r;
3220             guint v;
3221
3222             RECT_SET_INTERSECTION(r, *area, self->frame->area);
3223             v = r.width * r.height;
3224
3225             if (v > mostv) {
3226                 mostv = v;
3227                 most = i;
3228             }
3229         }
3230     }
3231     return most;
3232 }
3233
3234 GSList *client_search_top_transients(ObClient *self)
3235 {
3236     GSList *ret = NULL;
3237
3238     /* move up the direct transient chain as far as possible */
3239     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3240         self = self->transient_for;
3241
3242     if (!self->transient_for)
3243         ret = g_slist_prepend(ret, self);
3244     else {
3245             GSList *it;
3246
3247             g_assert(self->group);
3248
3249             for (it = self->group->members; it; it = g_slist_next(it)) {
3250                 ObClient *c = it->data;
3251
3252                 if (!c->transient_for)
3253                     ret = g_slist_prepend(ret, c);
3254             }
3255
3256             if (ret == NULL) /* no group parents */
3257                 ret = g_slist_prepend(ret, self);
3258     }
3259
3260     return ret;
3261 }
3262
3263 ObClient *client_search_focus_parent(ObClient *self)
3264 {
3265     if (self->transient_for) {
3266         if (self->transient_for != OB_TRAN_GROUP) {
3267             if (client_focused(self->transient_for))
3268                 return self->transient_for;
3269         } else {
3270             GSList *it;
3271
3272             for (it = self->group->members; it; it = g_slist_next(it)) {
3273                 ObClient *c = it->data;
3274
3275                 /* checking transient_for prevents infinate loops! */
3276                 if (c != self && !c->transient_for)
3277                     if (client_focused(c))
3278                         return c;
3279             }
3280         }
3281     }
3282
3283     return NULL;
3284 }
3285
3286 ObClient *client_search_parent(ObClient *self, ObClient *search)
3287 {
3288     if (self->transient_for) {
3289         if (self->transient_for != OB_TRAN_GROUP) {
3290             if (self->transient_for == search)
3291                 return search;
3292         } else {
3293             GSList *it;
3294
3295             for (it = self->group->members; it; it = g_slist_next(it)) {
3296                 ObClient *c = it->data;
3297
3298                 /* checking transient_for prevents infinate loops! */
3299                 if (c != self && !c->transient_for)
3300                     if (c == search)
3301                         return search;
3302             }
3303         }
3304     }
3305
3306     return NULL;
3307 }
3308
3309 ObClient *client_search_transient(ObClient *self, ObClient *search)
3310 {
3311     GSList *sit;
3312
3313     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3314         if (sit->data == search)
3315             return search;
3316         if (client_search_transient(sit->data, search))
3317             return search;
3318     }
3319     return NULL;
3320 }
3321
3322 void client_update_sm_client_id(ObClient *self)
3323 {
3324     g_free(self->sm_client_id);
3325     self->sm_client_id = NULL;
3326
3327     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3328         self->group)
3329         PROP_GETS(self->group->leader, sm_client_id, locale,
3330                   &self->sm_client_id);
3331 }
3332
3333 #define WANT_EDGE(cur, c) \
3334             if(cur == c)                                                      \
3335                 continue;                                                     \
3336             if(!client_normal(cur))                                   \
3337                 continue;                                                     \
3338             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3339                 continue;                                                     \
3340             if(cur->iconic)                                                   \
3341                 continue;                                                     \
3342             if(cur->layer < c->layer && !config_resist_layers_below)          \
3343                 continue;
3344
3345 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3346             if ((his_edge_start >= my_edge_start && \
3347                  his_edge_start <= my_edge_end) ||  \
3348                 (my_edge_start >= his_edge_start && \
3349                  my_edge_start <= his_edge_end))    \
3350                 dest = his_offset;
3351
3352 /* finds the nearest edge in the given direction from the current client
3353  * note to self: the edge is the -frame- edge (the actual one), not the
3354  * client edge.
3355  */
3356 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3357 {
3358     gint dest, monitor_dest;
3359     gint my_edge_start, my_edge_end, my_offset;
3360     GList *it;
3361     Rect *a, *monitor;
3362     
3363     if(!client_list)
3364         return -1;
3365
3366     a = screen_area(c->desktop);
3367     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3368
3369     switch(dir) {
3370     case OB_DIRECTION_NORTH:
3371         my_edge_start = c->frame->area.x;
3372         my_edge_end = c->frame->area.x + c->frame->area.width;
3373         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3374         
3375         /* default: top of screen */
3376         dest = a->y + (hang ? c->frame->area.height : 0);
3377         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3378         /* if the monitor edge comes before the screen edge, */
3379         /* use that as the destination instead. (For xinerama) */
3380         if (monitor_dest != dest && my_offset > monitor_dest)
3381             dest = monitor_dest; 
3382
3383         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3384             gint his_edge_start, his_edge_end, his_offset;
3385             ObClient *cur = it->data;
3386
3387             WANT_EDGE(cur, c)
3388
3389             his_edge_start = cur->frame->area.x;
3390             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3391             his_offset = cur->frame->area.y + 
3392                          (hang ? 0 : cur->frame->area.height);
3393
3394             if(his_offset + 1 > my_offset)
3395                 continue;
3396
3397             if(his_offset < dest)
3398                 continue;
3399
3400             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3401         }
3402         break;
3403     case OB_DIRECTION_SOUTH:
3404         my_edge_start = c->frame->area.x;
3405         my_edge_end = c->frame->area.x + c->frame->area.width;
3406         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3407
3408         /* default: bottom of screen */
3409         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3410         monitor_dest = monitor->y + monitor->height -
3411                        (hang ? c->frame->area.height : 0);
3412         /* if the monitor edge comes before the screen edge, */
3413         /* use that as the destination instead. (For xinerama) */
3414         if (monitor_dest != dest && my_offset < monitor_dest)
3415             dest = monitor_dest; 
3416
3417         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3418             gint his_edge_start, his_edge_end, his_offset;
3419             ObClient *cur = it->data;
3420
3421             WANT_EDGE(cur, c)
3422
3423             his_edge_start = cur->frame->area.x;
3424             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3425             his_offset = cur->frame->area.y +
3426                          (hang ? cur->frame->area.height : 0);
3427
3428
3429             if(his_offset - 1 < my_offset)
3430                 continue;
3431             
3432             if(his_offset > dest)
3433                 continue;
3434
3435             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3436         }
3437         break;
3438     case OB_DIRECTION_WEST:
3439         my_edge_start = c->frame->area.y;
3440         my_edge_end = c->frame->area.y + c->frame->area.height;
3441         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3442
3443         /* default: leftmost egde of screen */
3444         dest = a->x + (hang ? c->frame->area.width : 0);
3445         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3446         /* if the monitor edge comes before the screen edge, */
3447         /* use that as the destination instead. (For xinerama) */
3448         if (monitor_dest != dest && my_offset > monitor_dest)
3449             dest = monitor_dest;            
3450
3451         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3452             gint his_edge_start, his_edge_end, his_offset;
3453             ObClient *cur = it->data;
3454
3455             WANT_EDGE(cur, c)
3456
3457             his_edge_start = cur->frame->area.y;
3458             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3459             his_offset = cur->frame->area.x +
3460                          (hang ? 0 : cur->frame->area.width);
3461
3462             if(his_offset + 1 > my_offset)
3463                 continue;
3464
3465             if(his_offset < dest)
3466                 continue;
3467
3468             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3469         }
3470        break;
3471     case OB_DIRECTION_EAST:
3472         my_edge_start = c->frame->area.y;
3473         my_edge_end = c->frame->area.y + c->frame->area.height;
3474         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3475         
3476         /* default: rightmost edge of screen */
3477         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3478         monitor_dest = monitor->x + monitor->width -
3479                        (hang ? c->frame->area.width : 0);
3480         /* if the monitor edge comes before the screen edge, */
3481         /* use that as the destination instead. (For xinerama) */
3482         if (monitor_dest != dest && my_offset < monitor_dest)
3483             dest = monitor_dest;            
3484
3485         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3486             gint his_edge_start, his_edge_end, his_offset;
3487             ObClient *cur = it->data;
3488
3489             WANT_EDGE(cur, c)
3490
3491             his_edge_start = cur->frame->area.y;
3492             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3493             his_offset = cur->frame->area.x +
3494                          (hang ? cur->frame->area.width : 0);
3495
3496             if(his_offset - 1 < my_offset)
3497                 continue;
3498             
3499             if(his_offset > dest)
3500                 continue;
3501
3502             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3503         }
3504         break;
3505     case OB_DIRECTION_NORTHEAST:
3506     case OB_DIRECTION_SOUTHEAST:
3507     case OB_DIRECTION_NORTHWEST:
3508     case OB_DIRECTION_SOUTHWEST:
3509         /* not implemented */
3510     default:
3511         g_assert_not_reached();
3512         dest = 0; /* suppress warning */
3513     }
3514     return dest;
3515 }
3516
3517 ObClient* client_under_pointer()
3518 {
3519     gint x, y;
3520     GList *it;
3521     ObClient *ret = NULL;
3522
3523     if (screen_pointer_pos(&x, &y)) {
3524         for (it = stacking_list; it; it = g_list_next(it)) {
3525             if (WINDOW_IS_CLIENT(it->data)) {
3526                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3527                 if (c->frame->visible &&
3528                     RECT_CONTAINS(c->frame->area, x, y)) {
3529                     ret = c;
3530                     break;
3531                 }
3532             }
3533         }
3534     }
3535     return ret;
3536 }
3537
3538 gboolean client_has_group_siblings(ObClient *self)
3539 {
3540     return self->group && self->group->members->next;
3541 }