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