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