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