]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
http://mail.gnome.org/archives/wm-spec-list/2006-May/msg00000.html
[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_showhide(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_showhide(ObClient *self)
2062 {
2063
2064     if (client_should_show(self)) {
2065         frame_show(self->frame);
2066     }
2067     else {
2068         frame_hide(self->frame);
2069
2070         /* Fall back focus since we're disappearing */
2071         if (focus_client == self)
2072             client_unfocus(self);
2073     }
2074
2075     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2076        needs to be in IconicState. This includes when it is on another
2077        desktop!
2078     */
2079     client_change_wm_state(self);
2080 }
2081
2082 gboolean client_normal(ObClient *self) {
2083     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2084               self->type == OB_CLIENT_TYPE_DOCK ||
2085               self->type == OB_CLIENT_TYPE_SPLASH);
2086 }
2087
2088 static void client_apply_startup_state(ObClient *self, gint x, gint y)
2089 {
2090     gboolean pos = FALSE; /* has the window's position been configured? */
2091     gint ox, oy;
2092
2093     /* save the position, and set self->area for these to use */
2094     ox = self->area.x;
2095     oy = self->area.y;
2096     self->area.x = x;
2097     self->area.y = y;
2098
2099     /* these are in a carefully crafted order.. */
2100
2101     if (self->iconic) {
2102         self->iconic = FALSE;
2103         client_iconify(self, TRUE, FALSE);
2104     }
2105     if (self->fullscreen) {
2106         self->fullscreen = FALSE;
2107         client_fullscreen(self, TRUE);
2108         pos = TRUE;
2109     }
2110     if (self->undecorated) {
2111         self->undecorated = FALSE;
2112         client_set_undecorated(self, TRUE);
2113     }
2114     if (self->shaded) {
2115         self->shaded = FALSE;
2116         client_shade(self, TRUE);
2117     }
2118     if (self->demands_attention) {
2119         self->demands_attention = FALSE;
2120         client_hilite(self, TRUE);
2121     }
2122   
2123     if (self->max_vert && self->max_horz) {
2124         self->max_vert = self->max_horz = FALSE;
2125         client_maximize(self, TRUE, 0);
2126         pos = TRUE;
2127     } else if (self->max_vert) {
2128         self->max_vert = FALSE;
2129         client_maximize(self, TRUE, 2);
2130         pos = TRUE;
2131     } else if (self->max_horz) {
2132         self->max_horz = FALSE;
2133         client_maximize(self, TRUE, 1);
2134         pos = TRUE;
2135     }
2136
2137     /* if the client didn't get positioned yet, then do so now
2138        call client_move even if the window is not being moved anywhere, because
2139        when we reparent it and decorate it, it is getting moved and we need to
2140        be telling it so with a ConfigureNotify event.
2141     */
2142     if (!pos) {
2143         /* use the saved position */
2144         self->area.x = ox;
2145         self->area.y = oy;
2146         client_move(self, x, y);
2147     }
2148
2149     /* nothing to do for the other states:
2150        skip_taskbar
2151        skip_pager
2152        modal
2153        above
2154        below
2155     */
2156 }
2157
2158 void client_try_configure(ObClient *self, ObCorner anchor,
2159                           gint *x, gint *y, gint *w, gint *h,
2160                           gint *logicalw, gint *logicalh,
2161                           gboolean user)
2162 {
2163     Rect desired_area = {*x, *y, *w, *h};
2164
2165     /* make the frame recalculate its dimentions n shit without changing
2166        anything visible for real, this way the constraints below can work with
2167        the updated frame dimensions. */
2168     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2169
2170     /* work within the prefered sizes given by the window */
2171     if (!(*w == self->area.width && *h == self->area.height)) {
2172         gint basew, baseh, minw, minh;
2173
2174         /* base size is substituted with min size if not specified */
2175         if (self->base_size.width || self->base_size.height) {
2176             basew = self->base_size.width;
2177             baseh = self->base_size.height;
2178         } else {
2179             basew = self->min_size.width;
2180             baseh = self->min_size.height;
2181         }
2182         /* min size is substituted with base size if not specified */
2183         if (self->min_size.width || self->min_size.height) {
2184             minw = self->min_size.width;
2185             minh = self->min_size.height;
2186         } else {
2187             minw = self->base_size.width;
2188             minh = self->base_size.height;
2189         }
2190
2191         /* if this is a user-requested resize, then check against min/max
2192            sizes */
2193
2194         /* smaller than min size or bigger than max size? */
2195         if (*w > self->max_size.width) *w = self->max_size.width;
2196         if (*w < minw) *w = minw;
2197         if (*h > self->max_size.height) *h = self->max_size.height;
2198         if (*h < minh) *h = minh;
2199
2200         *w -= basew;
2201         *h -= baseh;
2202
2203         /* keep to the increments */
2204         *w /= self->size_inc.width;
2205         *h /= self->size_inc.height;
2206
2207         /* you cannot resize to nothing */
2208         if (basew + *w < 1) *w = 1 - basew;
2209         if (baseh + *h < 1) *h = 1 - baseh;
2210   
2211         /* save the logical size */
2212         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2213         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2214
2215         *w *= self->size_inc.width;
2216         *h *= self->size_inc.height;
2217
2218         *w += basew;
2219         *h += baseh;
2220
2221         /* adjust the height to match the width for the aspect ratios.
2222            for this, min size is not substituted for base size ever. */
2223         *w -= self->base_size.width;
2224         *h -= self->base_size.height;
2225
2226         if (!self->fullscreen) {
2227             if (self->min_ratio)
2228                 if (*h * self->min_ratio > *w) {
2229                     *h = (gint)(*w / self->min_ratio);
2230
2231                     /* you cannot resize to nothing */
2232                     if (*h < 1) {
2233                         *h = 1;
2234                         *w = (gint)(*h * self->min_ratio);
2235                     }
2236                 }
2237             if (self->max_ratio)
2238                 if (*h * self->max_ratio < *w) {
2239                     *h = (gint)(*w / self->max_ratio);
2240
2241                     /* you cannot resize to nothing */
2242                     if (*h < 1) {
2243                         *h = 1;
2244                         *w = (gint)(*h * self->min_ratio);
2245                     }
2246                 }
2247         }
2248
2249         *w += self->base_size.width;
2250         *h += self->base_size.height;
2251     }
2252
2253     /* gets the frame's position */
2254     frame_client_gravity(self->frame, x, y);
2255
2256     /* these positions are frame positions, not client positions */
2257
2258     /* set the size and position if fullscreen */
2259     if (self->fullscreen) {
2260         Rect *a;
2261         guint i;
2262
2263         i = screen_find_monitor(&desired_area);
2264         a = screen_physical_area_monitor(i);
2265
2266         *x = a->x;
2267         *y = a->y;
2268         *w = a->width;
2269         *h = a->height;
2270
2271         user = FALSE; /* ignore if the client can't be moved/resized when it
2272                          is entering fullscreen */
2273     } else if (self->max_horz || self->max_vert) {
2274         Rect *a;
2275         guint i;
2276
2277         i = screen_find_monitor(&desired_area);
2278         a = screen_area_monitor(self->desktop, i);
2279
2280         /* set the size and position if maximized */
2281         if (self->max_horz) {
2282             *x = a->x;
2283             *w = a->width - self->frame->size.left - self->frame->size.right;
2284         }
2285         if (self->max_vert) {
2286             *y = a->y;
2287             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2288         }
2289
2290         /* maximizing is not allowed if the user can't move+resize the window
2291          */
2292     }
2293
2294     /* gets the client's position */
2295     frame_frame_gravity(self->frame, x, y);
2296
2297     /* these override the above states! if you cant move you can't move! */
2298     if (user) {
2299         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2300             *x = self->area.x;
2301             *y = self->area.y;
2302         }
2303         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2304             *w = self->area.width;
2305             *h = self->area.height;
2306         }
2307     }
2308
2309     g_assert(*w > 0);
2310     g_assert(*h > 0);
2311
2312     switch (anchor) {
2313     case OB_CORNER_TOPLEFT:
2314         break;
2315     case OB_CORNER_TOPRIGHT:
2316         *x -= *w - self->area.width;
2317         break;
2318     case OB_CORNER_BOTTOMLEFT:
2319         *y -= *h - self->area.height;
2320         break;
2321     case OB_CORNER_BOTTOMRIGHT:
2322         *x -= *w - self->area.width;
2323         *y -= *h - self->area.height;
2324         break;
2325     }
2326 }
2327
2328
2329 void client_configure_full(ObClient *self, ObCorner anchor,
2330                            gint x, gint y, gint w, gint h,
2331                            gboolean user, gboolean final,
2332                            gboolean force_reply)
2333 {
2334     gint oldw, oldh;
2335     gboolean send_resize_client;
2336     gboolean moved = FALSE, resized = FALSE;
2337     guint fdecor = self->frame->decorations;
2338     gboolean fhorz = self->frame->max_horz;
2339     gint logicalw, logicalh;
2340
2341     /* find the new x, y, width, and height (and logical size) */
2342     client_try_configure(self, anchor, &x, &y, &w, &h,
2343                          &logicalw, &logicalh, user);
2344
2345     /* set the logical size if things changed */
2346     if (!(w == self->area.width && h == self->area.height))
2347         SIZE_SET(self->logical_size, logicalw, logicalh);
2348
2349     /* figure out if we moved or resized or what */
2350     moved = x != self->area.x || y != self->area.y;
2351     resized = w != self->area.width || h != self->area.height;
2352
2353     oldw = self->area.width;
2354     oldh = self->area.height;
2355     RECT_SET(self->area, x, y, w, h);
2356
2357     /* for app-requested resizes, always resize if 'resized' is true.
2358        for user-requested ones, only resize if final is true, or when
2359        resizing in redraw mode */
2360     send_resize_client = ((!user && resized) ||
2361                           (user && (final ||
2362                                     (resized && config_resize_redraw))));
2363
2364     /* if the client is enlarging, then resize the client before the frame */
2365     if (send_resize_client && user && (w > oldw || h > oldh))
2366         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2367
2368     /* move/resize the frame to match the request */
2369     if (self->frame) {
2370         if (self->decorations != fdecor || self->max_horz != fhorz)
2371             moved = resized = TRUE;
2372
2373         if (moved || resized)
2374             frame_adjust_area(self->frame, moved, resized, FALSE);
2375
2376         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2377         {
2378             XEvent event;
2379             event.type = ConfigureNotify;
2380             event.xconfigure.display = ob_display;
2381             event.xconfigure.event = self->window;
2382             event.xconfigure.window = self->window;
2383
2384             /* root window real coords */
2385             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2386                 self->border_width;
2387             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2388                 self->border_width;
2389             event.xconfigure.width = w;
2390             event.xconfigure.height = h;
2391             event.xconfigure.border_width = 0;
2392             event.xconfigure.above = self->frame->plate;
2393             event.xconfigure.override_redirect = FALSE;
2394             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2395                        FALSE, StructureNotifyMask, &event);
2396         }
2397     }
2398
2399     /* if the client is shrinking, then resize the frame before the client */
2400     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2401         XResizeWindow(ob_display, self->window, w, h);
2402
2403     XFlush(ob_display);
2404 }
2405
2406 void client_fullscreen(ObClient *self, gboolean fs)
2407 {
2408     gint x, y, w, h;
2409
2410     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2411         self->fullscreen == fs) return;                   /* already done */
2412
2413     self->fullscreen = fs;
2414     client_change_state(self); /* change the state hints on the client */
2415     client_calc_layer(self);   /* and adjust out layer/stacking */
2416
2417     if (fs) {
2418         self->pre_fullscreen_area = self->area;
2419         /* if the window is maximized, its area isn't all that meaningful.
2420            save it's premax area instead. */
2421         if (self->max_horz) {
2422             self->pre_fullscreen_area.x = self->pre_max_area.x;
2423             self->pre_fullscreen_area.width = self->pre_max_area.width;
2424         }
2425         if (self->max_vert) {
2426             self->pre_fullscreen_area.y = self->pre_max_area.y;
2427             self->pre_fullscreen_area.height = self->pre_max_area.height;
2428         }
2429
2430         /* these are not actually used cuz client_configure will set them
2431            as appropriate when the window is fullscreened */
2432         x = y = w = h = 0;
2433     } else {
2434         Rect *a;
2435
2436         if (self->pre_fullscreen_area.width > 0 &&
2437             self->pre_fullscreen_area.height > 0)
2438         {
2439             x = self->pre_fullscreen_area.x;
2440             y = self->pre_fullscreen_area.y;
2441             w = self->pre_fullscreen_area.width;
2442             h = self->pre_fullscreen_area.height;
2443             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2444         } else {
2445             /* pick some fallbacks... */
2446             a = screen_area_monitor(self->desktop, 0);
2447             x = a->x + a->width / 4;
2448             y = a->y + a->height / 4;
2449             w = a->width / 2;
2450             h = a->height / 2;
2451         }
2452     }
2453
2454     client_setup_decor_and_functions(self);
2455
2456     client_move_resize(self, x, y, w, h);
2457
2458     /* try focus us when we go into fullscreen mode */
2459     client_focus(self);
2460 }
2461
2462 static void client_iconify_recursive(ObClient *self,
2463                                      gboolean iconic, gboolean curdesk)
2464 {
2465     GSList *it;
2466     gboolean changed = FALSE;
2467
2468
2469     if (self->iconic != iconic) {
2470         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2471                  self->window);
2472
2473         if (iconic) {
2474             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2475                 self->iconic = iconic;
2476
2477                 /* update the focus lists.. iconic windows go to the bottom of
2478                    the list, put the new iconic window at the 'top of the
2479                    bottom'. */
2480                 focus_order_to_top(self);
2481
2482                 changed = TRUE;
2483             }
2484         } else {
2485             self->iconic = iconic;
2486
2487             if (curdesk)
2488                 client_set_desktop(self, screen_desktop, FALSE);
2489
2490             /* this puts it after the current focused window */
2491             focus_order_remove(self);
2492             focus_order_add_new(self);
2493
2494             changed = TRUE;
2495         }
2496     }
2497
2498     if (changed) {
2499         client_change_state(self);
2500         client_showhide(self);
2501         if (STRUT_EXISTS(self->strut))
2502             screen_update_areas();
2503     }
2504
2505     /* iconify all direct transients */
2506     for (it = self->transients; it; it = g_slist_next(it))
2507         if (it->data != self)
2508             if (client_is_direct_child(self, it->data))
2509                 client_iconify_recursive(it->data, iconic, curdesk);
2510 }
2511
2512 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2513 {
2514     /* move up the transient chain as far as possible first */
2515     self = client_search_top_parent(self);
2516     client_iconify_recursive(self, iconic, curdesk);
2517 }
2518
2519 void client_maximize(ObClient *self, gboolean max, gint dir)
2520 {
2521     gint x, y, w, h;
2522      
2523     g_assert(dir == 0 || dir == 1 || dir == 2);
2524     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2525
2526     /* check if already done */
2527     if (max) {
2528         if (dir == 0 && self->max_horz && self->max_vert) return;
2529         if (dir == 1 && self->max_horz) return;
2530         if (dir == 2 && self->max_vert) return;
2531     } else {
2532         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2533         if (dir == 1 && !self->max_horz) return;
2534         if (dir == 2 && !self->max_vert) return;
2535     }
2536
2537     /* we just tell it to configure in the same place and client_configure
2538        worries about filling the screen with the window */
2539     x = self->area.x;
2540     y = self->area.y;
2541     w = self->area.width;
2542     h = self->area.height;
2543
2544     if (max) {
2545         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2546             RECT_SET(self->pre_max_area,
2547                      self->area.x, self->pre_max_area.y,
2548                      self->area.width, self->pre_max_area.height);
2549         }
2550         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2551             RECT_SET(self->pre_max_area,
2552                      self->pre_max_area.x, self->area.y,
2553                      self->pre_max_area.width, self->area.height);
2554         }
2555     } else {
2556         Rect *a;
2557
2558         a = screen_area_monitor(self->desktop, 0);
2559         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2560             if (self->pre_max_area.width > 0) {
2561                 x = self->pre_max_area.x;
2562                 w = self->pre_max_area.width;
2563
2564                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2565                          0, self->pre_max_area.height);
2566             } else {
2567                 /* pick some fallbacks... */
2568                 x = a->x + a->width / 4;
2569                 w = a->width / 2;
2570             }
2571         }
2572         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2573             if (self->pre_max_area.height > 0) {
2574                 y = self->pre_max_area.y;
2575                 h = self->pre_max_area.height;
2576
2577                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2578                          self->pre_max_area.width, 0);
2579             } else {
2580                 /* pick some fallbacks... */
2581                 y = a->y + a->height / 4;
2582                 h = a->height / 2;
2583             }
2584         }
2585     }
2586
2587     if (dir == 0 || dir == 1) /* horz */
2588         self->max_horz = max;
2589     if (dir == 0 || dir == 2) /* vert */
2590         self->max_vert = max;
2591
2592     client_change_state(self); /* change the state hints on the client */
2593
2594     client_setup_decor_and_functions(self);
2595
2596     client_move_resize(self, x, y, w, h);
2597 }
2598
2599 void client_shade(ObClient *self, gboolean shade)
2600 {
2601     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2602          shade) ||                         /* can't shade */
2603         self->shaded == shade) return;     /* already done */
2604
2605     self->shaded = shade;
2606     client_change_state(self);
2607     client_change_wm_state(self); /* the window is being hidden/shown */
2608     /* resize the frame to just the titlebar */
2609     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2610 }
2611
2612 void client_close(ObClient *self)
2613 {
2614     XEvent ce;
2615
2616     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2617
2618     /* in the case that the client provides no means to requesting that it
2619        close, we just kill it */
2620     if (!self->delete_window)
2621         client_kill(self);
2622     
2623     /*
2624       XXX: itd be cool to do timeouts and shit here for killing the client's
2625       process off
2626       like... if the window is around after 5 seconds, then the close button
2627       turns a nice red, and if this function is called again, the client is
2628       explicitly killed.
2629     */
2630
2631     ce.xclient.type = ClientMessage;
2632     ce.xclient.message_type =  prop_atoms.wm_protocols;
2633     ce.xclient.display = ob_display;
2634     ce.xclient.window = self->window;
2635     ce.xclient.format = 32;
2636     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2637     ce.xclient.data.l[1] = event_curtime;
2638     ce.xclient.data.l[2] = 0l;
2639     ce.xclient.data.l[3] = 0l;
2640     ce.xclient.data.l[4] = 0l;
2641     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2642 }
2643
2644 void client_kill(ObClient *self)
2645 {
2646     XKillClient(ob_display, self->window);
2647 }
2648
2649 void client_hilite(ObClient *self, gboolean hilite)
2650 {
2651     if (self->demands_attention == hilite)
2652         return; /* no change */
2653
2654     /* don't allow focused windows to hilite */
2655     self->demands_attention = hilite && !client_focused(self);
2656     if (self->demands_attention)
2657         frame_flash_start(self->frame);
2658     else
2659         frame_flash_stop(self->frame);
2660     client_change_state(self);
2661 }
2662
2663 void client_set_desktop_recursive(ObClient *self,
2664                                   guint target, gboolean donthide)
2665 {
2666     guint old;
2667     GSList *it;
2668
2669     if (target != self->desktop) {
2670
2671         ob_debug("Setting desktop %u\n", target+1);
2672
2673         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2674
2675         /* remove from the old desktop(s) */
2676         focus_order_remove(self);
2677
2678         old = self->desktop;
2679         self->desktop = target;
2680         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2681         /* the frame can display the current desktop state */
2682         frame_adjust_state(self->frame);
2683         /* 'move' the window to the new desktop */
2684         if (!donthide)
2685             client_showhide(self);
2686         /* raise if it was not already on the desktop */
2687         if (old != DESKTOP_ALL)
2688             client_raise(self);
2689         if (STRUT_EXISTS(self->strut))
2690             screen_update_areas();
2691
2692         /* add to the new desktop(s) */
2693         if (config_focus_new)
2694             focus_order_to_top(self);
2695         else
2696             focus_order_to_bottom(self);
2697     }
2698
2699     /* move all transients */
2700     for (it = self->transients; it; it = g_slist_next(it))
2701         if (it->data != self)
2702             if (client_is_direct_child(self, it->data))
2703                 client_set_desktop_recursive(it->data, target, donthide);
2704 }
2705
2706 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2707 {
2708     self = client_search_top_parent(self);
2709     client_set_desktop_recursive(self, target, donthide);
2710 }
2711
2712 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
2713 {
2714     while (child != parent &&
2715            child->transient_for && child->transient_for != OB_TRAN_GROUP)
2716         child = child->transient_for;
2717     return child == parent;
2718 }
2719
2720 ObClient *client_search_modal_child(ObClient *self)
2721 {
2722     GSList *it;
2723     ObClient *ret;
2724   
2725     for (it = self->transients; it; it = g_slist_next(it)) {
2726         ObClient *c = it->data;
2727         if ((ret = client_search_modal_child(c))) return ret;
2728         if (c->modal) return c;
2729     }
2730     return NULL;
2731 }
2732
2733 gboolean client_validate(ObClient *self)
2734 {
2735     XEvent e; 
2736
2737     XSync(ob_display, FALSE); /* get all events on the server */
2738
2739     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2740         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2741         XPutBackEvent(ob_display, &e);
2742         return FALSE;
2743     }
2744
2745     return TRUE;
2746 }
2747
2748 void client_set_wm_state(ObClient *self, glong state)
2749 {
2750     if (state == self->wmstate) return; /* no change */
2751   
2752     switch (state) {
2753     case IconicState:
2754         client_iconify(self, TRUE, TRUE);
2755         break;
2756     case NormalState:
2757         client_iconify(self, FALSE, TRUE);
2758         break;
2759     }
2760 }
2761
2762 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2763 {
2764     gboolean shaded = self->shaded;
2765     gboolean fullscreen = self->fullscreen;
2766     gboolean undecorated = self->undecorated;
2767     gboolean max_horz = self->max_horz;
2768     gboolean max_vert = self->max_vert;
2769     gboolean modal = self->modal;
2770     gboolean iconic = self->iconic;
2771     gboolean demands_attention = self->demands_attention;
2772     gint i;
2773
2774     if (!(action == prop_atoms.net_wm_state_add ||
2775           action == prop_atoms.net_wm_state_remove ||
2776           action == prop_atoms.net_wm_state_toggle))
2777         /* an invalid action was passed to the client message, ignore it */
2778         return; 
2779
2780     for (i = 0; i < 2; ++i) {
2781         Atom state = i == 0 ? data1 : data2;
2782     
2783         if (!state) continue;
2784
2785         /* if toggling, then pick whether we're adding or removing */
2786         if (action == prop_atoms.net_wm_state_toggle) {
2787             if (state == prop_atoms.net_wm_state_modal)
2788                 action = modal ? prop_atoms.net_wm_state_remove :
2789                     prop_atoms.net_wm_state_add;
2790             else if (state == prop_atoms.net_wm_state_maximized_vert)
2791                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2792                     prop_atoms.net_wm_state_add;
2793             else if (state == prop_atoms.net_wm_state_maximized_horz)
2794                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2795                     prop_atoms.net_wm_state_add;
2796             else if (state == prop_atoms.net_wm_state_shaded)
2797                 action = shaded ? prop_atoms.net_wm_state_remove :
2798                     prop_atoms.net_wm_state_add;
2799             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2800                 action = self->skip_taskbar ?
2801                     prop_atoms.net_wm_state_remove :
2802                     prop_atoms.net_wm_state_add;
2803             else if (state == prop_atoms.net_wm_state_skip_pager)
2804                 action = self->skip_pager ?
2805                     prop_atoms.net_wm_state_remove :
2806                     prop_atoms.net_wm_state_add;
2807             else if (state == prop_atoms.net_wm_state_hidden)
2808                 action = self->iconic ?
2809                     prop_atoms.net_wm_state_remove :
2810                     prop_atoms.net_wm_state_add;
2811             else if (state == prop_atoms.net_wm_state_fullscreen)
2812                 action = fullscreen ?
2813                     prop_atoms.net_wm_state_remove :
2814                     prop_atoms.net_wm_state_add;
2815             else if (state == prop_atoms.net_wm_state_above)
2816                 action = self->above ? prop_atoms.net_wm_state_remove :
2817                     prop_atoms.net_wm_state_add;
2818             else if (state == prop_atoms.net_wm_state_below)
2819                 action = self->below ? prop_atoms.net_wm_state_remove :
2820                     prop_atoms.net_wm_state_add;
2821             else if (state == prop_atoms.net_wm_state_demands_attention)
2822                 action = self->demands_attention ?
2823                     prop_atoms.net_wm_state_remove :
2824                     prop_atoms.net_wm_state_add;
2825             else if (state == prop_atoms.ob_wm_state_undecorated)
2826                 action = undecorated ? prop_atoms.net_wm_state_remove :
2827                     prop_atoms.net_wm_state_add;
2828         }
2829     
2830         if (action == prop_atoms.net_wm_state_add) {
2831             if (state == prop_atoms.net_wm_state_modal) {
2832                 modal = TRUE;
2833             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2834                 max_vert = TRUE;
2835             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2836                 max_horz = TRUE;
2837             } else if (state == prop_atoms.net_wm_state_shaded) {
2838                 shaded = TRUE;
2839             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2840                 self->skip_taskbar = TRUE;
2841             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2842                 self->skip_pager = TRUE;
2843             } else if (state == prop_atoms.net_wm_state_hidden) {
2844                 iconic = TRUE;
2845             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2846                 fullscreen = TRUE;
2847             } else if (state == prop_atoms.net_wm_state_above) {
2848                 self->above = TRUE;
2849                 self->below = FALSE;
2850             } else if (state == prop_atoms.net_wm_state_below) {
2851                 self->above = FALSE;
2852                 self->below = TRUE;
2853             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2854                 demands_attention = TRUE;
2855             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2856                 undecorated = TRUE;
2857             }
2858
2859         } else { /* action == prop_atoms.net_wm_state_remove */
2860             if (state == prop_atoms.net_wm_state_modal) {
2861                 modal = FALSE;
2862             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2863                 max_vert = FALSE;
2864             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2865                 max_horz = FALSE;
2866             } else if (state == prop_atoms.net_wm_state_shaded) {
2867                 shaded = FALSE;
2868             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2869                 self->skip_taskbar = FALSE;
2870             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2871                 self->skip_pager = FALSE;
2872             } else if (state == prop_atoms.net_wm_state_hidden) {
2873                 iconic = FALSE;
2874             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2875                 fullscreen = FALSE;
2876             } else if (state == prop_atoms.net_wm_state_above) {
2877                 self->above = FALSE;
2878             } else if (state == prop_atoms.net_wm_state_below) {
2879                 self->below = FALSE;
2880             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2881                 demands_attention = FALSE;
2882             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2883                 undecorated = FALSE;
2884             }
2885         }
2886     }
2887     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2888         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2889             /* toggling both */
2890             if (max_horz == max_vert) { /* both going the same way */
2891                 client_maximize(self, max_horz, 0);
2892             } else {
2893                 client_maximize(self, max_horz, 1);
2894                 client_maximize(self, max_vert, 2);
2895             }
2896         } else {
2897             /* toggling one */
2898             if (max_horz != self->max_horz)
2899                 client_maximize(self, max_horz, 1);
2900             else
2901                 client_maximize(self, max_vert, 2);
2902         }
2903     }
2904     /* change fullscreen state before shading, as it will affect if the window
2905        can shade or not */
2906     if (fullscreen != self->fullscreen)
2907         client_fullscreen(self, fullscreen);
2908     if (shaded != self->shaded)
2909         client_shade(self, shaded);
2910     if (undecorated != self->undecorated)
2911         client_set_undecorated(self, undecorated);
2912     if (modal != self->modal) {
2913         self->modal = modal;
2914         /* when a window changes modality, then its stacking order with its
2915            transients needs to change */
2916         client_raise(self);
2917     }
2918     if (iconic != self->iconic)
2919         client_iconify(self, iconic, FALSE);
2920
2921     if (demands_attention != self->demands_attention)
2922         client_hilite(self, demands_attention);
2923
2924     client_change_state(self); /* change the hint to reflect these changes */
2925 }
2926
2927 ObClient *client_focus_target(ObClient *self)
2928 {
2929     ObClient *child = NULL;
2930
2931     child = client_search_modal_child(self);
2932     if (child) return child;
2933     return self;
2934 }
2935
2936 gboolean client_can_focus(ObClient *self)
2937 {
2938     XEvent ev;
2939
2940     /* choose the correct target */
2941     self = client_focus_target(self);
2942
2943     if (!self->frame->visible)
2944         return FALSE;
2945
2946     if (!(self->can_focus || self->focus_notify))
2947         return FALSE;
2948
2949     /* do a check to see if the window has already been unmapped or destroyed
2950        do this intelligently while watching out for unmaps we've generated
2951        (ignore_unmaps > 0) */
2952     if (XCheckTypedWindowEvent(ob_display, self->window,
2953                                DestroyNotify, &ev)) {
2954         XPutBackEvent(ob_display, &ev);
2955         return FALSE;
2956     }
2957     while (XCheckTypedWindowEvent(ob_display, self->window,
2958                                   UnmapNotify, &ev)) {
2959         if (self->ignore_unmaps) {
2960             self->ignore_unmaps--;
2961         } else {
2962             XPutBackEvent(ob_display, &ev);
2963             return FALSE;
2964         }
2965     }
2966
2967     return TRUE;
2968 }
2969
2970 gboolean client_focus(ObClient *self)
2971 {
2972     /* choose the correct target */
2973     self = client_focus_target(self);
2974
2975     if (!client_can_focus(self)) {
2976         if (!self->frame->visible) {
2977             /* update the focus lists */
2978             focus_order_to_top(self);
2979         }
2980         return FALSE;
2981     }
2982
2983     ob_debug("Focusing client \"%s\" at time %u\n", self->title, event_curtime);
2984
2985     if (self->can_focus) {
2986         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
2987                        event_curtime);
2988     }
2989
2990     if (self->focus_notify) {
2991         XEvent ce;
2992         ce.xclient.type = ClientMessage;
2993         ce.xclient.message_type = prop_atoms.wm_protocols;
2994         ce.xclient.display = ob_display;
2995         ce.xclient.window = self->window;
2996         ce.xclient.format = 32;
2997         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2998         ce.xclient.data.l[1] = event_curtime;
2999         ce.xclient.data.l[2] = 0l;
3000         ce.xclient.data.l[3] = 0l;
3001         ce.xclient.data.l[4] = 0l;
3002         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3003     }
3004
3005 #ifdef DEBUG_FOCUS
3006     ob_debug("%sively focusing %lx at %d\n",
3007              (self->can_focus ? "act" : "pass"),
3008              self->window, (gint) event_curtime);
3009 #endif
3010
3011     /* Cause the FocusIn to come back to us. Important for desktop switches,
3012        since otherwise we'll have no FocusIn on the queue and send it off to
3013        the focus_backup. */
3014     XSync(ob_display, FALSE);
3015     return TRUE;
3016 }
3017
3018 /* Used when the current client is closed or otherwise hidden, focus_last will
3019    then prevent focus from going to the mouse pointer
3020 */
3021 static void client_unfocus(ObClient *self)
3022 {
3023     if (focus_client == self) {
3024 #ifdef DEBUG_FOCUS
3025         ob_debug("client_unfocus for %lx\n", self->window);
3026 #endif
3027         focus_fallback(FALSE);
3028     }
3029 }
3030
3031 void client_activate(ObClient *self, gboolean here, gboolean user)
3032 {
3033     /* XXX do some stuff here if user is false to determine if we really want
3034        to activate it or not (a parent or group member is currently
3035        active)?
3036     */
3037     ob_debug("Want to activate window 0x%x with time %u (last time %u), "
3038              "source=%s\n",
3039              self->window, event_curtime, client_last_user_time,
3040              (user ? "user" : "application"));
3041     if (!user && event_curtime &&
3042         !event_time_after(event_curtime, client_last_user_time))
3043     {
3044         client_hilite(self, TRUE);
3045     } else {
3046         if (client_normal(self) && screen_showing_desktop)
3047             screen_show_desktop(FALSE);
3048         if (self->iconic)
3049             client_iconify(self, FALSE, here);
3050         if (self->desktop != DESKTOP_ALL &&
3051             self->desktop != screen_desktop) {
3052             if (here)
3053                 client_set_desktop(self, screen_desktop, FALSE);
3054             else
3055                 screen_set_desktop(self->desktop);
3056         } else if (!self->frame->visible)
3057             /* if its not visible for other reasons, then don't mess
3058                with it */
3059             return;
3060         if (self->shaded)
3061             client_shade(self, FALSE);
3062
3063         client_focus(self);
3064
3065         /* we do this an action here. this is rather important. this is because
3066            we want the results from the focus change to take place BEFORE we go
3067            about raising the window. when a fullscreen window loses focus, we
3068            need this or else the raise wont be able to raise above the
3069            to-lose-focus fullscreen window. */
3070         client_raise(self);
3071     }
3072 }
3073
3074 void client_raise(ObClient *self)
3075 {
3076     action_run_string("Raise", self, CurrentTime);
3077 }
3078
3079 void client_lower(ObClient *self)
3080 {
3081     action_run_string("Lower", self, CurrentTime);
3082 }
3083
3084 gboolean client_focused(ObClient *self)
3085 {
3086     return self == focus_client;
3087 }
3088
3089 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3090 {
3091     guint i;
3092     /* si is the smallest image >= req */
3093     /* li is the largest image < req */
3094     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3095
3096     if (!self->nicons) {
3097         ObClientIcon *parent = NULL;
3098
3099         if (self->transient_for) {
3100             if (self->transient_for != OB_TRAN_GROUP)
3101                 parent = client_icon_recursive(self->transient_for, w, h);
3102             else {
3103                 GSList *it;
3104                 for (it = self->group->members; it; it = g_slist_next(it)) {
3105                     ObClient *c = it->data;
3106                     if (c != self && !c->transient_for) {
3107                         if ((parent = client_icon_recursive(c, w, h)))
3108                             break;
3109                     }
3110                 }
3111             }
3112         }
3113         
3114         return parent;
3115     }
3116
3117     for (i = 0; i < self->nicons; ++i) {
3118         size = self->icons[i].width * self->icons[i].height;
3119         if (size < smallest && size >= (unsigned)(w * h)) {
3120             smallest = size;
3121             si = i;
3122         }
3123         if (size > largest && size <= (unsigned)(w * h)) {
3124             largest = size;
3125             li = i;
3126         }
3127     }
3128     if (largest == 0) /* didnt find one smaller than the requested size */
3129         return &self->icons[si];
3130     return &self->icons[li];
3131 }
3132
3133 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3134 {
3135     ObClientIcon *ret;
3136     static ObClientIcon deficon;
3137
3138     if (!(ret = client_icon_recursive(self, w, h))) {
3139         deficon.width = deficon.height = 48;
3140         deficon.data = ob_rr_theme->def_win_icon;
3141         ret = &deficon;
3142     }
3143     return ret;
3144 }
3145
3146 /* this be mostly ripped from fvwm */
3147 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
3148 {
3149     gint my_cx, my_cy, his_cx, his_cy;
3150     gint offset = 0;
3151     gint distance = 0;
3152     gint score, best_score;
3153     ObClient *best_client, *cur;
3154     GList *it;
3155
3156     if(!client_list)
3157         return NULL;
3158
3159     /* first, find the centre coords of the currently focused window */
3160     my_cx = c->frame->area.x + c->frame->area.width / 2;
3161     my_cy = c->frame->area.y + c->frame->area.height / 2;
3162
3163     best_score = -1;
3164     best_client = NULL;
3165
3166     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
3167         cur = it->data;
3168
3169         /* the currently selected window isn't interesting */
3170         if(cur == c)
3171             continue;
3172         if (!client_normal(cur))
3173             continue;
3174         /* using c->desktop instead of screen_desktop doesn't work if the
3175          * current window was omnipresent, hope this doesn't have any other
3176          * side effects */
3177         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3178             continue;
3179         if(cur->iconic)
3180             continue;
3181         if(!(client_focus_target(cur) == cur &&
3182              client_can_focus(cur)))
3183             continue;
3184
3185         /* find the centre coords of this window, from the
3186          * currently focused window's point of view */
3187         his_cx = (cur->frame->area.x - my_cx)
3188             + cur->frame->area.width / 2;
3189         his_cy = (cur->frame->area.y - my_cy)
3190             + cur->frame->area.height / 2;
3191
3192         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3193            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3194             gint tx;
3195             /* Rotate the diagonals 45 degrees counterclockwise.
3196              * To do this, multiply the matrix /+h +h\ with the
3197              * vector (x y).                   \-h +h/
3198              * h = sqrt(0.5). We can set h := 1 since absolute
3199              * distance doesn't matter here. */
3200             tx = his_cx + his_cy;
3201             his_cy = -his_cx + his_cy;
3202             his_cx = tx;
3203         }
3204
3205         switch(dir) {
3206         case OB_DIRECTION_NORTH:
3207         case OB_DIRECTION_SOUTH:
3208         case OB_DIRECTION_NORTHEAST:
3209         case OB_DIRECTION_SOUTHWEST:
3210             offset = (his_cx < 0) ? -his_cx : his_cx;
3211             distance = ((dir == OB_DIRECTION_NORTH ||
3212                          dir == OB_DIRECTION_NORTHEAST) ?
3213                         -his_cy : his_cy);
3214             break;
3215         case OB_DIRECTION_EAST:
3216         case OB_DIRECTION_WEST:
3217         case OB_DIRECTION_SOUTHEAST:
3218         case OB_DIRECTION_NORTHWEST:
3219             offset = (his_cy < 0) ? -his_cy : his_cy;
3220             distance = ((dir == OB_DIRECTION_WEST ||
3221                          dir == OB_DIRECTION_NORTHWEST) ?
3222                         -his_cx : his_cx);
3223             break;
3224         }
3225
3226         /* the target must be in the requested direction */
3227         if(distance <= 0)
3228             continue;
3229
3230         /* Calculate score for this window.  The smaller the better. */
3231         score = distance + offset;
3232
3233         /* windows more than 45 degrees off the direction are
3234          * heavily penalized and will only be chosen if nothing
3235          * else within a million pixels */
3236         if(offset > distance)
3237             score += 1000000;
3238
3239         if(best_score == -1 || score < best_score)
3240             best_client = cur,
3241                 best_score = score;
3242     }
3243
3244     return best_client;
3245 }
3246
3247 void client_set_layer(ObClient *self, gint layer)
3248 {
3249     if (layer < 0) {
3250         self->below = TRUE;
3251         self->above = FALSE;
3252     } else if (layer == 0) {
3253         self->below = self->above = FALSE;
3254     } else {
3255         self->below = FALSE;
3256         self->above = TRUE;
3257     }
3258     client_calc_layer(self);
3259     client_change_state(self); /* reflect this in the state hints */
3260 }
3261
3262 void client_set_undecorated(ObClient *self, gboolean undecorated)
3263 {
3264     if (self->undecorated != undecorated) {
3265         self->undecorated = undecorated;
3266         client_setup_decor_and_functions(self);
3267         /* Make sure the client knows it might have moved. Maybe there is a
3268          * better way of doing this so only one client_configure is sent, but
3269          * since 125 of these are sent per second when moving the window (with
3270          * user = FALSE) i doubt it matters much.
3271          */
3272         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3273                          self->area.width, self->area.height, TRUE, TRUE);
3274         client_change_state(self); /* reflect this in the state hints */
3275     }
3276 }
3277
3278 guint client_monitor(ObClient *self)
3279 {
3280     return screen_find_monitor(&self->frame->area);
3281 }
3282
3283 ObClient *client_search_top_parent(ObClient *self)
3284 {
3285     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3286         self = self->transient_for;
3287     return self;
3288 }
3289
3290 GSList *client_search_all_top_parents(ObClient *self)
3291 {
3292     GSList *ret = NULL;
3293
3294     /* move up the direct transient chain as far as possible */
3295     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3296         self = self->transient_for;
3297
3298     if (!self->transient_for)
3299         ret = g_slist_prepend(ret, self);
3300     else {
3301             GSList *it;
3302
3303             g_assert(self->group);
3304
3305             for (it = self->group->members; it; it = g_slist_next(it)) {
3306                 ObClient *c = it->data;
3307
3308                 if (!c->transient_for)
3309                     ret = g_slist_prepend(ret, c);
3310             }
3311
3312             if (ret == NULL) /* no group parents */
3313                 ret = g_slist_prepend(ret, self);
3314     }
3315
3316     return ret;
3317 }
3318
3319 ObClient *client_search_focus_parent(ObClient *self)
3320 {
3321     if (self->transient_for) {
3322         if (self->transient_for != OB_TRAN_GROUP) {
3323             if (client_focused(self->transient_for))
3324                 return self->transient_for;
3325         } else {
3326             GSList *it;
3327
3328             for (it = self->group->members; it; it = g_slist_next(it)) {
3329                 ObClient *c = it->data;
3330
3331                 /* checking transient_for prevents infinate loops! */
3332                 if (c != self && !c->transient_for)
3333                     if (client_focused(c))
3334                         return c;
3335             }
3336         }
3337     }
3338
3339     return NULL;
3340 }
3341
3342 ObClient *client_search_parent(ObClient *self, ObClient *search)
3343 {
3344     if (self->transient_for) {
3345         if (self->transient_for != OB_TRAN_GROUP) {
3346             if (self->transient_for == search)
3347                 return search;
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 (c == search)
3357                         return search;
3358             }
3359         }
3360     }
3361
3362     return NULL;
3363 }
3364
3365 ObClient *client_search_transient(ObClient *self, ObClient *search)
3366 {
3367     GSList *sit;
3368
3369     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3370         if (sit->data == search)
3371             return search;
3372         if (client_search_transient(sit->data, search))
3373             return search;
3374     }
3375     return NULL;
3376 }
3377
3378 void client_update_sm_client_id(ObClient *self)
3379 {
3380     g_free(self->sm_client_id);
3381     self->sm_client_id = NULL;
3382
3383     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3384         self->group)
3385         PROP_GETS(self->group->leader, sm_client_id, locale,
3386                   &self->sm_client_id);
3387 }
3388
3389 #define WANT_EDGE(cur, c) \
3390             if(cur == c)                                                      \
3391                 continue;                                                     \
3392             if(!client_normal(cur))                                   \
3393                 continue;                                                     \
3394             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3395                 continue;                                                     \
3396             if(cur->iconic)                                                   \
3397                 continue;                                                     \
3398             if(cur->layer < c->layer && !config_resist_layers_below)          \
3399                 continue;
3400
3401 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3402             if ((his_edge_start >= my_edge_start && \
3403                  his_edge_start <= my_edge_end) ||  \
3404                 (my_edge_start >= his_edge_start && \
3405                  my_edge_start <= his_edge_end))    \
3406                 dest = his_offset;
3407
3408 /* finds the nearest edge in the given direction from the current client
3409  * note to self: the edge is the -frame- edge (the actual one), not the
3410  * client edge.
3411  */
3412 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3413 {
3414     gint dest, monitor_dest;
3415     gint my_edge_start, my_edge_end, my_offset;
3416     GList *it;
3417     Rect *a, *monitor;
3418     
3419     if(!client_list)
3420         return -1;
3421
3422     a = screen_area(c->desktop);
3423     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3424
3425     switch(dir) {
3426     case OB_DIRECTION_NORTH:
3427         my_edge_start = c->frame->area.x;
3428         my_edge_end = c->frame->area.x + c->frame->area.width;
3429         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3430         
3431         /* default: top of screen */
3432         dest = a->y + (hang ? c->frame->area.height : 0);
3433         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3434         /* if the monitor edge comes before the screen edge, */
3435         /* use that as the destination instead. (For xinerama) */
3436         if (monitor_dest != dest && my_offset > monitor_dest)
3437             dest = monitor_dest; 
3438
3439         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3440             gint his_edge_start, his_edge_end, his_offset;
3441             ObClient *cur = it->data;
3442
3443             WANT_EDGE(cur, c)
3444
3445             his_edge_start = cur->frame->area.x;
3446             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3447             his_offset = cur->frame->area.y + 
3448                          (hang ? 0 : cur->frame->area.height);
3449
3450             if(his_offset + 1 > my_offset)
3451                 continue;
3452
3453             if(his_offset < dest)
3454                 continue;
3455
3456             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3457         }
3458         break;
3459     case OB_DIRECTION_SOUTH:
3460         my_edge_start = c->frame->area.x;
3461         my_edge_end = c->frame->area.x + c->frame->area.width;
3462         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3463
3464         /* default: bottom of screen */
3465         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3466         monitor_dest = monitor->y + monitor->height -
3467                        (hang ? c->frame->area.height : 0);
3468         /* if the monitor edge comes before the screen edge, */
3469         /* use that as the destination instead. (For xinerama) */
3470         if (monitor_dest != dest && my_offset < monitor_dest)
3471             dest = monitor_dest; 
3472
3473         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3474             gint his_edge_start, his_edge_end, his_offset;
3475             ObClient *cur = it->data;
3476
3477             WANT_EDGE(cur, c)
3478
3479             his_edge_start = cur->frame->area.x;
3480             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3481             his_offset = cur->frame->area.y +
3482                          (hang ? cur->frame->area.height : 0);
3483
3484
3485             if(his_offset - 1 < my_offset)
3486                 continue;
3487             
3488             if(his_offset > dest)
3489                 continue;
3490
3491             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3492         }
3493         break;
3494     case OB_DIRECTION_WEST:
3495         my_edge_start = c->frame->area.y;
3496         my_edge_end = c->frame->area.y + c->frame->area.height;
3497         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3498
3499         /* default: leftmost egde of screen */
3500         dest = a->x + (hang ? c->frame->area.width : 0);
3501         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3502         /* if the monitor edge comes before the screen edge, */
3503         /* use that as the destination instead. (For xinerama) */
3504         if (monitor_dest != dest && my_offset > monitor_dest)
3505             dest = monitor_dest;            
3506
3507         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3508             gint his_edge_start, his_edge_end, his_offset;
3509             ObClient *cur = it->data;
3510
3511             WANT_EDGE(cur, c)
3512
3513             his_edge_start = cur->frame->area.y;
3514             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3515             his_offset = cur->frame->area.x +
3516                          (hang ? 0 : cur->frame->area.width);
3517
3518             if(his_offset + 1 > my_offset)
3519                 continue;
3520
3521             if(his_offset < dest)
3522                 continue;
3523
3524             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3525         }
3526        break;
3527     case OB_DIRECTION_EAST:
3528         my_edge_start = c->frame->area.y;
3529         my_edge_end = c->frame->area.y + c->frame->area.height;
3530         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3531         
3532         /* default: rightmost edge of screen */
3533         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3534         monitor_dest = monitor->x + monitor->width -
3535                        (hang ? c->frame->area.width : 0);
3536         /* if the monitor edge comes before the screen edge, */
3537         /* use that as the destination instead. (For xinerama) */
3538         if (monitor_dest != dest && my_offset < monitor_dest)
3539             dest = monitor_dest;            
3540
3541         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3542             gint his_edge_start, his_edge_end, his_offset;
3543             ObClient *cur = it->data;
3544
3545             WANT_EDGE(cur, c)
3546
3547             his_edge_start = cur->frame->area.y;
3548             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3549             his_offset = cur->frame->area.x +
3550                          (hang ? cur->frame->area.width : 0);
3551
3552             if(his_offset - 1 < my_offset)
3553                 continue;
3554             
3555             if(his_offset > dest)
3556                 continue;
3557
3558             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3559         }
3560         break;
3561     case OB_DIRECTION_NORTHEAST:
3562     case OB_DIRECTION_SOUTHEAST:
3563     case OB_DIRECTION_NORTHWEST:
3564     case OB_DIRECTION_SOUTHWEST:
3565         /* not implemented */
3566     default:
3567         g_assert_not_reached();
3568         dest = 0; /* suppress warning */
3569     }
3570     return dest;
3571 }
3572
3573 ObClient* client_under_pointer()
3574 {
3575     gint x, y;
3576     GList *it;
3577     ObClient *ret = NULL;
3578
3579     if (screen_pointer_pos(&x, &y)) {
3580         for (it = stacking_list; it; it = g_list_next(it)) {
3581             if (WINDOW_IS_CLIENT(it->data)) {
3582                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3583                 if (c->frame->visible &&
3584                     RECT_CONTAINS(c->frame->area, x, y)) {
3585                     ret = c;
3586                     break;
3587                 }
3588             }
3589         }
3590     }
3591     return ret;
3592 }
3593
3594 gboolean client_has_group_siblings(ObClient *self)
3595 {
3596     return self->group && self->group->members->next;
3597 }