oops typo of doom
[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; it = g_list_next(it), ++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 = g_slist_next(it)) {
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 = g_slist_next(it))
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 = g_slist_next(it))
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 = g_slist_next(it))
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; it = g_slist_next(it)) {
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 = g_slist_next(it))
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 = g_slist_next(it))
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;
1363                          it = g_slist_next(it))
1364                     {
1365                         ObClient *c = it->data;
1366
1367                         if (c != self && !c->transient_for)
1368                             c->transients = g_slist_remove(c->transients,
1369                                                            self);
1370                     }
1371                 }
1372
1373                 group_remove(self->group, self);
1374                 self->group = NULL;
1375             }
1376             if (hints->window_group != None) {
1377                 self->group = group_add(hints->window_group, self);
1378
1379                 /* i can only have transients from the group if i am not
1380                    transient myself */
1381                 if (!self->transient_for) {
1382                     /* add other transients of the group that are already
1383                        set up */
1384                     for (it = self->group->members; it;
1385                          it = g_slist_next(it))
1386                     {
1387                         ObClient *c = it->data;
1388                         if (c != self && c->transient_for == OB_TRAN_GROUP)
1389                             self->transients =
1390                                 g_slist_append(self->transients, c);
1391                     }
1392                 }
1393             }
1394
1395             /* because the self->transient flag wont change from this call,
1396                we don't need to update the window's type and such, only its
1397                transient_for, and the transients lists of other windows in
1398                the group may be affected */
1399             client_update_transient_for(self);
1400         }
1401
1402         /* the WM_HINTS can contain an icon */
1403         client_update_icons(self);
1404
1405         XFree(hints);
1406     }
1407
1408     if (ur != self->urgent) {
1409         self->urgent = ur;
1410         /* fire the urgent callback if we're mapped, otherwise, wait until
1411            after we're mapped */
1412         if (self->frame)
1413             client_urgent_notify(self);
1414     }
1415 }
1416
1417 void client_update_title(ObClient *self)
1418 {
1419     GList *it;
1420     guint32 nums;
1421     guint i;
1422     gchar *data = NULL;
1423     gboolean read_title;
1424     gchar *old_title;
1425
1426     old_title = self->title;
1427      
1428     /* try netwm */
1429     if (!PROP_GETS(self->window, net_wm_name, utf8, &data))
1430         /* try old x stuff */
1431         if (!PROP_GETS(self->window, wm_name, locale, &data))
1432             data = g_strdup("Unnamed Window");
1433
1434     /* did the title change? then reset the title_count */
1435     if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1436         self->title_count = 1;
1437
1438     /* look for duplicates and append a number */
1439     nums = 0;
1440     for (it = client_list; it; it = g_list_next(it))
1441         if (it->data != self) {
1442             ObClient *c = it->data;
1443             if (0 == strncmp(c->title, data, strlen(data)))
1444                 nums |= 1 << c->title_count;
1445         }
1446     /* find first free number */
1447     for (i = 1; i <= 32; ++i)
1448         if (!(nums & (1 << i))) {
1449             if (self->title_count == 1 || i == 1)
1450                 self->title_count = i;
1451             break;
1452         }
1453     /* dont display the number for the first window */
1454     if (self->title_count > 1) {
1455         gchar *ndata;
1456         ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1457         g_free(data);
1458         data = ndata;
1459     }
1460
1461     PROP_SETS(self->window, net_wm_visible_name, data);
1462
1463     self->title = data;
1464
1465     if (self->frame)
1466         frame_adjust_title(self->frame);
1467
1468     g_free(old_title);
1469
1470     /* update the icon title */
1471     data = NULL;
1472     g_free(self->icon_title);
1473
1474     read_title = TRUE;
1475     /* try netwm */
1476     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1477         /* try old x stuff */
1478         if (!PROP_GETS(self->window, wm_icon_name, locale, &data)) {
1479             data = g_strdup(self->title);
1480             read_title = FALSE;
1481         }
1482
1483     /* append the title count, dont display the number for the first window */
1484     if (read_title && self->title_count > 1) {
1485         gchar *vdata, *ndata;
1486         ndata = g_strdup_printf(" - [%u]", self->title_count);
1487         vdata = g_strconcat(data, ndata, NULL);
1488         g_free(ndata);
1489         g_free(data);
1490         data = vdata;
1491     }
1492
1493     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1494
1495     self->icon_title = data;
1496 }
1497
1498 void client_update_class(ObClient *self)
1499 {
1500     gchar **data;
1501     gchar *s;
1502
1503     if (self->name) g_free(self->name);
1504     if (self->class) g_free(self->class);
1505     if (self->role) g_free(self->role);
1506
1507     self->name = self->class = self->role = NULL;
1508
1509     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1510         if (data[0]) {
1511             self->name = g_strdup(data[0]);
1512             if (data[1])
1513                 self->class = g_strdup(data[1]);
1514         }
1515         g_strfreev(data);     
1516     }
1517
1518     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1519         self->role = s;
1520
1521     if (self->name == NULL) self->name = g_strdup("");
1522     if (self->class == NULL) self->class = g_strdup("");
1523     if (self->role == NULL) self->role = g_strdup("");
1524 }
1525
1526 void client_update_strut(ObClient *self)
1527 {
1528     guint num;
1529     guint32 *data;
1530     gboolean got = FALSE;
1531     StrutPartial strut;
1532
1533     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1534                     &data, &num)) {
1535         if (num == 12) {
1536             got = TRUE;
1537             STRUT_PARTIAL_SET(strut,
1538                               data[0], data[2], data[1], data[3],
1539                               data[4], data[5], data[8], data[9],
1540                               data[6], data[7], data[10], data[11]);
1541         }
1542         g_free(data);
1543     }
1544
1545     if (!got &&
1546         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1547         if (num == 4) {
1548             const Rect *a;
1549
1550             got = TRUE;
1551
1552             /* use the screen's width/height */
1553             a = screen_physical_area();
1554
1555             STRUT_PARTIAL_SET(strut,
1556                               data[0], data[2], data[1], data[3],
1557                               a->y, a->y + a->height - 1,
1558                               a->x, a->x + a->width - 1,
1559                               a->y, a->y + a->height - 1,
1560                               a->x, a->x + a->width - 1);
1561         }
1562         g_free(data);
1563     }
1564
1565     if (!got)
1566         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1567                           0, 0, 0, 0, 0, 0, 0, 0);
1568
1569     if (!STRUT_EQUAL(strut, self->strut)) {
1570         self->strut = strut;
1571
1572         /* updating here is pointless while we're being mapped cuz we're not in
1573            the client list yet */
1574         if (self->frame)
1575             screen_update_areas();
1576     }
1577 }
1578
1579 void client_update_icons(ObClient *self)
1580 {
1581     guint num;
1582     guint32 *data;
1583     guint w, h, i, j;
1584
1585     for (i = 0; i < self->nicons; ++i)
1586         g_free(self->icons[i].data);
1587     if (self->nicons > 0)
1588         g_free(self->icons);
1589     self->nicons = 0;
1590
1591     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1592         /* figure out how many valid icons are in here */
1593         i = 0;
1594         while (num - i > 2) {
1595             w = data[i++];
1596             h = data[i++];
1597             i += w * h;
1598             if (i > num || w*h == 0) break;
1599             ++self->nicons;
1600         }
1601
1602         self->icons = g_new(ObClientIcon, self->nicons);
1603     
1604         /* store the icons */
1605         i = 0;
1606         for (j = 0; j < self->nicons; ++j) {
1607             guint x, y, t;
1608
1609             w = self->icons[j].width = data[i++];
1610             h = self->icons[j].height = data[i++];
1611
1612             if (w*h == 0) continue;
1613
1614             self->icons[j].data = g_new(RrPixel32, w * h);
1615             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1616                 if (x >= w) {
1617                     x = 0;
1618                     ++y;
1619                 }
1620                 self->icons[j].data[t] =
1621                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1622                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1623                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1624                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1625             }
1626             g_assert(i <= num);
1627         }
1628
1629         g_free(data);
1630     } else if (PROP_GETA32(self->window, kwm_win_icon,
1631                            kwm_win_icon, &data, &num)) {
1632         if (num == 2) {
1633             self->nicons++;
1634             self->icons = g_new(ObClientIcon, self->nicons);
1635             xerror_set_ignore(TRUE);
1636             if (!RrPixmapToRGBA(ob_rr_inst,
1637                                 data[0], data[1],
1638                                 &self->icons[self->nicons-1].width,
1639                                 &self->icons[self->nicons-1].height,
1640                                 &self->icons[self->nicons-1].data)) {
1641                 g_free(&self->icons[self->nicons-1]);
1642                 self->nicons--;
1643             }
1644             xerror_set_ignore(FALSE);
1645         }
1646         g_free(data);
1647     } else {
1648         XWMHints *hints;
1649
1650         if ((hints = XGetWMHints(ob_display, self->window))) {
1651             if (hints->flags & IconPixmapHint) {
1652                 self->nicons++;
1653                 self->icons = g_new(ObClientIcon, self->nicons);
1654                 xerror_set_ignore(TRUE);
1655                 if (!RrPixmapToRGBA(ob_rr_inst,
1656                                     hints->icon_pixmap,
1657                                     (hints->flags & IconMaskHint ?
1658                                      hints->icon_mask : None),
1659                                     &self->icons[self->nicons-1].width,
1660                                     &self->icons[self->nicons-1].height,
1661                                     &self->icons[self->nicons-1].data)){
1662                     g_free(&self->icons[self->nicons-1]);
1663                     self->nicons--;
1664                 }
1665                 xerror_set_ignore(FALSE);
1666             }
1667             XFree(hints);
1668         }
1669     }
1670
1671     if (self->frame)
1672         frame_adjust_icon(self->frame);
1673 }
1674
1675 static void client_change_state(ObClient *self)
1676 {
1677     guint32 state[2];
1678     guint32 netstate[11];
1679     guint num;
1680
1681     state[0] = self->wmstate;
1682     state[1] = None;
1683     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1684
1685     num = 0;
1686     if (self->modal)
1687         netstate[num++] = prop_atoms.net_wm_state_modal;
1688     if (self->shaded)
1689         netstate[num++] = prop_atoms.net_wm_state_shaded;
1690     if (self->iconic)
1691         netstate[num++] = prop_atoms.net_wm_state_hidden;
1692     if (self->skip_taskbar)
1693         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1694     if (self->skip_pager)
1695         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1696     if (self->fullscreen)
1697         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1698     if (self->max_vert)
1699         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1700     if (self->max_horz)
1701         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1702     if (self->above)
1703         netstate[num++] = prop_atoms.net_wm_state_above;
1704     if (self->below)
1705         netstate[num++] = prop_atoms.net_wm_state_below;
1706     if (self->undecorated)
1707         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1708     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1709
1710     client_calc_layer(self);
1711
1712     if (self->frame)
1713         frame_adjust_state(self->frame);
1714 }
1715
1716 ObClient *client_search_focus_tree(ObClient *self)
1717 {
1718     GSList *it;
1719     ObClient *ret;
1720
1721     for (it = self->transients; it; it = g_slist_next(it)) {
1722         if (client_focused(it->data)) return it->data;
1723         if ((ret = client_search_focus_tree(it->data))) return ret;
1724     }
1725     return NULL;
1726 }
1727
1728 ObClient *client_search_focus_tree_full(ObClient *self)
1729 {
1730     if (self->transient_for) {
1731         if (self->transient_for != OB_TRAN_GROUP) {
1732             return client_search_focus_tree_full(self->transient_for);
1733         } else {
1734             GSList *it;
1735             gboolean recursed = FALSE;
1736         
1737             for (it = self->group->members; it; it = g_slist_next(it))
1738                 if (!((ObClient*)it->data)->transient_for) {
1739                     ObClient *c;
1740                     if ((c = client_search_focus_tree_full(it->data)))
1741                         return c;
1742                     recursed = TRUE;
1743                 }
1744             if (recursed)
1745                 return NULL;
1746         }
1747     }
1748
1749     /* this function checks the whole tree, the client_search_focus_tree~
1750        does not, so we need to check this window */
1751     if (client_focused(self))
1752         return self;
1753     return client_search_focus_tree(self);
1754 }
1755
1756 static ObStackingLayer calc_layer(ObClient *self)
1757 {
1758     ObStackingLayer l;
1759
1760     if (self->fullscreen &&
1761         (client_focused(self) || client_search_focus_tree(self)))
1762         l = OB_STACKING_LAYER_FULLSCREEN;
1763     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1764         l = OB_STACKING_LAYER_DESKTOP;
1765     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1766         if (self->above) l = OB_STACKING_LAYER_DOCK_ABOVE;
1767         else if (self->below) l = OB_STACKING_LAYER_DOCK_BELOW;
1768         else l = OB_STACKING_LAYER_DOCK_NORMAL;
1769     }
1770     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1771     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1772     else l = OB_STACKING_LAYER_NORMAL;
1773
1774     return l;
1775 }
1776
1777 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1778                                         ObStackingLayer l, gboolean raised)
1779 {
1780     ObStackingLayer old, own;
1781     GSList *it;
1782
1783     old = self->layer;
1784     own = calc_layer(self);
1785     self->layer = l > own ? l : own;
1786
1787     for (it = self->transients; it; it = g_slist_next(it))
1788         client_calc_layer_recursive(it->data, orig,
1789                                     l, raised ? raised : l != old);
1790
1791     if (!raised && l != old)
1792         if (orig->frame) { /* only restack if the original window is managed */
1793             stacking_remove(CLIENT_AS_WINDOW(self));
1794             stacking_add(CLIENT_AS_WINDOW(self));
1795         }
1796 }
1797
1798 void client_calc_layer(ObClient *self)
1799 {
1800     ObStackingLayer l;
1801     ObClient *orig;
1802
1803     orig = self;
1804
1805     /* transients take on the layer of their parents */
1806     self = client_search_top_transient(self);
1807
1808     l = calc_layer(self);
1809
1810     client_calc_layer_recursive(self, orig, l, FALSE);
1811 }
1812
1813 gboolean client_should_show(ObClient *self)
1814 {
1815     if (self->iconic)
1816         return FALSE;
1817     if (client_normal(self) && screen_showing_desktop)
1818         return FALSE;
1819     /*
1820     if (self->transient_for) {
1821         if (self->transient_for != OB_TRAN_GROUP)
1822             return client_should_show(self->transient_for);
1823         else {
1824             GSList *it;
1825
1826             for (it = self->group->members; it; it = g_slist_next(it)) {
1827                 ObClient *c = it->data;
1828                 if (c != self && !c->transient_for) {
1829                     if (client_should_show(c))
1830                         return TRUE;
1831                 }
1832             }
1833         }
1834     }
1835     */
1836     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1837         return TRUE;
1838     
1839     return FALSE;
1840 }
1841
1842 static void client_showhide(ObClient *self)
1843 {
1844
1845     if (client_should_show(self))
1846         frame_show(self->frame);
1847     else
1848         frame_hide(self->frame);
1849 }
1850
1851 gboolean client_normal(ObClient *self) {
1852     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1853               self->type == OB_CLIENT_TYPE_DOCK ||
1854               self->type == OB_CLIENT_TYPE_SPLASH);
1855 }
1856
1857 static void client_apply_startup_state(ObClient *self)
1858 {
1859     /* these are in a carefully crafted order.. */
1860
1861     if (self->iconic) {
1862         self->iconic = FALSE;
1863         client_iconify(self, TRUE, FALSE);
1864     }
1865     if (self->fullscreen) {
1866         self->fullscreen = FALSE;
1867         client_fullscreen(self, TRUE, FALSE);
1868     }
1869     if (self->undecorated) {
1870         self->undecorated = FALSE;
1871         client_set_undecorated(self, TRUE);
1872     }
1873     if (self->shaded) {
1874         self->shaded = FALSE;
1875         client_shade(self, TRUE);
1876     }
1877     if (self->urgent)
1878         client_urgent_notify(self);
1879   
1880     if (self->max_vert && self->max_horz) {
1881         self->max_vert = self->max_horz = FALSE;
1882         client_maximize(self, TRUE, 0, FALSE);
1883     } else if (self->max_vert) {
1884         self->max_vert = FALSE;
1885         client_maximize(self, TRUE, 2, FALSE);
1886     } else if (self->max_horz) {
1887         self->max_horz = FALSE;
1888         client_maximize(self, TRUE, 1, FALSE);
1889     }
1890
1891     /* nothing to do for the other states:
1892        skip_taskbar
1893        skip_pager
1894        modal
1895        above
1896        below
1897     */
1898 }
1899
1900 void client_configure_full(ObClient *self, ObCorner anchor,
1901                            gint x, gint y, gint w, gint h,
1902                            gboolean user, gboolean final,
1903                            gboolean force_reply)
1904 {
1905     gint oldw, oldh;
1906     gboolean send_resize_client;
1907     gboolean moved = FALSE, resized = FALSE;
1908     guint fdecor = self->frame->decorations;
1909     gboolean fhorz = self->frame->max_horz;
1910
1911     /* make the frame recalculate its dimentions n shit without changing
1912        anything visible for real, this way the constraints below can work with
1913        the updated frame dimensions. */
1914     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
1915
1916     /* gets the frame's position */
1917     frame_client_gravity(self->frame, &x, &y);
1918
1919     /* these positions are frame positions, not client positions */
1920
1921     /* set the size and position if fullscreen */
1922     if (self->fullscreen) {
1923 #ifdef VIDMODE
1924         gint dot;
1925         XF86VidModeModeLine mode;
1926 #endif
1927         Rect *a;
1928         guint i;
1929
1930         i = client_monitor(self);
1931         a = screen_physical_area_monitor(i);
1932
1933 #ifdef VIDMODE
1934         if (i == 0 && /* primary head */
1935             extensions_vidmode &&
1936             XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
1937             /* get the mode last so the mode.privsize isnt freed incorrectly */
1938             XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
1939             x += a->x;
1940             y += a->y;
1941             w = mode.hdisplay;
1942             h = mode.vdisplay;
1943             if (mode.privsize) XFree(mode.private);
1944         } else
1945 #endif
1946         {
1947             x = a->x;
1948             y = a->y;
1949             w = a->width;
1950             h = a->height;
1951         }
1952
1953         user = FALSE; /* ignore that increment etc shit when in fullscreen */
1954     } else {
1955         Rect *a;
1956
1957         a = screen_area_monitor(self->desktop, client_monitor(self));
1958
1959         /* set the size and position if maximized */
1960         if (self->max_horz) {
1961             x = a->x;
1962             w = a->width - self->frame->size.left - self->frame->size.right;
1963         }
1964         if (self->max_vert) {
1965             y = a->y;
1966             h = a->height - self->frame->size.top - self->frame->size.bottom;
1967         }
1968     }
1969
1970     /* gets the client's position */
1971     frame_frame_gravity(self->frame, &x, &y);
1972
1973     /* these override the above states! if you cant move you can't move! */
1974     if (user) {
1975         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
1976             x = self->area.x;
1977             y = self->area.y;
1978         }
1979         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
1980             w = self->area.width;
1981             h = self->area.height;
1982         }
1983     }
1984
1985     if (!(w == self->area.width && h == self->area.height)) {
1986         gint basew, baseh, minw, minh;
1987
1988         /* base size is substituted with min size if not specified */
1989         if (self->base_size.width || self->base_size.height) {
1990             basew = self->base_size.width;
1991             baseh = self->base_size.height;
1992         } else {
1993             basew = self->min_size.width;
1994             baseh = self->min_size.height;
1995         }
1996         /* min size is substituted with base size if not specified */
1997         if (self->min_size.width || self->min_size.height) {
1998             minw = self->min_size.width;
1999             minh = self->min_size.height;
2000         } else {
2001             minw = self->base_size.width;
2002             minh = self->base_size.height;
2003         }
2004
2005         /* if this is a user-requested resize, then check against min/max
2006            sizes */
2007
2008         /* smaller than min size or bigger than max size? */
2009         if (w > self->max_size.width) w = self->max_size.width;
2010         if (w < minw) w = minw;
2011         if (h > self->max_size.height) h = self->max_size.height;
2012         if (h < minh) h = minh;
2013
2014         w -= basew;
2015         h -= baseh;
2016
2017         /* keep to the increments */
2018         w /= self->size_inc.width;
2019         h /= self->size_inc.height;
2020
2021         /* you cannot resize to nothing */
2022         if (basew + w < 1) w = 1 - basew;
2023         if (baseh + h < 1) h = 1 - baseh;
2024   
2025         /* store the logical size */
2026         SIZE_SET(self->logical_size,
2027                  self->size_inc.width > 1 ? w : w + basew,
2028                  self->size_inc.height > 1 ? h : h + baseh);
2029
2030         w *= self->size_inc.width;
2031         h *= self->size_inc.height;
2032
2033         w += basew;
2034         h += baseh;
2035
2036         /* adjust the height to match the width for the aspect ratios.
2037            for this, min size is not substituted for base size ever. */
2038         w -= self->base_size.width;
2039         h -= self->base_size.height;
2040
2041         if (self->min_ratio)
2042             if (h * self->min_ratio > w) {
2043                 h = (gint)(w / self->min_ratio);
2044
2045                 /* you cannot resize to nothing */
2046                 if (h < 1) {
2047                     h = 1;
2048                     w = (gint)(h * self->min_ratio);
2049                 }
2050             }
2051         if (self->max_ratio)
2052             if (h * self->max_ratio < w) {
2053                 h = (gint)(w / self->max_ratio);
2054
2055                 /* you cannot resize to nothing */
2056                 if (h < 1) {
2057                     h = 1;
2058                     w = (gint)(h * self->min_ratio);
2059                 }
2060             }
2061
2062         w += self->base_size.width;
2063         h += self->base_size.height;
2064     }
2065
2066     g_assert(w > 0);
2067     g_assert(h > 0);
2068
2069     switch (anchor) {
2070     case OB_CORNER_TOPLEFT:
2071         break;
2072     case OB_CORNER_TOPRIGHT:
2073         x -= w - self->area.width;
2074         break;
2075     case OB_CORNER_BOTTOMLEFT:
2076         y -= h - self->area.height;
2077         break;
2078     case OB_CORNER_BOTTOMRIGHT:
2079         x -= w - self->area.width;
2080         y -= h - self->area.height;
2081         break;
2082     }
2083
2084     moved = x != self->area.x || y != self->area.y;
2085     resized = w != self->area.width || h != self->area.height;
2086
2087     oldw = self->area.width;
2088     oldh = self->area.height;
2089     RECT_SET(self->area, x, y, w, h);
2090
2091     /* for app-requested resizes, always resize if 'resized' is true.
2092        for user-requested ones, only resize if final is true, or when
2093        resizing in redraw mode */
2094     send_resize_client = ((!user && resized) ||
2095                           (user && (final ||
2096                                     (resized && config_redraw_resize))));
2097
2098     /* if the client is enlarging, the resize the client before the frame */
2099     if (send_resize_client && user && (w > oldw || h > oldh))
2100         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2101
2102     /* move/resize the frame to match the request */
2103     if (self->frame) {
2104         if (self->decorations != fdecor || self->max_horz != fhorz)
2105             moved = resized = TRUE;
2106
2107         if (moved || resized)
2108             frame_adjust_area(self->frame, moved, resized, FALSE);
2109
2110         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2111         {
2112             XEvent event;
2113             event.type = ConfigureNotify;
2114             event.xconfigure.display = ob_display;
2115             event.xconfigure.event = self->window;
2116             event.xconfigure.window = self->window;
2117
2118             /* root window real coords */
2119             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2120                 self->border_width;
2121             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2122                 self->border_width;
2123             event.xconfigure.width = w;
2124             event.xconfigure.height = h;
2125             event.xconfigure.border_width = 0;
2126             event.xconfigure.above = self->frame->plate;
2127             event.xconfigure.override_redirect = FALSE;
2128             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2129                        FALSE, StructureNotifyMask, &event);
2130         }
2131     }
2132
2133     /* if the client is shrinking, then resize the frame before the client */
2134     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2135         XResizeWindow(ob_display, self->window, w, h);
2136
2137     XFlush(ob_display);
2138 }
2139
2140 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2141 {
2142     gint x, y, w, h;
2143
2144     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2145         self->fullscreen == fs) return;                   /* already done */
2146
2147     self->fullscreen = fs;
2148     client_change_state(self); /* change the state hints on the client,
2149                                   and adjust out layer/stacking */
2150
2151     if (fs) {
2152         if (savearea)
2153             self->pre_fullscreen_area = self->area;
2154
2155         /* these are not actually used cuz client_configure will set them
2156            as appropriate when the window is fullscreened */
2157         x = y = w = h = 0;
2158     } else {
2159         Rect *a;
2160
2161         if (self->pre_fullscreen_area.width > 0 &&
2162             self->pre_fullscreen_area.height > 0)
2163         {
2164             x = self->pre_fullscreen_area.x;
2165             y = self->pre_fullscreen_area.y;
2166             w = self->pre_fullscreen_area.width;
2167             h = self->pre_fullscreen_area.height;
2168             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2169         } else {
2170             /* pick some fallbacks... */
2171             a = screen_area_monitor(self->desktop, 0);
2172             x = a->x + a->width / 4;
2173             y = a->y + a->height / 4;
2174             w = a->width / 2;
2175             h = a->height / 2;
2176         }
2177     }
2178
2179     client_setup_decor_and_functions(self);
2180
2181     client_move_resize(self, x, y, w, h);
2182
2183     /* try focus us when we go into fullscreen mode */
2184     client_focus(self);
2185 }
2186
2187 static void client_iconify_recursive(ObClient *self,
2188                                      gboolean iconic, gboolean curdesk)
2189 {
2190     GSList *it;
2191     gboolean changed = FALSE;
2192
2193
2194     if (self->iconic != iconic) {
2195         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2196                  self->window);
2197
2198         self->iconic = iconic;
2199
2200         if (iconic) {
2201             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2202                 glong old;
2203
2204                 old = self->wmstate;
2205                 self->wmstate = IconicState;
2206                 if (old != self->wmstate)
2207                     PROP_MSG(self->window, kde_wm_change_state,
2208                              self->wmstate, 1, 0, 0);
2209
2210                 /* update the focus lists.. iconic windows go to the bottom of
2211                    the list, put the new iconic window at the 'top of the
2212                    bottom'. */
2213                 focus_order_to_top(self);
2214
2215                 changed = TRUE;
2216             }
2217         } else {
2218             glong old;
2219
2220             if (curdesk)
2221                 client_set_desktop(self, screen_desktop, FALSE);
2222
2223             old = self->wmstate;
2224             self->wmstate = self->shaded ? IconicState : NormalState;
2225             if (old != self->wmstate)
2226                 PROP_MSG(self->window, kde_wm_change_state,
2227                          self->wmstate, 1, 0, 0);
2228
2229             /* this puts it after the current focused window */
2230             focus_order_remove(self);
2231             focus_order_add_new(self);
2232
2233             /* this is here cuz with the VIDMODE extension, the viewport can
2234                change while a fullscreen window is iconic, and when it
2235                uniconifies, it would be nice if it did so to the new position
2236                of the viewport */
2237             client_reconfigure(self);
2238
2239             changed = TRUE;
2240         }
2241     }
2242
2243     if (changed) {
2244         client_change_state(self);
2245         client_showhide(self);
2246         screen_update_areas();
2247     }
2248
2249     /* iconify all transients */
2250     for (it = self->transients; it; it = g_slist_next(it))
2251         if (it->data != self) client_iconify_recursive(it->data,
2252                                                        iconic, curdesk);
2253 }
2254
2255 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2256 {
2257     /* move up the transient chain as far as possible first */
2258     self = client_search_top_transient(self);
2259
2260     client_iconify_recursive(client_search_top_transient(self),
2261                              iconic, curdesk);
2262 }
2263
2264 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2265 {
2266     gint x, y, w, h;
2267      
2268     g_assert(dir == 0 || dir == 1 || dir == 2);
2269     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2270
2271     /* check if already done */
2272     if (max) {
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     } else {
2277         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2278         if (dir == 1 && !self->max_horz) return;
2279         if (dir == 2 && !self->max_vert) return;
2280     }
2281
2282     /* we just tell it to configure in the same place and client_configure
2283        worries about filling the screen with the window */
2284     x = self->area.x;
2285     y = self->area.y;
2286     w = self->area.width;
2287     h = self->area.height;
2288
2289     if (max) {
2290         if (savearea) {
2291             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2292                 RECT_SET(self->pre_max_area,
2293                          self->area.x, self->pre_max_area.y,
2294                          self->area.width, self->pre_max_area.height);
2295             }
2296             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2297                 RECT_SET(self->pre_max_area,
2298                          self->pre_max_area.x, self->area.y,
2299                          self->pre_max_area.width, self->area.height);
2300             }
2301         }
2302     } else {
2303         Rect *a;
2304
2305         a = screen_area_monitor(self->desktop, 0);
2306         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2307             if (self->pre_max_area.width > 0) {
2308                 x = self->pre_max_area.x;
2309                 w = self->pre_max_area.width;
2310
2311                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2312                          0, self->pre_max_area.height);
2313             } else {
2314                 /* pick some fallbacks... */
2315                 x = a->x + a->width / 4;
2316                 w = a->width / 2;
2317             }
2318         }
2319         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2320             if (self->pre_max_area.height > 0) {
2321                 y = self->pre_max_area.y;
2322                 h = self->pre_max_area.height;
2323
2324                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2325                          self->pre_max_area.width, 0);
2326             } else {
2327                 /* pick some fallbacks... */
2328                 y = a->y + a->height / 4;
2329                 h = a->height / 2;
2330             }
2331         }
2332     }
2333
2334     if (dir == 0 || dir == 1) /* horz */
2335         self->max_horz = max;
2336     if (dir == 0 || dir == 2) /* vert */
2337         self->max_vert = max;
2338
2339     client_change_state(self); /* change the state hints on the client */
2340
2341     client_setup_decor_and_functions(self);
2342
2343     client_move_resize(self, x, y, w, h);
2344 }
2345
2346 void client_shade(ObClient *self, gboolean shade)
2347 {
2348     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2349          shade) ||                         /* can't shade */
2350         self->shaded == shade) return;     /* already done */
2351
2352     /* when we're iconic, don't change the wmstate */
2353     if (!self->iconic) {
2354         glong old;
2355
2356         old = self->wmstate;
2357         self->wmstate = shade ? IconicState : NormalState;
2358         if (old != self->wmstate)
2359             PROP_MSG(self->window, kde_wm_change_state,
2360                      self->wmstate, 1, 0, 0);
2361     }
2362
2363     self->shaded = shade;
2364     client_change_state(self);
2365     /* resize the frame to just the titlebar */
2366     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2367 }
2368
2369 void client_close(ObClient *self)
2370 {
2371     XEvent ce;
2372
2373     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2374
2375     /* in the case that the client provides no means to requesting that it
2376        close, we just kill it */
2377     if (!self->delete_window)
2378         client_kill(self);
2379     
2380     /*
2381       XXX: itd be cool to do timeouts and shit here for killing the client's
2382       process off
2383       like... if the window is around after 5 seconds, then the close button
2384       turns a nice red, and if this function is called again, the client is
2385       explicitly killed.
2386     */
2387
2388     ce.xclient.type = ClientMessage;
2389     ce.xclient.message_type =  prop_atoms.wm_protocols;
2390     ce.xclient.display = ob_display;
2391     ce.xclient.window = self->window;
2392     ce.xclient.format = 32;
2393     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2394     ce.xclient.data.l[1] = event_lasttime;
2395     ce.xclient.data.l[2] = 0l;
2396     ce.xclient.data.l[3] = 0l;
2397     ce.xclient.data.l[4] = 0l;
2398     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2399 }
2400
2401 void client_kill(ObClient *self)
2402 {
2403     XKillClient(ob_display, self->window);
2404 }
2405
2406 void client_set_desktop_recursive(ObClient *self,
2407                                   guint target, gboolean donthide)
2408 {
2409     guint old;
2410     GSList *it;
2411
2412     if (target != self->desktop) {
2413
2414         ob_debug("Setting desktop %u\n", target+1);
2415
2416         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2417
2418         /* remove from the old desktop(s) */
2419         focus_order_remove(self);
2420
2421         old = self->desktop;
2422         self->desktop = target;
2423         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2424         /* the frame can display the current desktop state */
2425         frame_adjust_state(self->frame);
2426         /* 'move' the window to the new desktop */
2427         if (!donthide)
2428             client_showhide(self);
2429         /* raise if it was not already on the desktop */
2430         if (old != DESKTOP_ALL)
2431             client_raise(self);
2432         screen_update_areas();
2433
2434         /* add to the new desktop(s) */
2435         if (config_focus_new)
2436             focus_order_to_top(self);
2437         else
2438             focus_order_to_bottom(self);
2439     }
2440
2441     /* move all transients */
2442     for (it = self->transients; it; it = g_slist_next(it))
2443         if (it->data != self) client_set_desktop_recursive(it->data,
2444                                                            target, donthide);
2445 }
2446
2447 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2448 {
2449     client_set_desktop_recursive(client_search_top_transient(self),
2450                                  target, donthide);
2451 }
2452
2453 ObClient *client_search_modal_child(ObClient *self)
2454 {
2455     GSList *it;
2456     ObClient *ret;
2457   
2458     for (it = self->transients; it; it = g_slist_next(it)) {
2459         ObClient *c = it->data;
2460         if ((ret = client_search_modal_child(c))) return ret;
2461         if (c->modal) return c;
2462     }
2463     return NULL;
2464 }
2465
2466 gboolean client_validate(ObClient *self)
2467 {
2468     XEvent e; 
2469
2470     XSync(ob_display, FALSE); /* get all events on the server */
2471
2472     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2473         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2474         XPutBackEvent(ob_display, &e);
2475         return FALSE;
2476     }
2477
2478     return TRUE;
2479 }
2480
2481 void client_set_wm_state(ObClient *self, glong state)
2482 {
2483     if (state == self->wmstate) return; /* no change */
2484   
2485     switch (state) {
2486     case IconicState:
2487         client_iconify(self, TRUE, TRUE);
2488         break;
2489     case NormalState:
2490         client_iconify(self, FALSE, TRUE);
2491         break;
2492     }
2493 }
2494
2495 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2496 {
2497     gboolean shaded = self->shaded;
2498     gboolean fullscreen = self->fullscreen;
2499     gboolean undecorated = self->undecorated;
2500     gboolean max_horz = self->max_horz;
2501     gboolean max_vert = self->max_vert;
2502     gboolean modal = self->modal;
2503     gint i;
2504
2505     if (!(action == prop_atoms.net_wm_state_add ||
2506           action == prop_atoms.net_wm_state_remove ||
2507           action == prop_atoms.net_wm_state_toggle))
2508         /* an invalid action was passed to the client message, ignore it */
2509         return; 
2510
2511     for (i = 0; i < 2; ++i) {
2512         Atom state = i == 0 ? data1 : data2;
2513     
2514         if (!state) continue;
2515
2516         /* if toggling, then pick whether we're adding or removing */
2517         if (action == prop_atoms.net_wm_state_toggle) {
2518             if (state == prop_atoms.net_wm_state_modal)
2519                 action = modal ? prop_atoms.net_wm_state_remove :
2520                     prop_atoms.net_wm_state_add;
2521             else if (state == prop_atoms.net_wm_state_maximized_vert)
2522                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2523                     prop_atoms.net_wm_state_add;
2524             else if (state == prop_atoms.net_wm_state_maximized_horz)
2525                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2526                     prop_atoms.net_wm_state_add;
2527             else if (state == prop_atoms.net_wm_state_shaded)
2528                 action = shaded ? prop_atoms.net_wm_state_remove :
2529                     prop_atoms.net_wm_state_add;
2530             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2531                 action = self->skip_taskbar ?
2532                     prop_atoms.net_wm_state_remove :
2533                     prop_atoms.net_wm_state_add;
2534             else if (state == prop_atoms.net_wm_state_skip_pager)
2535                 action = self->skip_pager ?
2536                     prop_atoms.net_wm_state_remove :
2537                     prop_atoms.net_wm_state_add;
2538             else if (state == prop_atoms.net_wm_state_fullscreen)
2539                 action = fullscreen ?
2540                     prop_atoms.net_wm_state_remove :
2541                     prop_atoms.net_wm_state_add;
2542             else if (state == prop_atoms.net_wm_state_above)
2543                 action = self->above ? prop_atoms.net_wm_state_remove :
2544                     prop_atoms.net_wm_state_add;
2545             else if (state == prop_atoms.net_wm_state_below)
2546                 action = self->below ? prop_atoms.net_wm_state_remove :
2547                     prop_atoms.net_wm_state_add;
2548             else if (state == prop_atoms.ob_wm_state_undecorated)
2549                 action = undecorated ? prop_atoms.net_wm_state_remove :
2550                     prop_atoms.net_wm_state_add;
2551         }
2552     
2553         if (action == prop_atoms.net_wm_state_add) {
2554             if (state == prop_atoms.net_wm_state_modal) {
2555                 modal = TRUE;
2556             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2557                 max_vert = TRUE;
2558             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2559                 max_horz = TRUE;
2560             } else if (state == prop_atoms.net_wm_state_shaded) {
2561                 shaded = TRUE;
2562             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2563                 self->skip_taskbar = TRUE;
2564             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2565                 self->skip_pager = TRUE;
2566             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2567                 fullscreen = TRUE;
2568             } else if (state == prop_atoms.net_wm_state_above) {
2569                 self->above = TRUE;
2570                 self->below = FALSE;
2571             } else if (state == prop_atoms.net_wm_state_below) {
2572                 self->above = FALSE;
2573                 self->below = TRUE;
2574             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2575                 undecorated = TRUE;
2576             }
2577
2578         } else { /* action == prop_atoms.net_wm_state_remove */
2579             if (state == prop_atoms.net_wm_state_modal) {
2580                 modal = FALSE;
2581             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2582                 max_vert = FALSE;
2583             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2584                 max_horz = FALSE;
2585             } else if (state == prop_atoms.net_wm_state_shaded) {
2586                 shaded = FALSE;
2587             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2588                 self->skip_taskbar = FALSE;
2589             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2590                 self->skip_pager = FALSE;
2591             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2592                 fullscreen = FALSE;
2593             } else if (state == prop_atoms.net_wm_state_above) {
2594                 self->above = FALSE;
2595             } else if (state == prop_atoms.net_wm_state_below) {
2596                 self->below = FALSE;
2597             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2598                 undecorated = FALSE;
2599             }
2600         }
2601     }
2602     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2603         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2604             /* toggling both */
2605             if (max_horz == max_vert) { /* both going the same way */
2606                 client_maximize(self, max_horz, 0, TRUE);
2607             } else {
2608                 client_maximize(self, max_horz, 1, TRUE);
2609                 client_maximize(self, max_vert, 2, TRUE);
2610             }
2611         } else {
2612             /* toggling one */
2613             if (max_horz != self->max_horz)
2614                 client_maximize(self, max_horz, 1, TRUE);
2615             else
2616                 client_maximize(self, max_vert, 2, TRUE);
2617         }
2618     }
2619     /* change fullscreen state before shading, as it will affect if the window
2620        can shade or not */
2621     if (fullscreen != self->fullscreen)
2622         client_fullscreen(self, fullscreen, TRUE);
2623     if (shaded != self->shaded)
2624         client_shade(self, shaded);
2625     if (undecorated != self->undecorated)
2626         client_set_undecorated(self, undecorated);
2627     if (modal != self->modal) {
2628         self->modal = modal;
2629         /* when a window changes modality, then its stacking order with its
2630            transients needs to change */
2631         client_raise(self);
2632     }
2633     client_calc_layer(self);
2634     client_change_state(self); /* change the hint to reflect these changes */
2635 }
2636
2637 ObClient *client_focus_target(ObClient *self)
2638 {
2639     ObClient *child;
2640      
2641     /* if we have a modal child, then focus it, not us */
2642     child = client_search_modal_child(client_search_top_transient(self));
2643     if (child) return child;
2644     return self;
2645 }
2646
2647 gboolean client_can_focus(ObClient *self)
2648 {
2649     XEvent ev;
2650
2651     /* choose the correct target */
2652     self = client_focus_target(self);
2653
2654     if (!self->frame->visible)
2655         return FALSE;
2656
2657     if (!(self->can_focus || self->focus_notify))
2658         return FALSE;
2659
2660     /* do a check to see if the window has already been unmapped or destroyed
2661        do this intelligently while watching out for unmaps we've generated
2662        (ignore_unmaps > 0) */
2663     if (XCheckTypedWindowEvent(ob_display, self->window,
2664                                DestroyNotify, &ev)) {
2665         XPutBackEvent(ob_display, &ev);
2666         return FALSE;
2667     }
2668     while (XCheckTypedWindowEvent(ob_display, self->window,
2669                                   UnmapNotify, &ev)) {
2670         if (self->ignore_unmaps) {
2671             self->ignore_unmaps--;
2672         } else {
2673             XPutBackEvent(ob_display, &ev);
2674             return FALSE;
2675         }
2676     }
2677
2678     return TRUE;
2679 }
2680
2681 gboolean client_focus(ObClient *self)
2682 {
2683     /* choose the correct target */
2684     self = client_focus_target(self);
2685
2686     if (!client_can_focus(self)) {
2687         if (!self->frame->visible) {
2688             /* update the focus lists */
2689             focus_order_to_top(self);
2690         }
2691         return FALSE;
2692     }
2693
2694     if (self->can_focus) {
2695         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2696            I choose to use it always, hopefully to find errors quicker, if any
2697            are left. (I hate X. I hate focus events.)
2698            
2699            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2700            #799. So now it is RevertToNone again.
2701         */
2702         XSetInputFocus(ob_display, self->window, RevertToNone,
2703                        event_lasttime);
2704     }
2705
2706     if (self->focus_notify) {
2707         XEvent ce;
2708         ce.xclient.type = ClientMessage;
2709         ce.xclient.message_type = prop_atoms.wm_protocols;
2710         ce.xclient.display = ob_display;
2711         ce.xclient.window = self->window;
2712         ce.xclient.format = 32;
2713         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2714         ce.xclient.data.l[1] = event_lasttime;
2715         ce.xclient.data.l[2] = 0l;
2716         ce.xclient.data.l[3] = 0l;
2717         ce.xclient.data.l[4] = 0l;
2718         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2719     }
2720
2721 #ifdef DEBUG_FOCUS
2722     ob_debug("%sively focusing %lx at %d\n",
2723              (self->can_focus ? "act" : "pass"),
2724              self->window, (gint) event_lasttime);
2725 #endif
2726
2727     /* Cause the FocusIn to come back to us. Important for desktop switches,
2728        since otherwise we'll have no FocusIn on the queue and send it off to
2729        the focus_backup. */
2730     XSync(ob_display, FALSE);
2731     return TRUE;
2732 }
2733
2734 void client_unfocus(ObClient *self)
2735 {
2736     if (focus_client == self) {
2737 #ifdef DEBUG_FOCUS
2738         ob_debug("client_unfocus for %lx\n", self->window);
2739 #endif
2740         focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2741     }
2742 }
2743
2744 void client_activate(ObClient *self, gboolean here)
2745 {
2746     if (client_normal(self) && screen_showing_desktop)
2747         screen_show_desktop(FALSE);
2748     if (self->iconic)
2749         client_iconify(self, FALSE, here);
2750     if (self->desktop != DESKTOP_ALL &&
2751         self->desktop != screen_desktop) {
2752         if (here)
2753             client_set_desktop(self, screen_desktop, FALSE);
2754         else
2755             screen_set_desktop(self->desktop);
2756     } else if (!self->frame->visible)
2757         /* if its not visible for other reasons, then don't mess
2758            with it */
2759         return;
2760     if (self->shaded)
2761         client_shade(self, FALSE);
2762
2763     client_focus(self);
2764
2765     /* we do this an action here. this is rather important. this is because
2766        we want the results from the focus change to take place BEFORE we go
2767        about raising the window. when a fullscreen window loses focus, we need
2768        this or else the raise wont be able to raise above the to-lose-focus
2769        fullscreen window. */
2770     client_raise(self);
2771 }
2772
2773 void client_raise(ObClient *self)
2774 {
2775     action_run_string("Raise", self);
2776 }
2777
2778 void client_lower(ObClient *self)
2779 {
2780     action_run_string("Lower", self);
2781 }
2782
2783 gboolean client_focused(ObClient *self)
2784 {
2785     return self == focus_client;
2786 }
2787
2788 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2789 {
2790     guint i;
2791     /* si is the smallest image >= req */
2792     /* li is the largest image < req */
2793     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2794
2795     if (!self->nicons) {
2796         ObClientIcon *parent = NULL;
2797
2798         if (self->transient_for) {
2799             if (self->transient_for != OB_TRAN_GROUP)
2800                 parent = client_icon_recursive(self->transient_for, w, h);
2801             else {
2802                 GSList *it;
2803                 for (it = self->group->members; it; it = g_slist_next(it)) {
2804                     ObClient *c = it->data;
2805                     if (c != self && !c->transient_for) {
2806                         if ((parent = client_icon_recursive(c, w, h)))
2807                             break;
2808                     }
2809                 }
2810             }
2811         }
2812         
2813         return parent;
2814     }
2815
2816     for (i = 0; i < self->nicons; ++i) {
2817         size = self->icons[i].width * self->icons[i].height;
2818         if (size < smallest && size >= (unsigned)(w * h)) {
2819             smallest = size;
2820             si = i;
2821         }
2822         if (size > largest && size <= (unsigned)(w * h)) {
2823             largest = size;
2824             li = i;
2825         }
2826     }
2827     if (largest == 0) /* didnt find one smaller than the requested size */
2828         return &self->icons[si];
2829     return &self->icons[li];
2830 }
2831
2832 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2833 {
2834     ObClientIcon *ret;
2835     static ObClientIcon deficon;
2836
2837     if (!(ret = client_icon_recursive(self, w, h))) {
2838         deficon.width = deficon.height = 48;
2839         deficon.data = ob_rr_theme->def_win_icon;
2840         ret = &deficon;
2841     }
2842     return ret;
2843 }
2844
2845 /* this be mostly ripped from fvwm */
2846 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2847 {
2848     gint my_cx, my_cy, his_cx, his_cy;
2849     gint offset = 0;
2850     gint distance = 0;
2851     gint score, best_score;
2852     ObClient *best_client, *cur;
2853     GList *it;
2854
2855     if(!client_list)
2856         return NULL;
2857
2858     /* first, find the centre coords of the currently focused window */
2859     my_cx = c->frame->area.x + c->frame->area.width / 2;
2860     my_cy = c->frame->area.y + c->frame->area.height / 2;
2861
2862     best_score = -1;
2863     best_client = NULL;
2864
2865     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
2866         cur = it->data;
2867
2868         /* the currently selected window isn't interesting */
2869         if(cur == c)
2870             continue;
2871         if (!client_normal(cur))
2872             continue;
2873         if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2874             continue;
2875         if(cur->iconic)
2876             continue;
2877         if(client_focus_target(cur) == cur &&
2878            !(cur->can_focus || cur->focus_notify))
2879             continue;
2880
2881         /* find the centre coords of this window, from the
2882          * currently focused window's point of view */
2883         his_cx = (cur->frame->area.x - my_cx)
2884             + cur->frame->area.width / 2;
2885         his_cy = (cur->frame->area.y - my_cy)
2886             + cur->frame->area.height / 2;
2887
2888         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
2889            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
2890             gint tx;
2891             /* Rotate the diagonals 45 degrees counterclockwise.
2892              * To do this, multiply the matrix /+h +h\ with the
2893              * vector (x y).                   \-h +h/
2894              * h = sqrt(0.5). We can set h := 1 since absolute
2895              * distance doesn't matter here. */
2896             tx = his_cx + his_cy;
2897             his_cy = -his_cx + his_cy;
2898             his_cx = tx;
2899         }
2900
2901         switch(dir) {
2902         case OB_DIRECTION_NORTH:
2903         case OB_DIRECTION_SOUTH:
2904         case OB_DIRECTION_NORTHEAST:
2905         case OB_DIRECTION_SOUTHWEST:
2906             offset = (his_cx < 0) ? -his_cx : his_cx;
2907             distance = ((dir == OB_DIRECTION_NORTH ||
2908                          dir == OB_DIRECTION_NORTHEAST) ?
2909                         -his_cy : his_cy);
2910             break;
2911         case OB_DIRECTION_EAST:
2912         case OB_DIRECTION_WEST:
2913         case OB_DIRECTION_SOUTHEAST:
2914         case OB_DIRECTION_NORTHWEST:
2915             offset = (his_cy < 0) ? -his_cy : his_cy;
2916             distance = ((dir == OB_DIRECTION_WEST ||
2917                          dir == OB_DIRECTION_NORTHWEST) ?
2918                         -his_cx : his_cx);
2919             break;
2920         }
2921
2922         /* the target must be in the requested direction */
2923         if(distance <= 0)
2924             continue;
2925
2926         /* Calculate score for this window.  The smaller the better. */
2927         score = distance + offset;
2928
2929         /* windows more than 45 degrees off the direction are
2930          * heavily penalized and will only be chosen if nothing
2931          * else within a million pixels */
2932         if(offset > distance)
2933             score += 1000000;
2934
2935         if(best_score == -1 || score < best_score)
2936             best_client = cur,
2937                 best_score = score;
2938     }
2939
2940     return best_client;
2941 }
2942
2943 void client_set_layer(ObClient *self, gint layer)
2944 {
2945     if (layer < 0) {
2946         self->below = TRUE;
2947         self->above = FALSE;
2948     } else if (layer == 0) {
2949         self->below = self->above = FALSE;
2950     } else {
2951         self->below = FALSE;
2952         self->above = TRUE;
2953     }
2954     client_calc_layer(self);
2955     client_change_state(self); /* reflect this in the state hints */
2956 }
2957
2958 void client_set_undecorated(ObClient *self, gboolean undecorated)
2959 {
2960     if (self->undecorated != undecorated) {
2961         self->undecorated = undecorated;
2962         client_setup_decor_and_functions(self);
2963         client_change_state(self); /* reflect this in the state hints */
2964     }
2965 }
2966
2967 guint client_monitor(ObClient *self)
2968 {
2969     guint i;
2970     guint most = 0;
2971     guint mostv = 0;
2972
2973     for (i = 0; i < screen_num_monitors; ++i) {
2974         Rect *area = screen_physical_area_monitor(i);
2975         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
2976             Rect r;
2977             guint v;
2978
2979             RECT_SET_INTERSECTION(r, *area, self->frame->area);
2980             v = r.width * r.height;
2981
2982             if (v > mostv) {
2983                 mostv = v;
2984                 most = i;
2985             }
2986         }
2987     }
2988     return most;
2989 }
2990
2991 ObClient *client_search_top_transient(ObClient *self)
2992 {
2993     /* move up the transient chain as far as possible */
2994     if (self->transient_for) {
2995         if (self->transient_for != OB_TRAN_GROUP) {
2996             return client_search_top_transient(self->transient_for);
2997         } else {
2998             GSList *it;
2999
3000             g_assert(self->group);
3001
3002             for (it = self->group->members; it; it = g_slist_next(it)) {
3003                 ObClient *c = it->data;
3004
3005                 /* checking transient_for prevents infinate loops! */
3006                 if (c != self && !c->transient_for)
3007                     break;
3008             }
3009             if (it)
3010                 return it->data;
3011         }
3012     }
3013
3014     return self;
3015 }
3016
3017 ObClient *client_search_focus_parent(ObClient *self)
3018 {
3019     if (self->transient_for) {
3020         if (self->transient_for != OB_TRAN_GROUP) {
3021             if (client_focused(self->transient_for))
3022                 return self->transient_for;
3023         } else {
3024             GSList *it;
3025
3026             for (it = self->group->members; it; it = g_slist_next(it)) {
3027                 ObClient *c = it->data;
3028
3029                 /* checking transient_for prevents infinate loops! */
3030                 if (c != self && !c->transient_for)
3031                     if (client_focused(c))
3032                         return c;
3033             }
3034         }
3035     }
3036
3037     return NULL;
3038 }
3039
3040 ObClient *client_search_parent(ObClient *self, ObClient *search)
3041 {
3042     if (self->transient_for) {
3043         if (self->transient_for != OB_TRAN_GROUP) {
3044             if (self->transient_for == search)
3045                 return search;
3046         } else {
3047             GSList *it;
3048
3049             for (it = self->group->members; it; it = g_slist_next(it)) {
3050                 ObClient *c = it->data;
3051
3052                 /* checking transient_for prevents infinate loops! */
3053                 if (c != self && !c->transient_for)
3054                     if (c == search)
3055                         return search;
3056             }
3057         }
3058     }
3059
3060     return NULL;
3061 }
3062
3063 ObClient *client_search_transient(ObClient *self, ObClient *search)
3064 {
3065     GSList *sit;
3066
3067     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3068         if (sit->data == search)
3069             return search;
3070         if (client_search_transient(sit->data, search))
3071             return search;
3072     }
3073     return NULL;
3074 }
3075
3076 void client_update_sm_client_id(ObClient *self)
3077 {
3078     g_free(self->sm_client_id);
3079     self->sm_client_id = NULL;
3080
3081     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3082         self->group)
3083         PROP_GETS(self->group->leader, sm_client_id, locale,
3084                   &self->sm_client_id);
3085 }
3086
3087 /* finds the nearest edge in the given direction from the current client
3088  * note to self: the edge is the -frame- edge (the actual one), not the
3089  * client edge.
3090  */
3091 gint client_directional_edge_search(ObClient *c, ObDirection dir)
3092 {
3093     gint dest;
3094     gint my_edge_start, my_edge_end, my_offset;
3095     GList *it;
3096     Rect *a;
3097     
3098     if(!client_list)
3099         return -1;
3100
3101     a = screen_area(c->desktop);
3102
3103     switch(dir) {
3104     case OB_DIRECTION_NORTH:
3105         my_edge_start = c->frame->area.x;
3106         my_edge_end = c->frame->area.x + c->frame->area.width;
3107         my_offset = c->frame->area.y;
3108         
3109         /* default: top of screen */
3110         dest = a->y;
3111
3112         for(it = client_list; it; it = g_list_next(it)) {
3113             gint his_edge_start, his_edge_end, his_offset;
3114             ObClient *cur = it->data;
3115
3116             if(cur == c)
3117                 continue;
3118             if(!client_normal(cur))
3119                 continue;
3120             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3121                 continue;
3122             if(cur->iconic)
3123                 continue;
3124
3125             his_edge_start = cur->frame->area.x;
3126             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3127             his_offset = cur->frame->area.y + cur->frame->area.height;
3128
3129             if(his_offset + 1 > my_offset)
3130                 continue;
3131
3132             if(his_offset < dest)
3133                 continue;
3134             
3135             if(his_edge_start >= my_edge_start &&
3136                his_edge_start <= my_edge_end)
3137                 dest = his_offset;
3138
3139             if(my_edge_start >= his_edge_start &&
3140                my_edge_start <= his_edge_end)
3141                 dest = his_offset;
3142
3143         }
3144         break;
3145     case OB_DIRECTION_SOUTH:
3146         my_edge_start = c->frame->area.x;
3147         my_edge_end = c->frame->area.x + c->frame->area.width;
3148         my_offset = c->frame->area.y + c->frame->area.height;
3149
3150         /* default: bottom of screen */
3151         dest = a->y + a->height;
3152
3153         for(it = client_list; it; it = g_list_next(it)) {
3154             gint his_edge_start, his_edge_end, his_offset;
3155             ObClient *cur = it->data;
3156
3157             if(cur == c)
3158                 continue;
3159             if(!client_normal(cur))
3160                 continue;
3161             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3162                 continue;
3163             if(cur->iconic)
3164                 continue;
3165
3166             his_edge_start = cur->frame->area.x;
3167             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3168             his_offset = cur->frame->area.y;
3169
3170
3171             if(his_offset - 1 < my_offset)
3172                 continue;
3173             
3174             if(his_offset > dest)
3175                 continue;
3176             
3177             if(his_edge_start >= my_edge_start &&
3178                his_edge_start <= my_edge_end)
3179                 dest = his_offset;
3180
3181             if(my_edge_start >= his_edge_start &&
3182                my_edge_start <= his_edge_end)
3183                 dest = his_offset;
3184
3185         }
3186         break;
3187     case OB_DIRECTION_WEST:
3188         my_edge_start = c->frame->area.y;
3189         my_edge_end = c->frame->area.y + c->frame->area.height;
3190         my_offset = c->frame->area.x;
3191
3192         /* default: leftmost egde of screen */
3193         dest = a->x;
3194
3195         for(it = client_list; it; it = g_list_next(it)) {
3196             gint his_edge_start, his_edge_end, his_offset;
3197             ObClient *cur = it->data;
3198
3199             if(cur == c)
3200                 continue;
3201             if(!client_normal(cur))
3202                 continue;
3203             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3204                 continue;
3205             if(cur->iconic)
3206                 continue;
3207
3208             his_edge_start = cur->frame->area.y;
3209             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3210             his_offset = cur->frame->area.x + cur->frame->area.width;
3211
3212             if(his_offset + 1 > my_offset)
3213                 continue;
3214             
3215             if(his_offset < dest)
3216                 continue;
3217             
3218             if(his_edge_start >= my_edge_start &&
3219                his_edge_start <= my_edge_end)
3220                 dest = his_offset;
3221
3222             if(my_edge_start >= his_edge_start &&
3223                my_edge_start <= his_edge_end)
3224                 dest = his_offset;
3225                 
3226
3227         }
3228         break;
3229     case OB_DIRECTION_EAST:
3230         my_edge_start = c->frame->area.y;
3231         my_edge_end = c->frame->area.y + c->frame->area.height;
3232         my_offset = c->frame->area.x + c->frame->area.width;
3233         
3234         /* default: rightmost edge of screen */
3235         dest = a->x + a->width;
3236
3237         for(it = client_list; it; it = g_list_next(it)) {
3238             gint his_edge_start, his_edge_end, his_offset;
3239             ObClient *cur = it->data;
3240
3241             if(cur == c)
3242                 continue;
3243             if(!client_normal(cur))
3244                 continue;
3245             if(c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3246                 continue;
3247             if(cur->iconic)
3248                 continue;
3249
3250             his_edge_start = cur->frame->area.y;
3251             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3252             his_offset = cur->frame->area.x;
3253
3254             if(his_offset - 1 < my_offset)
3255                 continue;
3256             
3257             if(his_offset > dest)
3258                 continue;
3259             
3260             if(his_edge_start >= my_edge_start &&
3261                his_edge_start <= my_edge_end)
3262                 dest = his_offset;
3263
3264             if(my_edge_start >= his_edge_start &&
3265                my_edge_start <= his_edge_end)
3266                 dest = his_offset;
3267
3268         }
3269         break;
3270     case OB_DIRECTION_NORTHEAST:
3271     case OB_DIRECTION_SOUTHEAST:
3272     case OB_DIRECTION_NORTHWEST:
3273     case OB_DIRECTION_SOUTHWEST:
3274         /* not implemented */
3275     default:
3276         g_assert_not_reached();
3277     }
3278     return dest;
3279 }
3280
3281 ObClient* client_under_pointer()
3282 {
3283     gint x, y;
3284     GList *it;
3285     ObClient *ret = NULL;
3286
3287     if (screen_pointer_pos(&x, &y)) {
3288         for (it = stacking_list; it; it = g_list_next(it)) {
3289             if (WINDOW_IS_CLIENT(it->data)) {
3290                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3291                 if (c->frame->visible &&
3292                     RECT_CONTAINS(c->frame->area, x, y)) {
3293                     ret = c;
3294                     break;
3295                 }
3296             }
3297         }
3298     }
3299     return ret;
3300 }
3301
3302 gboolean client_has_group_siblings(ObClient *self)
3303 {
3304     return self->group && self->group->members->next;
3305 }