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