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