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