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