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