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