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