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