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