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