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