add a (temporary?) check to client_activate to make client_list_menu not crash openbo...
[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     /* This check is for the client_list_menu trying to activate
2792      * a closed client. */
2793     if (!g_list_find(client_list, self)) return;
2794     if (client_normal(self) && screen_showing_desktop)
2795         screen_show_desktop(FALSE);
2796     if (self->iconic)
2797         client_iconify(self, FALSE, here);
2798     if (self->desktop != DESKTOP_ALL &&
2799         self->desktop != screen_desktop) {
2800         if (here)
2801             client_set_desktop(self, screen_desktop, FALSE);
2802         else
2803             screen_set_desktop(self->desktop);
2804     } else if (!self->frame->visible)
2805         /* if its not visible for other reasons, then don't mess
2806            with it */
2807         return;
2808     if (self->shaded)
2809         client_shade(self, FALSE);
2810
2811     client_focus(self);
2812
2813     /* we do this an action here. this is rather important. this is because
2814        we want the results from the focus change to take place BEFORE we go
2815        about raising the window. when a fullscreen window loses focus, we need
2816        this or else the raise wont be able to raise above the to-lose-focus
2817        fullscreen window. */
2818     client_raise(self);
2819 }
2820
2821 void client_raise(ObClient *self)
2822 {
2823     action_run_string("Raise", self);
2824 }
2825
2826 void client_lower(ObClient *self)
2827 {
2828     action_run_string("Lower", self);
2829 }
2830
2831 gboolean client_focused(ObClient *self)
2832 {
2833     return self == focus_client;
2834 }
2835
2836 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2837 {
2838     guint i;
2839     /* si is the smallest image >= req */
2840     /* li is the largest image < req */
2841     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2842
2843     if (!self->nicons) {
2844         ObClientIcon *parent = NULL;
2845
2846         if (self->transient_for) {
2847             if (self->transient_for != OB_TRAN_GROUP)
2848                 parent = client_icon_recursive(self->transient_for, w, h);
2849             else {
2850                 GSList *it;
2851                 for (it = self->group->members; it; it = g_slist_next(it)) {
2852                     ObClient *c = it->data;
2853                     if (c != self && !c->transient_for) {
2854                         if ((parent = client_icon_recursive(c, w, h)))
2855                             break;
2856                     }
2857                 }
2858             }
2859         }
2860         
2861         return parent;
2862     }
2863
2864     for (i = 0; i < self->nicons; ++i) {
2865         size = self->icons[i].width * self->icons[i].height;
2866         if (size < smallest && size >= (unsigned)(w * h)) {
2867             smallest = size;
2868             si = i;
2869         }
2870         if (size > largest && size <= (unsigned)(w * h)) {
2871             largest = size;
2872             li = i;
2873         }
2874     }
2875     if (largest == 0) /* didnt find one smaller than the requested size */
2876         return &self->icons[si];
2877     return &self->icons[li];
2878 }
2879
2880 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2881 {
2882     ObClientIcon *ret;
2883     static ObClientIcon deficon;
2884
2885     if (!(ret = client_icon_recursive(self, w, h))) {
2886         deficon.width = deficon.height = 48;
2887         deficon.data = ob_rr_theme->def_win_icon;
2888         ret = &deficon;
2889     }
2890     return ret;
2891 }
2892
2893 /* this be mostly ripped from fvwm */
2894 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2895 {
2896     gint my_cx, my_cy, his_cx, his_cy;
2897     gint offset = 0;
2898     gint distance = 0;
2899     gint score, best_score;
2900     ObClient *best_client, *cur;
2901     GList *it;
2902
2903     if(!client_list)
2904         return NULL;
2905
2906     /* first, find the centre coords of the currently focused window */
2907     my_cx = c->frame->area.x + c->frame->area.width / 2;
2908     my_cy = c->frame->area.y + c->frame->area.height / 2;
2909
2910     best_score = -1;
2911     best_client = NULL;
2912
2913     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
2914         cur = it->data;
2915
2916         /* the currently selected window isn't interesting */
2917         if(cur == c)
2918             continue;
2919         if (!client_normal(cur))
2920             continue;
2921         /* using c->desktop instead of screen_desktop doesn't work if the
2922          * current window was omnipresent, hope this doesn't have any other
2923          * side effects */
2924         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
2925             continue;
2926         if(cur->iconic)
2927             continue;
2928         if(client_focus_target(cur) == cur &&
2929            !(cur->can_focus || cur->focus_notify))
2930             continue;
2931
2932         /* find the centre coords of this window, from the
2933          * currently focused window's point of view */
2934         his_cx = (cur->frame->area.x - my_cx)
2935             + cur->frame->area.width / 2;
2936         his_cy = (cur->frame->area.y - my_cy)
2937             + cur->frame->area.height / 2;
2938
2939         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
2940            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
2941             gint tx;
2942             /* Rotate the diagonals 45 degrees counterclockwise.
2943              * To do this, multiply the matrix /+h +h\ with the
2944              * vector (x y).                   \-h +h/
2945              * h = sqrt(0.5). We can set h := 1 since absolute
2946              * distance doesn't matter here. */
2947             tx = his_cx + his_cy;
2948             his_cy = -his_cx + his_cy;
2949             his_cx = tx;
2950         }
2951
2952         switch(dir) {
2953         case OB_DIRECTION_NORTH:
2954         case OB_DIRECTION_SOUTH:
2955         case OB_DIRECTION_NORTHEAST:
2956         case OB_DIRECTION_SOUTHWEST:
2957             offset = (his_cx < 0) ? -his_cx : his_cx;
2958             distance = ((dir == OB_DIRECTION_NORTH ||
2959                          dir == OB_DIRECTION_NORTHEAST) ?
2960                         -his_cy : his_cy);
2961             break;
2962         case OB_DIRECTION_EAST:
2963         case OB_DIRECTION_WEST:
2964         case OB_DIRECTION_SOUTHEAST:
2965         case OB_DIRECTION_NORTHWEST:
2966             offset = (his_cy < 0) ? -his_cy : his_cy;
2967             distance = ((dir == OB_DIRECTION_WEST ||
2968                          dir == OB_DIRECTION_NORTHWEST) ?
2969                         -his_cx : his_cx);
2970             break;
2971         }
2972
2973         /* the target must be in the requested direction */
2974         if(distance <= 0)
2975             continue;
2976
2977         /* Calculate score for this window.  The smaller the better. */
2978         score = distance + offset;
2979
2980         /* windows more than 45 degrees off the direction are
2981          * heavily penalized and will only be chosen if nothing
2982          * else within a million pixels */
2983         if(offset > distance)
2984             score += 1000000;
2985
2986         if(best_score == -1 || score < best_score)
2987             best_client = cur,
2988                 best_score = score;
2989     }
2990
2991     return best_client;
2992 }
2993
2994 void client_set_layer(ObClient *self, gint layer)
2995 {
2996     if (layer < 0) {
2997         self->below = TRUE;
2998         self->above = FALSE;
2999     } else if (layer == 0) {
3000         self->below = self->above = FALSE;
3001     } else {
3002         self->below = FALSE;
3003         self->above = TRUE;
3004     }
3005     client_calc_layer(self);
3006     client_change_state(self); /* reflect this in the state hints */
3007 }
3008
3009 void client_set_undecorated(ObClient *self, gboolean undecorated)
3010 {
3011     if (self->undecorated != undecorated) {
3012         self->undecorated = undecorated;
3013         client_setup_decor_and_functions(self);
3014         /* Make sure the client knows it might have moved. Maybe there is a
3015          * better way of doing this so only one client_configure is sent, but
3016          * since 125 of these are sent per second when moving the window (with
3017          * user = FALSE) i doubt it matters much.
3018          */
3019         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3020                          self->area.width, self->area.height, TRUE, TRUE);
3021         client_change_state(self); /* reflect this in the state hints */
3022     }
3023 }
3024
3025 /* Determines which physical monitor a client is on by calculating the
3026    area of the part of the client on each monitor.  The number of the
3027    monitor containing the greatest area of the client is returned.*/
3028 guint client_monitor(ObClient *self)
3029 {
3030     guint i;
3031     guint most = 0;
3032     guint mostv = 0;
3033
3034     for (i = 0; i < screen_num_monitors; ++i) {
3035         Rect *area = screen_physical_area_monitor(i);
3036         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
3037             Rect r;
3038             guint v;
3039
3040             RECT_SET_INTERSECTION(r, *area, self->frame->area);
3041             v = r.width * r.height;
3042
3043             if (v > mostv) {
3044                 mostv = v;
3045                 most = i;
3046             }
3047         }
3048     }
3049     return most;
3050 }
3051
3052 ObClient *client_search_top_transient(ObClient *self)
3053 {
3054     /* move up the transient chain as far as possible */
3055     if (self->transient_for) {
3056         if (self->transient_for != OB_TRAN_GROUP) {
3057             return client_search_top_transient(self->transient_for);
3058         } else {
3059             GSList *it;
3060
3061             g_assert(self->group);
3062
3063             for (it = self->group->members; it; it = g_slist_next(it)) {
3064                 ObClient *c = it->data;
3065
3066                 /* checking transient_for prevents infinate loops! */
3067                 if (c != self && !c->transient_for)
3068                     break;
3069             }
3070             if (it)
3071                 return it->data;
3072         }
3073     }
3074
3075     return self;
3076 }
3077
3078 ObClient *client_search_focus_parent(ObClient *self)
3079 {
3080     if (self->transient_for) {
3081         if (self->transient_for != OB_TRAN_GROUP) {
3082             if (client_focused(self->transient_for))
3083                 return self->transient_for;
3084         } else {
3085             GSList *it;
3086
3087             for (it = self->group->members; it; it = g_slist_next(it)) {
3088                 ObClient *c = it->data;
3089
3090                 /* checking transient_for prevents infinate loops! */
3091                 if (c != self && !c->transient_for)
3092                     if (client_focused(c))
3093                         return c;
3094             }
3095         }
3096     }
3097
3098     return NULL;
3099 }
3100
3101 ObClient *client_search_parent(ObClient *self, ObClient *search)
3102 {
3103     if (self->transient_for) {
3104         if (self->transient_for != OB_TRAN_GROUP) {
3105             if (self->transient_for == search)
3106                 return search;
3107         } else {
3108             GSList *it;
3109
3110             for (it = self->group->members; it; it = g_slist_next(it)) {
3111                 ObClient *c = it->data;
3112
3113                 /* checking transient_for prevents infinate loops! */
3114                 if (c != self && !c->transient_for)
3115                     if (c == search)
3116                         return search;
3117             }
3118         }
3119     }
3120
3121     return NULL;
3122 }
3123
3124 ObClient *client_search_transient(ObClient *self, ObClient *search)
3125 {
3126     GSList *sit;
3127
3128     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3129         if (sit->data == search)
3130             return search;
3131         if (client_search_transient(sit->data, search))
3132             return search;
3133     }
3134     return NULL;
3135 }
3136
3137 void client_update_sm_client_id(ObClient *self)
3138 {
3139     g_free(self->sm_client_id);
3140     self->sm_client_id = NULL;
3141
3142     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3143         self->group)
3144         PROP_GETS(self->group->leader, sm_client_id, locale,
3145                   &self->sm_client_id);
3146 }
3147
3148 /* finds the nearest edge in the given direction from the current client
3149  * note to self: the edge is the -frame- edge (the actual one), not the
3150  * client edge.
3151  */
3152 gint client_directional_edge_search(ObClient *c, ObDirection dir)
3153 {
3154     gint dest, monitor_dest;
3155     gint my_edge_start, my_edge_end, my_offset;
3156     GList *it;
3157     Rect *a, *monitor;
3158     
3159     if(!client_list)
3160         return -1;
3161
3162     a = screen_area(c->desktop);
3163     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3164
3165     switch(dir) {
3166     case OB_DIRECTION_NORTH:
3167         my_edge_start = c->frame->area.x;
3168         my_edge_end = c->frame->area.x + c->frame->area.width;
3169         my_offset = c->frame->area.y;
3170         
3171         /* default: top of screen */
3172         dest = a->y;
3173         monitor_dest = monitor->y;
3174         /* if the monitor edge comes before the screen edge, */
3175         /* use that as the destination instead. (For xinerama) */
3176         if (monitor_dest != dest && my_offset > monitor_dest)
3177             dest = monitor_dest; 
3178
3179         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3180             gint his_edge_start, his_edge_end, his_offset;
3181             ObClient *cur = it->data;
3182
3183             if(cur == c)
3184                 continue;
3185             if(!client_normal(cur))
3186                 continue;
3187             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3188                 continue;
3189             if(cur->iconic)
3190                 continue;
3191             if(cur->layer < c->layer && !config_resist_layers_below)
3192                 continue;
3193
3194             his_edge_start = cur->frame->area.x;
3195             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3196             his_offset = cur->frame->area.y + cur->frame->area.height;
3197
3198             if(his_offset + 1 > my_offset)
3199                 continue;
3200
3201             if(his_offset < dest)
3202                 continue;
3203             
3204             if(his_edge_start >= my_edge_start &&
3205                his_edge_start <= my_edge_end)
3206                 dest = his_offset;
3207
3208             if(my_edge_start >= his_edge_start &&
3209                my_edge_start <= his_edge_end)
3210                 dest = his_offset;
3211
3212         }
3213         break;
3214     case OB_DIRECTION_SOUTH:
3215         my_edge_start = c->frame->area.x;
3216         my_edge_end = c->frame->area.x + c->frame->area.width;
3217         my_offset = c->frame->area.y + c->frame->area.height;
3218
3219         /* default: bottom of screen */
3220         dest = a->y + a->height;
3221         monitor_dest = monitor->y + monitor->height;
3222         /* if the monitor edge comes before the screen edge, */
3223         /* use that as the destination instead. (For xinerama) */
3224         if (monitor_dest != dest && my_offset < monitor_dest)
3225             dest = monitor_dest; 
3226
3227         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3228             gint his_edge_start, his_edge_end, his_offset;
3229             ObClient *cur = it->data;
3230
3231             if(cur == c)
3232                 continue;
3233             if(!client_normal(cur))
3234                 continue;
3235             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3236                 continue;
3237             if(cur->iconic)
3238                 continue;
3239             if(cur->layer < c->layer && !config_resist_layers_below)
3240                 continue;
3241
3242             his_edge_start = cur->frame->area.x;
3243             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3244             his_offset = cur->frame->area.y;
3245
3246
3247             if(his_offset - 1 < my_offset)
3248                 continue;
3249             
3250             if(his_offset > dest)
3251                 continue;
3252             
3253             if(his_edge_start >= my_edge_start &&
3254                his_edge_start <= my_edge_end)
3255                 dest = his_offset;
3256
3257             if(my_edge_start >= his_edge_start &&
3258                my_edge_start <= his_edge_end)
3259                 dest = his_offset;
3260
3261         }
3262         break;
3263     case OB_DIRECTION_WEST:
3264         my_edge_start = c->frame->area.y;
3265         my_edge_end = c->frame->area.y + c->frame->area.height;
3266         my_offset = c->frame->area.x;
3267
3268         /* default: leftmost egde of screen */
3269         dest = a->x;
3270         monitor_dest = monitor->x;
3271         /* if the monitor edge comes before the screen edge, */
3272         /* use that as the destination instead. (For xinerama) */
3273         if (monitor_dest != dest && my_offset > monitor_dest)
3274             dest = monitor_dest;            
3275
3276         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3277             gint his_edge_start, his_edge_end, his_offset;
3278             ObClient *cur = it->data;
3279
3280             if(cur == c)
3281                 continue;
3282             if(!client_normal(cur))
3283                 continue;
3284             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3285                 continue;
3286             if(cur->iconic)
3287                 continue;
3288             if(cur->layer < c->layer && !config_resist_layers_below)
3289                 continue;
3290
3291             his_edge_start = cur->frame->area.y;
3292             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3293             his_offset = cur->frame->area.x + cur->frame->area.width;
3294
3295             if(his_offset + 1 > my_offset)
3296                 continue;
3297             
3298             if(his_offset < dest)
3299                 continue;
3300             
3301             if(his_edge_start >= my_edge_start &&
3302                his_edge_start <= my_edge_end)
3303                 dest = his_offset;
3304
3305             if(my_edge_start >= his_edge_start &&
3306                my_edge_start <= his_edge_end)
3307                 dest = his_offset;
3308                 
3309
3310         }
3311         break;
3312     case OB_DIRECTION_EAST:
3313         my_edge_start = c->frame->area.y;
3314         my_edge_end = c->frame->area.y + c->frame->area.height;
3315         my_offset = c->frame->area.x + c->frame->area.width;
3316         
3317         /* default: rightmost edge of screen */
3318         dest = a->x + a->width;
3319         monitor_dest = monitor->x + monitor->width;
3320         /* if the monitor edge comes before the screen edge, */
3321         /* use that as the destination instead. (For xinerama) */
3322         if (monitor_dest != dest && my_offset < monitor_dest)
3323             dest = monitor_dest;            
3324
3325         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3326             gint his_edge_start, his_edge_end, his_offset;
3327             ObClient *cur = it->data;
3328
3329             if(cur == c)
3330                 continue;
3331             if(!client_normal(cur))
3332                 continue;
3333             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3334                 continue;
3335             if(cur->iconic)
3336                 continue;
3337             if(cur->layer < c->layer && !config_resist_layers_below)
3338                 continue;
3339
3340             his_edge_start = cur->frame->area.y;
3341             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3342             his_offset = cur->frame->area.x;
3343
3344             if(his_offset - 1 < my_offset)
3345                 continue;
3346             
3347             if(his_offset > dest)
3348                 continue;
3349             
3350             if(his_edge_start >= my_edge_start &&
3351                his_edge_start <= my_edge_end)
3352                 dest = his_offset;
3353
3354             if(my_edge_start >= his_edge_start &&
3355                my_edge_start <= his_edge_end)
3356                 dest = his_offset;
3357
3358         }
3359         break;
3360     case OB_DIRECTION_NORTHEAST:
3361     case OB_DIRECTION_SOUTHEAST:
3362     case OB_DIRECTION_NORTHWEST:
3363     case OB_DIRECTION_SOUTHWEST:
3364         /* not implemented */
3365     default:
3366         g_assert_not_reached();
3367         dest = 0; /* suppress warning */
3368     }
3369     return dest;
3370 }
3371
3372 ObClient* client_under_pointer()
3373 {
3374     gint x, y;
3375     GList *it;
3376     ObClient *ret = NULL;
3377
3378     if (screen_pointer_pos(&x, &y)) {
3379         for (it = stacking_list; it; it = g_list_next(it)) {
3380             if (WINDOW_IS_CLIENT(it->data)) {
3381                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3382                 if (c->frame->visible &&
3383                     RECT_CONTAINS(c->frame->area, x, y)) {
3384                     ret = c;
3385                     break;
3386                 }
3387             }
3388         }
3389     }
3390     return ret;
3391 }
3392
3393 gboolean client_has_group_siblings(ObClient *self)
3394 {
3395     return self->group && self->group->members->next;
3396 }