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