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