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