797d5b85198faaa1727925cf148efa6b9a0e37cc
[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
1581                 if (c->title_count == 1) {
1582                     if (!strcmp(c->title, data))
1583                         nums |= 1 << c->title_count;
1584                 } else {
1585                     size_t len;
1586                     gchar *end;
1587
1588                     /* find the beginning of our " - [%u]", this relies on
1589                      that syntax being used */
1590                     end = strrchr(c->title, '-') - 1; 
1591                     len = end - c->title;
1592                     if (!strncmp(c->title, data, len))
1593                         nums |= 1 << c->title_count;
1594                 }
1595             }
1596         /* find first free number */
1597         for (i = 1; i <= 32; ++i)
1598             if (!(nums & (1 << i))) {
1599                 if (self->title_count == 1 || i == 1)
1600                     self->title_count = i;
1601                 break;
1602             }
1603         /* dont display the number for the first window */
1604         if (self->title_count > 1) {
1605             gchar *ndata;
1606             ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1607             g_free(data);
1608             data = ndata;
1609         }
1610     } else
1611         self->title_count = 1;
1612
1613 no_number:
1614     PROP_SETS(self->window, net_wm_visible_name, data);
1615     self->title = data;
1616
1617     if (self->frame)
1618         frame_adjust_title(self->frame);
1619
1620     g_free(old_title);
1621
1622     /* update the icon title */
1623     data = NULL;
1624     g_free(self->icon_title);
1625
1626     read_title = TRUE;
1627     /* try netwm */
1628     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1629         /* try old x stuff */
1630         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data)
1631               || PROP_GETS(self->window, wm_icon_name, utf8, &data))) {
1632             data = g_strdup(self->title);
1633             read_title = FALSE;
1634         }
1635
1636     /* append the title count, dont display the number for the first window.
1637      * We don't need to check for config_title_number here since title_count
1638      * is not set above 1 then. */
1639     if (read_title && self->title_count > 1) {
1640         gchar *newdata;
1641         newdata = g_strdup_printf("%s - [%u]", data, self->title_count);
1642         g_free(data);
1643         data = newdata;
1644     }
1645
1646     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1647
1648     self->icon_title = data;
1649 }
1650
1651 void client_update_class(ObClient *self)
1652 {
1653     gchar **data;
1654     gchar *s;
1655
1656     if (self->name) g_free(self->name);
1657     if (self->class) g_free(self->class);
1658     if (self->role) g_free(self->role);
1659
1660     self->name = self->class = self->role = NULL;
1661
1662     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1663         if (data[0]) {
1664             self->name = g_strdup(data[0]);
1665             if (data[1])
1666                 self->class = g_strdup(data[1]);
1667         }
1668         g_strfreev(data);     
1669     }
1670
1671     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1672         self->role = s;
1673
1674     if (self->name == NULL) self->name = g_strdup("");
1675     if (self->class == NULL) self->class = g_strdup("");
1676     if (self->role == NULL) self->role = g_strdup("");
1677 }
1678
1679 void client_update_strut(ObClient *self)
1680 {
1681     guint num;
1682     guint32 *data;
1683     gboolean got = FALSE;
1684     StrutPartial strut;
1685
1686     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1687                     &data, &num)) {
1688         if (num == 12) {
1689             got = TRUE;
1690             STRUT_PARTIAL_SET(strut,
1691                               data[0], data[2], data[1], data[3],
1692                               data[4], data[5], data[8], data[9],
1693                               data[6], data[7], data[10], data[11]);
1694         }
1695         g_free(data);
1696     }
1697
1698     if (!got &&
1699         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1700         if (num == 4) {
1701             const Rect *a;
1702
1703             got = TRUE;
1704
1705             /* use the screen's width/height */
1706             a = screen_physical_area();
1707
1708             STRUT_PARTIAL_SET(strut,
1709                               data[0], data[2], data[1], data[3],
1710                               a->y, a->y + a->height - 1,
1711                               a->x, a->x + a->width - 1,
1712                               a->y, a->y + a->height - 1,
1713                               a->x, a->x + a->width - 1);
1714         }
1715         g_free(data);
1716     }
1717
1718     if (!got)
1719         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1720                           0, 0, 0, 0, 0, 0, 0, 0);
1721
1722     if (!STRUT_EQUAL(strut, self->strut)) {
1723         self->strut = strut;
1724
1725         /* updating here is pointless while we're being mapped cuz we're not in
1726            the client list yet */
1727         if (self->frame)
1728             screen_update_areas();
1729     }
1730 }
1731
1732 void client_update_icons(ObClient *self)
1733 {
1734     guint num;
1735     guint32 *data;
1736     guint w, h, i, j;
1737
1738     for (i = 0; i < self->nicons; ++i)
1739         g_free(self->icons[i].data);
1740     if (self->nicons > 0)
1741         g_free(self->icons);
1742     self->nicons = 0;
1743
1744     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1745         /* figure out how many valid icons are in here */
1746         i = 0;
1747         while (num - i > 2) {
1748             w = data[i++];
1749             h = data[i++];
1750             i += w * h;
1751             if (i > num || w*h == 0) break;
1752             ++self->nicons;
1753         }
1754
1755         self->icons = g_new(ObClientIcon, self->nicons);
1756     
1757         /* store the icons */
1758         i = 0;
1759         for (j = 0; j < self->nicons; ++j) {
1760             guint x, y, t;
1761
1762             w = self->icons[j].width = data[i++];
1763             h = self->icons[j].height = data[i++];
1764
1765             if (w*h == 0) continue;
1766
1767             self->icons[j].data = g_new(RrPixel32, w * h);
1768             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1769                 if (x >= w) {
1770                     x = 0;
1771                     ++y;
1772                 }
1773                 self->icons[j].data[t] =
1774                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1775                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1776                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1777                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1778             }
1779             g_assert(i <= num);
1780         }
1781
1782         g_free(data);
1783     } else if (PROP_GETA32(self->window, kwm_win_icon,
1784                            kwm_win_icon, &data, &num)) {
1785         if (num == 2) {
1786             self->nicons++;
1787             self->icons = g_new(ObClientIcon, self->nicons);
1788             xerror_set_ignore(TRUE);
1789             if (!RrPixmapToRGBA(ob_rr_inst,
1790                                 data[0], data[1],
1791                                 &self->icons[self->nicons-1].width,
1792                                 &self->icons[self->nicons-1].height,
1793                                 &self->icons[self->nicons-1].data)) {
1794                 g_free(&self->icons[self->nicons-1]);
1795                 self->nicons--;
1796             }
1797             xerror_set_ignore(FALSE);
1798         }
1799         g_free(data);
1800     } else {
1801         XWMHints *hints;
1802
1803         if ((hints = XGetWMHints(ob_display, self->window))) {
1804             if (hints->flags & IconPixmapHint) {
1805                 self->nicons++;
1806                 self->icons = g_new(ObClientIcon, self->nicons);
1807                 xerror_set_ignore(TRUE);
1808                 if (!RrPixmapToRGBA(ob_rr_inst,
1809                                     hints->icon_pixmap,
1810                                     (hints->flags & IconMaskHint ?
1811                                      hints->icon_mask : None),
1812                                     &self->icons[self->nicons-1].width,
1813                                     &self->icons[self->nicons-1].height,
1814                                     &self->icons[self->nicons-1].data)){
1815                     g_free(&self->icons[self->nicons-1]);
1816                     self->nicons--;
1817                 }
1818                 xerror_set_ignore(FALSE);
1819             }
1820             XFree(hints);
1821         }
1822     }
1823
1824     if (self->frame)
1825         frame_adjust_icon(self->frame);
1826 }
1827
1828 static void client_change_state(ObClient *self)
1829 {
1830     gulong state[2];
1831     gulong netstate[11];
1832     guint num;
1833
1834     state[0] = self->wmstate;
1835     state[1] = None;
1836     PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1837
1838     num = 0;
1839     if (self->modal)
1840         netstate[num++] = prop_atoms.net_wm_state_modal;
1841     if (self->shaded)
1842         netstate[num++] = prop_atoms.net_wm_state_shaded;
1843     if (self->iconic)
1844         netstate[num++] = prop_atoms.net_wm_state_hidden;
1845     if (self->skip_taskbar)
1846         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1847     if (self->skip_pager)
1848         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1849     if (self->fullscreen)
1850         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1851     if (self->max_vert)
1852         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1853     if (self->max_horz)
1854         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1855     if (self->above)
1856         netstate[num++] = prop_atoms.net_wm_state_above;
1857     if (self->below)
1858         netstate[num++] = prop_atoms.net_wm_state_below;
1859     if (self->undecorated)
1860         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1861     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1862
1863     client_calc_layer(self);
1864
1865     if (self->frame)
1866         frame_adjust_state(self->frame);
1867 }
1868
1869 ObClient *client_search_focus_tree(ObClient *self)
1870 {
1871     GSList *it;
1872     ObClient *ret;
1873
1874     for (it = self->transients; it; it = g_slist_next(it)) {
1875         if (client_focused(it->data)) return it->data;
1876         if ((ret = client_search_focus_tree(it->data))) return ret;
1877     }
1878     return NULL;
1879 }
1880
1881 ObClient *client_search_focus_tree_full(ObClient *self)
1882 {
1883     if (self->transient_for) {
1884         if (self->transient_for != OB_TRAN_GROUP) {
1885             return client_search_focus_tree_full(self->transient_for);
1886         } else {
1887             GSList *it;
1888             gboolean recursed = FALSE;
1889         
1890             for (it = self->group->members; it; it = g_slist_next(it))
1891                 if (!((ObClient*)it->data)->transient_for) {
1892                     ObClient *c;
1893                     if ((c = client_search_focus_tree_full(it->data)))
1894                         return c;
1895                     recursed = TRUE;
1896                 }
1897             if (recursed)
1898                 return NULL;
1899         }
1900     }
1901
1902     /* this function checks the whole tree, the client_search_focus_tree~
1903        does not, so we need to check this window */
1904     if (client_focused(self))
1905         return self;
1906     return client_search_focus_tree(self);
1907 }
1908
1909 static ObStackingLayer calc_layer(ObClient *self)
1910 {
1911     ObStackingLayer l;
1912
1913     if (self->fullscreen &&
1914         (client_focused(self) || client_search_focus_tree(self)))
1915         l = OB_STACKING_LAYER_FULLSCREEN;
1916     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1917         l = OB_STACKING_LAYER_DESKTOP;
1918     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1919         if (self->below) l = OB_STACKING_LAYER_NORMAL;
1920         else l = OB_STACKING_LAYER_ABOVE;
1921     }
1922     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1923     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1924     else l = OB_STACKING_LAYER_NORMAL;
1925
1926     return l;
1927 }
1928
1929 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1930                                         ObStackingLayer l, gboolean raised)
1931 {
1932     ObStackingLayer old, own;
1933     GSList *it;
1934
1935     old = self->layer;
1936     own = calc_layer(self);
1937     self->layer = l > own ? l : own;
1938
1939     for (it = self->transients; it; it = g_slist_next(it))
1940         client_calc_layer_recursive(it->data, orig,
1941                                     l, raised ? raised : l != old);
1942
1943     if (!raised && l != old)
1944         if (orig->frame) { /* only restack if the original window is managed */
1945             stacking_remove(CLIENT_AS_WINDOW(self));
1946             stacking_add(CLIENT_AS_WINDOW(self));
1947         }
1948 }
1949
1950 void client_calc_layer(ObClient *self)
1951 {
1952     ObStackingLayer l;
1953     ObClient *orig;
1954
1955     orig = self;
1956
1957     /* transients take on the layer of their parents */
1958     self = client_search_top_transient(self);
1959
1960     l = calc_layer(self);
1961
1962     client_calc_layer_recursive(self, orig, l, FALSE);
1963 }
1964
1965 gboolean client_should_show(ObClient *self)
1966 {
1967     if (self->iconic)
1968         return FALSE;
1969     if (client_normal(self) && screen_showing_desktop)
1970         return FALSE;
1971     /*
1972     if (self->transient_for) {
1973         if (self->transient_for != OB_TRAN_GROUP)
1974             return client_should_show(self->transient_for);
1975         else {
1976             GSList *it;
1977
1978             for (it = self->group->members; it; it = g_slist_next(it)) {
1979                 ObClient *c = it->data;
1980                 if (c != self && !c->transient_for) {
1981                     if (client_should_show(c))
1982                         return TRUE;
1983                 }
1984             }
1985         }
1986     }
1987     */
1988     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1989         return TRUE;
1990     
1991     return FALSE;
1992 }
1993
1994 static void client_showhide(ObClient *self)
1995 {
1996
1997     if (client_should_show(self))
1998         frame_show(self->frame);
1999     else
2000         frame_hide(self->frame);
2001 }
2002
2003 gboolean client_normal(ObClient *self) {
2004     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2005               self->type == OB_CLIENT_TYPE_DOCK ||
2006               self->type == OB_CLIENT_TYPE_SPLASH);
2007 }
2008
2009 static void client_apply_startup_state(ObClient *self)
2010 {
2011     /* these are in a carefully crafted order.. */
2012
2013     if (self->iconic) {
2014         self->iconic = FALSE;
2015         client_iconify(self, TRUE, FALSE);
2016     }
2017     if (self->fullscreen) {
2018         self->fullscreen = FALSE;
2019         client_fullscreen(self, TRUE, FALSE);
2020     }
2021     if (self->undecorated) {
2022         self->undecorated = FALSE;
2023         client_set_undecorated(self, TRUE);
2024     }
2025     if (self->shaded) {
2026         self->shaded = FALSE;
2027         client_shade(self, TRUE);
2028     }
2029     if (self->urgent)
2030         client_urgent_notify(self);
2031   
2032     if (self->max_vert && self->max_horz) {
2033         self->max_vert = self->max_horz = FALSE;
2034         client_maximize(self, TRUE, 0, FALSE);
2035     } else if (self->max_vert) {
2036         self->max_vert = FALSE;
2037         client_maximize(self, TRUE, 2, FALSE);
2038     } else if (self->max_horz) {
2039         self->max_horz = FALSE;
2040         client_maximize(self, TRUE, 1, FALSE);
2041     }
2042
2043     /* nothing to do for the other states:
2044        skip_taskbar
2045        skip_pager
2046        modal
2047        above
2048        below
2049     */
2050 }
2051
2052 void client_configure_full(ObClient *self, ObCorner anchor,
2053                            gint x, gint y, gint w, gint h,
2054                            gboolean user, gboolean final,
2055                            gboolean force_reply)
2056 {
2057     gint oldw, oldh;
2058     gboolean send_resize_client;
2059     gboolean moved = FALSE, resized = FALSE;
2060     guint fdecor = self->frame->decorations;
2061     gboolean fhorz = self->frame->max_horz;
2062
2063     /* make the frame recalculate its dimentions n shit without changing
2064        anything visible for real, this way the constraints below can work with
2065        the updated frame dimensions. */
2066     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2067
2068     /* gets the frame's position */
2069     frame_client_gravity(self->frame, &x, &y);
2070
2071     /* these positions are frame positions, not client positions */
2072
2073     /* set the size and position if fullscreen */
2074     if (self->fullscreen) {
2075         Rect *a;
2076         guint i;
2077
2078         i = client_monitor(self);
2079         a = screen_physical_area_monitor(i);
2080
2081         x = a->x;
2082         y = a->y;
2083         w = a->width;
2084         h = a->height;
2085
2086         user = FALSE; /* ignore that increment etc shit when in fullscreen */
2087     } else {
2088         Rect *a;
2089
2090         a = screen_area_monitor(self->desktop, client_monitor(self));
2091
2092         /* set the size and position if maximized */
2093         if (self->max_horz) {
2094             x = a->x;
2095             w = a->width - self->frame->size.left - self->frame->size.right;
2096         }
2097         if (self->max_vert) {
2098             y = a->y;
2099             h = a->height - self->frame->size.top - self->frame->size.bottom;
2100         }
2101     }
2102
2103     /* gets the client's position */
2104     frame_frame_gravity(self->frame, &x, &y);
2105
2106     /* these override the above states! if you cant move you can't move! */
2107     if (user) {
2108         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2109             x = self->area.x;
2110             y = self->area.y;
2111         }
2112         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2113             w = self->area.width;
2114             h = self->area.height;
2115         }
2116     }
2117
2118     if (!(w == self->area.width && h == self->area.height)) {
2119         gint basew, baseh, minw, minh;
2120
2121         /* base size is substituted with min size if not specified */
2122         if (self->base_size.width || self->base_size.height) {
2123             basew = self->base_size.width;
2124             baseh = self->base_size.height;
2125         } else {
2126             basew = self->min_size.width;
2127             baseh = self->min_size.height;
2128         }
2129         /* min size is substituted with base size if not specified */
2130         if (self->min_size.width || self->min_size.height) {
2131             minw = self->min_size.width;
2132             minh = self->min_size.height;
2133         } else {
2134             minw = self->base_size.width;
2135             minh = self->base_size.height;
2136         }
2137
2138         /* if this is a user-requested resize, then check against min/max
2139            sizes */
2140
2141         /* smaller than min size or bigger than max size? */
2142         if (w > self->max_size.width) w = self->max_size.width;
2143         if (w < minw) w = minw;
2144         if (h > self->max_size.height) h = self->max_size.height;
2145         if (h < minh) h = minh;
2146
2147         w -= basew;
2148         h -= baseh;
2149
2150         /* keep to the increments */
2151         w /= self->size_inc.width;
2152         h /= self->size_inc.height;
2153
2154         /* you cannot resize to nothing */
2155         if (basew + w < 1) w = 1 - basew;
2156         if (baseh + h < 1) h = 1 - baseh;
2157   
2158         /* store the logical size */
2159         SIZE_SET(self->logical_size,
2160                  self->size_inc.width > 1 ? w : w + basew,
2161                  self->size_inc.height > 1 ? h : h + baseh);
2162
2163         w *= self->size_inc.width;
2164         h *= self->size_inc.height;
2165
2166         w += basew;
2167         h += baseh;
2168
2169         /* adjust the height to match the width for the aspect ratios.
2170            for this, min size is not substituted for base size ever. */
2171         w -= self->base_size.width;
2172         h -= self->base_size.height;
2173
2174         if (!self->fullscreen) {
2175             if (self->min_ratio)
2176                 if (h * self->min_ratio > w) {
2177                     h = (gint)(w / self->min_ratio);
2178
2179                     /* you cannot resize to nothing */
2180                     if (h < 1) {
2181                         h = 1;
2182                         w = (gint)(h * self->min_ratio);
2183                     }
2184                 }
2185             if (self->max_ratio)
2186                 if (h * self->max_ratio < w) {
2187                     h = (gint)(w / self->max_ratio);
2188
2189                     /* you cannot resize to nothing */
2190                     if (h < 1) {
2191                         h = 1;
2192                         w = (gint)(h * self->min_ratio);
2193                     }
2194                 }
2195         }
2196
2197         w += self->base_size.width;
2198         h += self->base_size.height;
2199     }
2200
2201     g_assert(w > 0);
2202     g_assert(h > 0);
2203
2204     switch (anchor) {
2205     case OB_CORNER_TOPLEFT:
2206         break;
2207     case OB_CORNER_TOPRIGHT:
2208         x -= w - self->area.width;
2209         break;
2210     case OB_CORNER_BOTTOMLEFT:
2211         y -= h - self->area.height;
2212         break;
2213     case OB_CORNER_BOTTOMRIGHT:
2214         x -= w - self->area.width;
2215         y -= h - self->area.height;
2216         break;
2217     }
2218
2219     moved = x != self->area.x || y != self->area.y;
2220     resized = w != self->area.width || h != self->area.height;
2221
2222     oldw = self->area.width;
2223     oldh = self->area.height;
2224     RECT_SET(self->area, x, y, w, h);
2225
2226     /* for app-requested resizes, always resize if 'resized' is true.
2227        for user-requested ones, only resize if final is true, or when
2228        resizing in redraw mode */
2229     send_resize_client = ((!user && resized) ||
2230                           (user && (final ||
2231                                     (resized && config_resize_redraw))));
2232
2233     /* if the client is enlarging, then resize the client before the frame */
2234     if (send_resize_client && user && (w > oldw || h > oldh))
2235         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2236
2237     /* move/resize the frame to match the request */
2238     if (self->frame) {
2239         if (self->decorations != fdecor || self->max_horz != fhorz)
2240             moved = resized = TRUE;
2241
2242         if (moved || resized)
2243             frame_adjust_area(self->frame, moved, resized, FALSE);
2244
2245         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2246         {
2247             XEvent event;
2248             event.type = ConfigureNotify;
2249             event.xconfigure.display = ob_display;
2250             event.xconfigure.event = self->window;
2251             event.xconfigure.window = self->window;
2252
2253             /* root window real coords */
2254             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2255                 self->border_width;
2256             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2257                 self->border_width;
2258             event.xconfigure.width = w;
2259             event.xconfigure.height = h;
2260             event.xconfigure.border_width = 0;
2261             event.xconfigure.above = self->frame->plate;
2262             event.xconfigure.override_redirect = FALSE;
2263             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2264                        FALSE, StructureNotifyMask, &event);
2265         }
2266     }
2267
2268     /* if the client is shrinking, then resize the frame before the client */
2269     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2270         XResizeWindow(ob_display, self->window, w, h);
2271
2272     XFlush(ob_display);
2273 }
2274
2275 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2276 {
2277     gint x, y, w, h;
2278
2279     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2280         self->fullscreen == fs) return;                   /* already done */
2281
2282     self->fullscreen = fs;
2283     client_change_state(self); /* change the state hints on the client,
2284                                   and adjust out layer/stacking */
2285
2286     if (fs) {
2287         if (savearea)
2288             self->pre_fullscreen_area = self->area;
2289
2290         /* these are not actually used cuz client_configure will set them
2291            as appropriate when the window is fullscreened */
2292         x = y = w = h = 0;
2293     } else {
2294         Rect *a;
2295
2296         if (self->pre_fullscreen_area.width > 0 &&
2297             self->pre_fullscreen_area.height > 0)
2298         {
2299             x = self->pre_fullscreen_area.x;
2300             y = self->pre_fullscreen_area.y;
2301             w = self->pre_fullscreen_area.width;
2302             h = self->pre_fullscreen_area.height;
2303             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2304         } else {
2305             /* pick some fallbacks... */
2306             a = screen_area_monitor(self->desktop, 0);
2307             x = a->x + a->width / 4;
2308             y = a->y + a->height / 4;
2309             w = a->width / 2;
2310             h = a->height / 2;
2311         }
2312     }
2313
2314     client_setup_decor_and_functions(self);
2315
2316     client_move_resize(self, x, y, w, h);
2317
2318     /* try focus us when we go into fullscreen mode */
2319     client_focus(self);
2320 }
2321
2322 static void client_iconify_recursive(ObClient *self,
2323                                      gboolean iconic, gboolean curdesk)
2324 {
2325     GSList *it;
2326     gboolean changed = FALSE;
2327
2328
2329     if (self->iconic != iconic) {
2330         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2331                  self->window);
2332
2333         self->iconic = iconic;
2334
2335         if (iconic) {
2336             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2337                 glong old;
2338
2339                 old = self->wmstate;
2340                 self->wmstate = IconicState;
2341                 if (old != self->wmstate)
2342                     PROP_MSG(self->window, kde_wm_change_state,
2343                              self->wmstate, 1, 0, 0);
2344
2345                 /* update the focus lists.. iconic windows go to the bottom of
2346                    the list, put the new iconic window at the 'top of the
2347                    bottom'. */
2348                 focus_order_to_top(self);
2349
2350                 changed = TRUE;
2351             }
2352         } else {
2353             glong old;
2354
2355             if (curdesk)
2356                 client_set_desktop(self, screen_desktop, FALSE);
2357
2358             old = self->wmstate;
2359             self->wmstate = self->shaded ? IconicState : NormalState;
2360             if (old != self->wmstate)
2361                 PROP_MSG(self->window, kde_wm_change_state,
2362                          self->wmstate, 1, 0, 0);
2363
2364             /* this puts it after the current focused window */
2365             focus_order_remove(self);
2366             focus_order_add_new(self);
2367
2368             changed = TRUE;
2369         }
2370     }
2371
2372     if (changed) {
2373         client_change_state(self);
2374         client_showhide(self);
2375         if (STRUT_EXISTS(self->strut))
2376             screen_update_areas();
2377     }
2378
2379     /* iconify all transients */
2380     for (it = self->transients; it; it = g_slist_next(it))
2381         if (it->data != self) client_iconify_recursive(it->data,
2382                                                        iconic, curdesk);
2383 }
2384
2385 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2386 {
2387     /* move up the transient chain as far as possible first */
2388     self = client_search_top_transient(self);
2389
2390     client_iconify_recursive(client_search_top_transient(self),
2391                              iconic, curdesk);
2392 }
2393
2394 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2395 {
2396     gint x, y, w, h;
2397      
2398     g_assert(dir == 0 || dir == 1 || dir == 2);
2399     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2400
2401     /* check if already done */
2402     if (max) {
2403         if (dir == 0 && self->max_horz && self->max_vert) return;
2404         if (dir == 1 && self->max_horz) return;
2405         if (dir == 2 && self->max_vert) return;
2406     } else {
2407         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2408         if (dir == 1 && !self->max_horz) return;
2409         if (dir == 2 && !self->max_vert) return;
2410     }
2411
2412     /* we just tell it to configure in the same place and client_configure
2413        worries about filling the screen with the window */
2414     x = self->area.x;
2415     y = self->area.y;
2416     w = self->area.width;
2417     h = self->area.height;
2418
2419     if (max) {
2420         if (savearea) {
2421             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2422                 RECT_SET(self->pre_max_area,
2423                          self->area.x, self->pre_max_area.y,
2424                          self->area.width, self->pre_max_area.height);
2425             }
2426             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2427                 RECT_SET(self->pre_max_area,
2428                          self->pre_max_area.x, self->area.y,
2429                          self->pre_max_area.width, self->area.height);
2430             }
2431         }
2432     } else {
2433         Rect *a;
2434
2435         a = screen_area_monitor(self->desktop, 0);
2436         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2437             if (self->pre_max_area.width > 0) {
2438                 x = self->pre_max_area.x;
2439                 w = self->pre_max_area.width;
2440
2441                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2442                          0, self->pre_max_area.height);
2443             } else {
2444                 /* pick some fallbacks... */
2445                 x = a->x + a->width / 4;
2446                 w = a->width / 2;
2447             }
2448         }
2449         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2450             if (self->pre_max_area.height > 0) {
2451                 y = self->pre_max_area.y;
2452                 h = self->pre_max_area.height;
2453
2454                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2455                          self->pre_max_area.width, 0);
2456             } else {
2457                 /* pick some fallbacks... */
2458                 y = a->y + a->height / 4;
2459                 h = a->height / 2;
2460             }
2461         }
2462     }
2463
2464     if (dir == 0 || dir == 1) /* horz */
2465         self->max_horz = max;
2466     if (dir == 0 || dir == 2) /* vert */
2467         self->max_vert = max;
2468
2469     client_change_state(self); /* change the state hints on the client */
2470
2471     client_setup_decor_and_functions(self);
2472
2473     client_move_resize(self, x, y, w, h);
2474 }
2475
2476 void client_shade(ObClient *self, gboolean shade)
2477 {
2478     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2479          shade) ||                         /* can't shade */
2480         self->shaded == shade) return;     /* already done */
2481
2482     /* when we're iconic, don't change the wmstate */
2483     if (!self->iconic) {
2484         glong old;
2485
2486         old = self->wmstate;
2487         self->wmstate = shade ? IconicState : NormalState;
2488         if (old != self->wmstate)
2489             PROP_MSG(self->window, kde_wm_change_state,
2490                      self->wmstate, 1, 0, 0);
2491     }
2492
2493     self->shaded = shade;
2494     client_change_state(self);
2495     /* resize the frame to just the titlebar */
2496     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2497 }
2498
2499 void client_close(ObClient *self)
2500 {
2501     XEvent ce;
2502
2503     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2504
2505     /* in the case that the client provides no means to requesting that it
2506        close, we just kill it */
2507     if (!self->delete_window)
2508         client_kill(self);
2509     
2510     /*
2511       XXX: itd be cool to do timeouts and shit here for killing the client's
2512       process off
2513       like... if the window is around after 5 seconds, then the close button
2514       turns a nice red, and if this function is called again, the client is
2515       explicitly killed.
2516     */
2517
2518     ce.xclient.type = ClientMessage;
2519     ce.xclient.message_type =  prop_atoms.wm_protocols;
2520     ce.xclient.display = ob_display;
2521     ce.xclient.window = self->window;
2522     ce.xclient.format = 32;
2523     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2524     ce.xclient.data.l[1] = event_curtime;
2525     ce.xclient.data.l[2] = 0l;
2526     ce.xclient.data.l[3] = 0l;
2527     ce.xclient.data.l[4] = 0l;
2528     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2529 }
2530
2531 void client_kill(ObClient *self)
2532 {
2533     XKillClient(ob_display, self->window);
2534 }
2535
2536 void client_set_desktop_recursive(ObClient *self,
2537                                   guint target, gboolean donthide)
2538 {
2539     guint old;
2540     GSList *it;
2541
2542     if (target != self->desktop) {
2543
2544         ob_debug("Setting desktop %u\n", target+1);
2545
2546         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2547
2548         /* remove from the old desktop(s) */
2549         focus_order_remove(self);
2550
2551         old = self->desktop;
2552         self->desktop = target;
2553         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2554         /* the frame can display the current desktop state */
2555         frame_adjust_state(self->frame);
2556         /* 'move' the window to the new desktop */
2557         if (!donthide)
2558             client_showhide(self);
2559         /* raise if it was not already on the desktop */
2560         if (old != DESKTOP_ALL)
2561             client_raise(self);
2562         if (STRUT_EXISTS(self->strut))
2563             screen_update_areas();
2564
2565         /* add to the new desktop(s) */
2566         if (config_focus_new)
2567             focus_order_to_top(self);
2568         else
2569             focus_order_to_bottom(self);
2570     }
2571
2572     /* move all transients */
2573     for (it = self->transients; it; it = g_slist_next(it))
2574         if (it->data != self) client_set_desktop_recursive(it->data,
2575                                                            target, donthide);
2576 }
2577
2578 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2579 {
2580     client_set_desktop_recursive(client_search_top_transient(self),
2581                                  target, donthide);
2582 }
2583
2584 ObClient *client_search_modal_child(ObClient *self)
2585 {
2586     GSList *it;
2587     ObClient *ret;
2588   
2589     for (it = self->transients; it; it = g_slist_next(it)) {
2590         ObClient *c = it->data;
2591         if ((ret = client_search_modal_child(c))) return ret;
2592         if (c->modal) return c;
2593     }
2594     return NULL;
2595 }
2596
2597 gboolean client_validate(ObClient *self)
2598 {
2599     XEvent e; 
2600
2601     XSync(ob_display, FALSE); /* get all events on the server */
2602
2603     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2604         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2605         XPutBackEvent(ob_display, &e);
2606         return FALSE;
2607     }
2608
2609     return TRUE;
2610 }
2611
2612 void client_set_wm_state(ObClient *self, glong state)
2613 {
2614     if (state == self->wmstate) return; /* no change */
2615   
2616     switch (state) {
2617     case IconicState:
2618         client_iconify(self, TRUE, TRUE);
2619         break;
2620     case NormalState:
2621         client_iconify(self, FALSE, TRUE);
2622         break;
2623     }
2624 }
2625
2626 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2627 {
2628     gboolean shaded = self->shaded;
2629     gboolean fullscreen = self->fullscreen;
2630     gboolean undecorated = self->undecorated;
2631     gboolean max_horz = self->max_horz;
2632     gboolean max_vert = self->max_vert;
2633     gboolean modal = self->modal;
2634     gboolean iconic = self->iconic;
2635     gint i;
2636
2637     if (!(action == prop_atoms.net_wm_state_add ||
2638           action == prop_atoms.net_wm_state_remove ||
2639           action == prop_atoms.net_wm_state_toggle))
2640         /* an invalid action was passed to the client message, ignore it */
2641         return; 
2642
2643     for (i = 0; i < 2; ++i) {
2644         Atom state = i == 0 ? data1 : data2;
2645     
2646         if (!state) continue;
2647
2648         /* if toggling, then pick whether we're adding or removing */
2649         if (action == prop_atoms.net_wm_state_toggle) {
2650             if (state == prop_atoms.net_wm_state_modal)
2651                 action = modal ? prop_atoms.net_wm_state_remove :
2652                     prop_atoms.net_wm_state_add;
2653             else if (state == prop_atoms.net_wm_state_maximized_vert)
2654                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2655                     prop_atoms.net_wm_state_add;
2656             else if (state == prop_atoms.net_wm_state_maximized_horz)
2657                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2658                     prop_atoms.net_wm_state_add;
2659             else if (state == prop_atoms.net_wm_state_shaded)
2660                 action = shaded ? prop_atoms.net_wm_state_remove :
2661                     prop_atoms.net_wm_state_add;
2662             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2663                 action = self->skip_taskbar ?
2664                     prop_atoms.net_wm_state_remove :
2665                     prop_atoms.net_wm_state_add;
2666             else if (state == prop_atoms.net_wm_state_skip_pager)
2667                 action = self->skip_pager ?
2668                     prop_atoms.net_wm_state_remove :
2669                     prop_atoms.net_wm_state_add;
2670             else if (state == prop_atoms.net_wm_state_hidden)
2671                 action = self->iconic ?
2672                     prop_atoms.net_wm_state_remove :
2673                     prop_atoms.net_wm_state_add;
2674             else if (state == prop_atoms.net_wm_state_fullscreen)
2675                 action = fullscreen ?
2676                     prop_atoms.net_wm_state_remove :
2677                     prop_atoms.net_wm_state_add;
2678             else if (state == prop_atoms.net_wm_state_above)
2679                 action = self->above ? prop_atoms.net_wm_state_remove :
2680                     prop_atoms.net_wm_state_add;
2681             else if (state == prop_atoms.net_wm_state_below)
2682                 action = self->below ? prop_atoms.net_wm_state_remove :
2683                     prop_atoms.net_wm_state_add;
2684             else if (state == prop_atoms.ob_wm_state_undecorated)
2685                 action = undecorated ? prop_atoms.net_wm_state_remove :
2686                     prop_atoms.net_wm_state_add;
2687         }
2688     
2689         if (action == prop_atoms.net_wm_state_add) {
2690             if (state == prop_atoms.net_wm_state_modal) {
2691                 modal = TRUE;
2692             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2693                 max_vert = TRUE;
2694             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2695                 max_horz = TRUE;
2696             } else if (state == prop_atoms.net_wm_state_shaded) {
2697                 shaded = TRUE;
2698             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2699                 self->skip_taskbar = TRUE;
2700             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2701                 self->skip_pager = TRUE;
2702             } else if (state == prop_atoms.net_wm_state_hidden) {
2703                 iconic = TRUE;
2704             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2705                 fullscreen = TRUE;
2706             } else if (state == prop_atoms.net_wm_state_above) {
2707                 self->above = TRUE;
2708                 self->below = FALSE;
2709             } else if (state == prop_atoms.net_wm_state_below) {
2710                 self->above = FALSE;
2711                 self->below = TRUE;
2712             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2713                 undecorated = TRUE;
2714             }
2715
2716         } else { /* action == prop_atoms.net_wm_state_remove */
2717             if (state == prop_atoms.net_wm_state_modal) {
2718                 modal = FALSE;
2719             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2720                 max_vert = FALSE;
2721             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2722                 max_horz = FALSE;
2723             } else if (state == prop_atoms.net_wm_state_shaded) {
2724                 shaded = FALSE;
2725             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2726                 self->skip_taskbar = FALSE;
2727             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2728                 self->skip_pager = FALSE;
2729             } else if (state == prop_atoms.net_wm_state_hidden) {
2730                 iconic = FALSE;
2731             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2732                 fullscreen = FALSE;
2733             } else if (state == prop_atoms.net_wm_state_above) {
2734                 self->above = FALSE;
2735             } else if (state == prop_atoms.net_wm_state_below) {
2736                 self->below = FALSE;
2737             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2738                 undecorated = FALSE;
2739             }
2740         }
2741     }
2742     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2743         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2744             /* toggling both */
2745             if (max_horz == max_vert) { /* both going the same way */
2746                 client_maximize(self, max_horz, 0, TRUE);
2747             } else {
2748                 client_maximize(self, max_horz, 1, TRUE);
2749                 client_maximize(self, max_vert, 2, TRUE);
2750             }
2751         } else {
2752             /* toggling one */
2753             if (max_horz != self->max_horz)
2754                 client_maximize(self, max_horz, 1, TRUE);
2755             else
2756                 client_maximize(self, max_vert, 2, TRUE);
2757         }
2758     }
2759     /* change fullscreen state before shading, as it will affect if the window
2760        can shade or not */
2761     if (fullscreen != self->fullscreen)
2762         client_fullscreen(self, fullscreen, TRUE);
2763     if (shaded != self->shaded)
2764         client_shade(self, shaded);
2765     if (undecorated != self->undecorated)
2766         client_set_undecorated(self, undecorated);
2767     if (modal != self->modal) {
2768         self->modal = modal;
2769         /* when a window changes modality, then its stacking order with its
2770            transients needs to change */
2771         client_raise(self);
2772     }
2773     if (iconic != self->iconic)
2774         client_iconify(self, iconic, FALSE);
2775
2776     client_calc_layer(self);
2777     client_change_state(self); /* change the hint to reflect these changes */
2778 }
2779
2780 ObClient *client_focus_target(ObClient *self)
2781 {
2782     ObClient *child;
2783      
2784     /* if we have a modal child, then focus it, not us */
2785     child = client_search_modal_child(client_search_top_transient(self));
2786     if (child) return child;
2787     return self;
2788 }
2789
2790 gboolean client_can_focus(ObClient *self)
2791 {
2792     XEvent ev;
2793
2794     /* choose the correct target */
2795     self = client_focus_target(self);
2796
2797     if (!self->frame->visible)
2798         return FALSE;
2799
2800     if (!(self->can_focus || self->focus_notify))
2801         return FALSE;
2802
2803     /* do a check to see if the window has already been unmapped or destroyed
2804        do this intelligently while watching out for unmaps we've generated
2805        (ignore_unmaps > 0) */
2806     if (XCheckTypedWindowEvent(ob_display, self->window,
2807                                DestroyNotify, &ev)) {
2808         XPutBackEvent(ob_display, &ev);
2809         return FALSE;
2810     }
2811     while (XCheckTypedWindowEvent(ob_display, self->window,
2812                                   UnmapNotify, &ev)) {
2813         if (self->ignore_unmaps) {
2814             self->ignore_unmaps--;
2815         } else {
2816             XPutBackEvent(ob_display, &ev);
2817             return FALSE;
2818         }
2819     }
2820
2821     return TRUE;
2822 }
2823
2824 gboolean client_focus(ObClient *self)
2825 {
2826     /* choose the correct target */
2827     self = client_focus_target(self);
2828
2829     if (!client_can_focus(self)) {
2830         if (!self->frame->visible) {
2831             /* update the focus lists */
2832             focus_order_to_top(self);
2833         }
2834         return FALSE;
2835     }
2836
2837     if (self->can_focus) {
2838         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2839            I choose to use it always, hopefully to find errors quicker, if any
2840            are left. (I hate X. I hate focus events.)
2841            
2842            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2843            #799. So now it is RevertToNone again.
2844         */
2845         XSetInputFocus(ob_display, self->window, RevertToNone,
2846                        event_curtime);
2847     }
2848
2849     if (self->focus_notify) {
2850         XEvent ce;
2851         ce.xclient.type = ClientMessage;
2852         ce.xclient.message_type = prop_atoms.wm_protocols;
2853         ce.xclient.display = ob_display;
2854         ce.xclient.window = self->window;
2855         ce.xclient.format = 32;
2856         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2857         ce.xclient.data.l[1] = event_curtime;
2858         ce.xclient.data.l[2] = 0l;
2859         ce.xclient.data.l[3] = 0l;
2860         ce.xclient.data.l[4] = 0l;
2861         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2862     }
2863
2864 #ifdef DEBUG_FOCUS
2865     ob_debug("%sively focusing %lx at %d\n",
2866              (self->can_focus ? "act" : "pass"),
2867              self->window, (gint) event_curtime);
2868 #endif
2869
2870     /* Cause the FocusIn to come back to us. Important for desktop switches,
2871        since otherwise we'll have no FocusIn on the queue and send it off to
2872        the focus_backup. */
2873     XSync(ob_display, FALSE);
2874     return TRUE;
2875 }
2876
2877 /* Used when the current client is closed, focus_last will then prevent
2878  * focus from going to the mouse pointer */
2879 void client_unfocus(ObClient *self)
2880 {
2881     if (focus_client == self) {
2882 #ifdef DEBUG_FOCUS
2883         ob_debug("client_unfocus for %lx\n", self->window);
2884 #endif
2885         focus_fallback(OB_FOCUS_FALLBACK_CLOSED);
2886     }
2887 }
2888
2889 void client_activate(ObClient *self, gboolean here, gboolean user)
2890 {
2891     /* XXX do some stuff here if user is false to determine if we really want
2892        to activate it or not (a parent or group member is currently active) */
2893
2894     if (client_normal(self) && screen_showing_desktop)
2895         screen_show_desktop(FALSE);
2896     if (self->iconic)
2897         client_iconify(self, FALSE, here);
2898     if (self->desktop != DESKTOP_ALL &&
2899         self->desktop != screen_desktop) {
2900         if (here)
2901             client_set_desktop(self, screen_desktop, FALSE);
2902         else
2903             screen_set_desktop(self->desktop);
2904     } else if (!self->frame->visible)
2905         /* if its not visible for other reasons, then don't mess
2906            with it */
2907         return;
2908     if (self->shaded)
2909         client_shade(self, FALSE);
2910
2911     client_focus(self);
2912
2913     /* we do this an action here. this is rather important. this is because
2914        we want the results from the focus change to take place BEFORE we go
2915        about raising the window. when a fullscreen window loses focus, we need
2916        this or else the raise wont be able to raise above the to-lose-focus
2917        fullscreen window. */
2918     client_raise(self);
2919 }
2920
2921 void client_raise(ObClient *self)
2922 {
2923     action_run_string("Raise", self);
2924 }
2925
2926 void client_lower(ObClient *self)
2927 {
2928     action_run_string("Lower", self);
2929 }
2930
2931 gboolean client_focused(ObClient *self)
2932 {
2933     return self == focus_client;
2934 }
2935
2936 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2937 {
2938     guint i;
2939     /* si is the smallest image >= req */
2940     /* li is the largest image < req */
2941     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2942
2943     if (!self->nicons) {
2944         ObClientIcon *parent = NULL;
2945
2946         if (self->transient_for) {
2947             if (self->transient_for != OB_TRAN_GROUP)
2948                 parent = client_icon_recursive(self->transient_for, w, h);
2949             else {
2950                 GSList *it;
2951                 for (it = self->group->members; it; it = g_slist_next(it)) {
2952                     ObClient *c = it->data;
2953                     if (c != self && !c->transient_for) {
2954                         if ((parent = client_icon_recursive(c, w, h)))
2955                             break;
2956                     }
2957                 }
2958             }
2959         }
2960         
2961         return parent;
2962     }
2963
2964     for (i = 0; i < self->nicons; ++i) {
2965         size = self->icons[i].width * self->icons[i].height;
2966         if (size < smallest && size >= (unsigned)(w * h)) {
2967             smallest = size;
2968             si = i;
2969         }
2970         if (size > largest && size <= (unsigned)(w * h)) {
2971             largest = size;
2972             li = i;
2973         }
2974     }
2975     if (largest == 0) /* didnt find one smaller than the requested size */
2976         return &self->icons[si];
2977     return &self->icons[li];
2978 }
2979
2980 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2981 {
2982     ObClientIcon *ret;
2983     static ObClientIcon deficon;
2984
2985     if (!(ret = client_icon_recursive(self, w, h))) {
2986         deficon.width = deficon.height = 48;
2987         deficon.data = ob_rr_theme->def_win_icon;
2988         ret = &deficon;
2989     }
2990     return ret;
2991 }
2992
2993 /* this be mostly ripped from fvwm */
2994 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
2995 {
2996     gint my_cx, my_cy, his_cx, his_cy;
2997     gint offset = 0;
2998     gint distance = 0;
2999     gint score, best_score;
3000     ObClient *best_client, *cur;
3001     GList *it;
3002
3003     if(!client_list)
3004         return NULL;
3005
3006     /* first, find the centre coords of the currently focused window */
3007     my_cx = c->frame->area.x + c->frame->area.width / 2;
3008     my_cy = c->frame->area.y + c->frame->area.height / 2;
3009
3010     best_score = -1;
3011     best_client = NULL;
3012
3013     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
3014         cur = it->data;
3015
3016         /* the currently selected window isn't interesting */
3017         if(cur == c)
3018             continue;
3019         if (!client_normal(cur))
3020             continue;
3021         /* using c->desktop instead of screen_desktop doesn't work if the
3022          * current window was omnipresent, hope this doesn't have any other
3023          * side effects */
3024         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3025             continue;
3026         if(cur->iconic)
3027             continue;
3028         if(!(client_focus_target(cur) == cur &&
3029              client_can_focus(cur)))
3030             continue;
3031
3032         /* find the centre coords of this window, from the
3033          * currently focused window's point of view */
3034         his_cx = (cur->frame->area.x - my_cx)
3035             + cur->frame->area.width / 2;
3036         his_cy = (cur->frame->area.y - my_cy)
3037             + cur->frame->area.height / 2;
3038
3039         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3040            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3041             gint tx;
3042             /* Rotate the diagonals 45 degrees counterclockwise.
3043              * To do this, multiply the matrix /+h +h\ with the
3044              * vector (x y).                   \-h +h/
3045              * h = sqrt(0.5). We can set h := 1 since absolute
3046              * distance doesn't matter here. */
3047             tx = his_cx + his_cy;
3048             his_cy = -his_cx + his_cy;
3049             his_cx = tx;
3050         }
3051
3052         switch(dir) {
3053         case OB_DIRECTION_NORTH:
3054         case OB_DIRECTION_SOUTH:
3055         case OB_DIRECTION_NORTHEAST:
3056         case OB_DIRECTION_SOUTHWEST:
3057             offset = (his_cx < 0) ? -his_cx : his_cx;
3058             distance = ((dir == OB_DIRECTION_NORTH ||
3059                          dir == OB_DIRECTION_NORTHEAST) ?
3060                         -his_cy : his_cy);
3061             break;
3062         case OB_DIRECTION_EAST:
3063         case OB_DIRECTION_WEST:
3064         case OB_DIRECTION_SOUTHEAST:
3065         case OB_DIRECTION_NORTHWEST:
3066             offset = (his_cy < 0) ? -his_cy : his_cy;
3067             distance = ((dir == OB_DIRECTION_WEST ||
3068                          dir == OB_DIRECTION_NORTHWEST) ?
3069                         -his_cx : his_cx);
3070             break;
3071         }
3072
3073         /* the target must be in the requested direction */
3074         if(distance <= 0)
3075             continue;
3076
3077         /* Calculate score for this window.  The smaller the better. */
3078         score = distance + offset;
3079
3080         /* windows more than 45 degrees off the direction are
3081          * heavily penalized and will only be chosen if nothing
3082          * else within a million pixels */
3083         if(offset > distance)
3084             score += 1000000;
3085
3086         if(best_score == -1 || score < best_score)
3087             best_client = cur,
3088                 best_score = score;
3089     }
3090
3091     return best_client;
3092 }
3093
3094 void client_set_layer(ObClient *self, gint layer)
3095 {
3096     if (layer < 0) {
3097         self->below = TRUE;
3098         self->above = FALSE;
3099     } else if (layer == 0) {
3100         self->below = self->above = FALSE;
3101     } else {
3102         self->below = FALSE;
3103         self->above = TRUE;
3104     }
3105     client_calc_layer(self);
3106     client_change_state(self); /* reflect this in the state hints */
3107 }
3108
3109 void client_set_undecorated(ObClient *self, gboolean undecorated)
3110 {
3111     if (self->undecorated != undecorated) {
3112         self->undecorated = undecorated;
3113         client_setup_decor_and_functions(self);
3114         /* Make sure the client knows it might have moved. Maybe there is a
3115          * better way of doing this so only one client_configure is sent, but
3116          * since 125 of these are sent per second when moving the window (with
3117          * user = FALSE) i doubt it matters much.
3118          */
3119         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3120                          self->area.width, self->area.height, TRUE, TRUE);
3121         client_change_state(self); /* reflect this in the state hints */
3122     }
3123 }
3124
3125 /* Determines which physical monitor a client is on by calculating the
3126    area of the part of the client on each monitor.  The number of the
3127    monitor containing the greatest area of the client is returned.*/
3128 guint client_monitor(ObClient *self)
3129 {
3130     guint i;
3131     guint most = 0;
3132     guint mostv = 0;
3133
3134     for (i = 0; i < screen_num_monitors; ++i) {
3135         Rect *area = screen_physical_area_monitor(i);
3136         if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
3137             Rect r;
3138             guint v;
3139
3140             RECT_SET_INTERSECTION(r, *area, self->frame->area);
3141             v = r.width * r.height;
3142
3143             if (v > mostv) {
3144                 mostv = v;
3145                 most = i;
3146             }
3147         }
3148     }
3149     return most;
3150 }
3151
3152 ObClient *client_search_top_transient(ObClient *self)
3153 {
3154     /* move up the transient chain as far as possible */
3155     if (self->transient_for) {
3156         if (self->transient_for != OB_TRAN_GROUP) {
3157             return client_search_top_transient(self->transient_for);
3158         } else {
3159             GSList *it;
3160
3161             g_assert(self->group);
3162
3163             for (it = self->group->members; it; it = g_slist_next(it)) {
3164                 ObClient *c = it->data;
3165
3166                 /* checking transient_for prevents infinate loops! */
3167                 if (c != self && !c->transient_for)
3168                     break;
3169             }
3170             if (it)
3171                 return it->data;
3172         }
3173     }
3174
3175     return self;
3176 }
3177
3178 ObClient *client_search_focus_parent(ObClient *self)
3179 {
3180     if (self->transient_for) {
3181         if (self->transient_for != OB_TRAN_GROUP) {
3182             if (client_focused(self->transient_for))
3183                 return self->transient_for;
3184         } else {
3185             GSList *it;
3186
3187             for (it = self->group->members; it; it = g_slist_next(it)) {
3188                 ObClient *c = it->data;
3189
3190                 /* checking transient_for prevents infinate loops! */
3191                 if (c != self && !c->transient_for)
3192                     if (client_focused(c))
3193                         return c;
3194             }
3195         }
3196     }
3197
3198     return NULL;
3199 }
3200
3201 ObClient *client_search_parent(ObClient *self, ObClient *search)
3202 {
3203     if (self->transient_for) {
3204         if (self->transient_for != OB_TRAN_GROUP) {
3205             if (self->transient_for == search)
3206                 return search;
3207         } else {
3208             GSList *it;
3209
3210             for (it = self->group->members; it; it = g_slist_next(it)) {
3211                 ObClient *c = it->data;
3212
3213                 /* checking transient_for prevents infinate loops! */
3214                 if (c != self && !c->transient_for)
3215                     if (c == search)
3216                         return search;
3217             }
3218         }
3219     }
3220
3221     return NULL;
3222 }
3223
3224 ObClient *client_search_transient(ObClient *self, ObClient *search)
3225 {
3226     GSList *sit;
3227
3228     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3229         if (sit->data == search)
3230             return search;
3231         if (client_search_transient(sit->data, search))
3232             return search;
3233     }
3234     return NULL;
3235 }
3236
3237 void client_update_sm_client_id(ObClient *self)
3238 {
3239     g_free(self->sm_client_id);
3240     self->sm_client_id = NULL;
3241
3242     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3243         self->group)
3244         PROP_GETS(self->group->leader, sm_client_id, locale,
3245                   &self->sm_client_id);
3246 }
3247
3248 #define WANT_EDGE(cur, c) \
3249             if(cur == c)                                                      \
3250                 continue;                                                     \
3251             if(!client_normal(cur))                                   \
3252                 continue;                                                     \
3253             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3254                 continue;                                                     \
3255             if(cur->iconic)                                                   \
3256                 continue;                                                     \
3257             if(cur->layer < c->layer && !config_resist_layers_below)          \
3258                 continue;
3259
3260 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3261             if ((his_edge_start >= my_edge_start && \
3262                  his_edge_start <= my_edge_end) ||  \
3263                 (my_edge_start >= his_edge_start && \
3264                  my_edge_start <= his_edge_end))    \
3265                 dest = his_offset;
3266
3267 /* finds the nearest edge in the given direction from the current client
3268  * note to self: the edge is the -frame- edge (the actual one), not the
3269  * client edge.
3270  */
3271 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3272 {
3273     gint dest, monitor_dest;
3274     gint my_edge_start, my_edge_end, my_offset;
3275     GList *it;
3276     Rect *a, *monitor;
3277     
3278     if(!client_list)
3279         return -1;
3280
3281     a = screen_area(c->desktop);
3282     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3283
3284     switch(dir) {
3285     case OB_DIRECTION_NORTH:
3286         my_edge_start = c->frame->area.x;
3287         my_edge_end = c->frame->area.x + c->frame->area.width;
3288         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3289         
3290         /* default: top of screen */
3291         dest = a->y + (hang ? c->frame->area.height : 0);
3292         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3293         /* if the monitor edge comes before the screen edge, */
3294         /* use that as the destination instead. (For xinerama) */
3295         if (monitor_dest != dest && my_offset > monitor_dest)
3296             dest = monitor_dest; 
3297
3298         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3299             gint his_edge_start, his_edge_end, his_offset;
3300             ObClient *cur = it->data;
3301
3302             WANT_EDGE(cur, c)
3303
3304             his_edge_start = cur->frame->area.x;
3305             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3306             his_offset = cur->frame->area.y + 
3307                          (hang ? 0 : cur->frame->area.height);
3308
3309             if(his_offset + 1 > my_offset)
3310                 continue;
3311
3312             if(his_offset < dest)
3313                 continue;
3314
3315             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3316         }
3317         break;
3318     case OB_DIRECTION_SOUTH:
3319         my_edge_start = c->frame->area.x;
3320         my_edge_end = c->frame->area.x + c->frame->area.width;
3321         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3322
3323         /* default: bottom of screen */
3324         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3325         monitor_dest = monitor->y + monitor->height -
3326                        (hang ? c->frame->area.height : 0);
3327         /* if the monitor edge comes before the screen edge, */
3328         /* use that as the destination instead. (For xinerama) */
3329         if (monitor_dest != dest && my_offset < monitor_dest)
3330             dest = monitor_dest; 
3331
3332         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3333             gint his_edge_start, his_edge_end, his_offset;
3334             ObClient *cur = it->data;
3335
3336             WANT_EDGE(cur, c)
3337
3338             his_edge_start = cur->frame->area.x;
3339             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3340             his_offset = cur->frame->area.y +
3341                          (hang ? cur->frame->area.height : 0);
3342
3343
3344             if(his_offset - 1 < my_offset)
3345                 continue;
3346             
3347             if(his_offset > dest)
3348                 continue;
3349
3350             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3351         }
3352         break;
3353     case OB_DIRECTION_WEST:
3354         my_edge_start = c->frame->area.y;
3355         my_edge_end = c->frame->area.y + c->frame->area.height;
3356         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3357
3358         /* default: leftmost egde of screen */
3359         dest = a->x + (hang ? c->frame->area.width : 0);
3360         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3361         /* if the monitor edge comes before the screen edge, */
3362         /* use that as the destination instead. (For xinerama) */
3363         if (monitor_dest != dest && my_offset > monitor_dest)
3364             dest = monitor_dest;            
3365
3366         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3367             gint his_edge_start, his_edge_end, his_offset;
3368             ObClient *cur = it->data;
3369
3370             WANT_EDGE(cur, c)
3371
3372             his_edge_start = cur->frame->area.y;
3373             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3374             his_offset = cur->frame->area.x +
3375                          (hang ? 0 : cur->frame->area.width);
3376
3377             if(his_offset + 1 > my_offset)
3378                 continue;
3379
3380             if(his_offset < dest)
3381                 continue;
3382
3383             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3384         }
3385        break;
3386     case OB_DIRECTION_EAST:
3387         my_edge_start = c->frame->area.y;
3388         my_edge_end = c->frame->area.y + c->frame->area.height;
3389         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3390         
3391         /* default: rightmost edge of screen */
3392         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3393         monitor_dest = monitor->x + monitor->width -
3394                        (hang ? c->frame->area.width : 0);
3395         /* if the monitor edge comes before the screen edge, */
3396         /* use that as the destination instead. (For xinerama) */
3397         if (monitor_dest != dest && my_offset < monitor_dest)
3398             dest = monitor_dest;            
3399
3400         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3401             gint his_edge_start, his_edge_end, his_offset;
3402             ObClient *cur = it->data;
3403
3404             WANT_EDGE(cur, c)
3405
3406             his_edge_start = cur->frame->area.y;
3407             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3408             his_offset = cur->frame->area.x +
3409                          (hang ? cur->frame->area.width : 0);
3410
3411             if(his_offset - 1 < my_offset)
3412                 continue;
3413             
3414             if(his_offset > dest)
3415                 continue;
3416
3417             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3418         }
3419         break;
3420     case OB_DIRECTION_NORTHEAST:
3421     case OB_DIRECTION_SOUTHEAST:
3422     case OB_DIRECTION_NORTHWEST:
3423     case OB_DIRECTION_SOUTHWEST:
3424         /* not implemented */
3425     default:
3426         g_assert_not_reached();
3427         dest = 0; /* suppress warning */
3428     }
3429     return dest;
3430 }
3431
3432 ObClient* client_under_pointer()
3433 {
3434     gint x, y;
3435     GList *it;
3436     ObClient *ret = NULL;
3437
3438     if (screen_pointer_pos(&x, &y)) {
3439         for (it = stacking_list; it; it = g_list_next(it)) {
3440             if (WINDOW_IS_CLIENT(it->data)) {
3441                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3442                 if (c->frame->visible &&
3443                     RECT_CONTAINS(c->frame->area, x, y)) {
3444                     ret = c;
3445                     break;
3446                 }
3447             }
3448         }
3449     }
3450     return ret;
3451 }
3452
3453 gboolean client_has_group_siblings(ObClient *self)
3454 {
3455     return self->group && self->group->members->next;
3456 }