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