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