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