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