]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
don't accept non-letter keys for shortcuts, they can't be shortcuts anyways and it...
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-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         /* resize the plate to show the client padding color underneath */
2443         frame_adjust_client_area(self->frame);
2444     }
2445
2446     /* find the frame's dimensions and move/resize it */
2447     if (self->decorations != fdecor || self->max_horz != fhorz)
2448         moved = resized = TRUE;
2449     if (moved || resized)
2450         frame_adjust_area(self->frame, moved, resized, FALSE);
2451
2452     /* find the client's position relative to the root window */
2453     oldrx = self->root_pos.x;
2454     oldry = self->root_pos.y;
2455     rootmoved = (oldrx != (signed)(self->frame->area.x +
2456                                    self->frame->size.left -
2457                                    self->border_width) ||
2458                  oldry != (signed)(self->frame->area.y +
2459                                    self->frame->size.top -
2460                                    self->border_width));
2461
2462     if (force_reply || ((!user || (user && final)) && rootmoved))
2463     {
2464         XEvent event;
2465
2466         POINT_SET(self->root_pos,
2467                   self->frame->area.x + self->frame->size.left -
2468                   self->border_width,
2469                   self->frame->area.y + self->frame->size.top -
2470                   self->border_width);
2471
2472         event.type = ConfigureNotify;
2473         event.xconfigure.display = ob_display;
2474         event.xconfigure.event = self->window;
2475         event.xconfigure.window = self->window;
2476
2477         /* root window real coords */
2478         event.xconfigure.x = self->root_pos.x;
2479         event.xconfigure.y = self->root_pos.y;
2480         event.xconfigure.width = w;
2481         event.xconfigure.height = h;
2482         event.xconfigure.border_width = 0;
2483         event.xconfigure.above = self->frame->plate;
2484         event.xconfigure.override_redirect = FALSE;
2485         XSendEvent(event.xconfigure.display, event.xconfigure.window,
2486                    FALSE, StructureNotifyMask, &event);
2487     }
2488
2489     /* if the client is shrinking, then resize the frame before the client */
2490     if (send_resize_client && (!user || (w <= oldw || h <= oldh))) {
2491         /* resize the plate to show the client padding color underneath */
2492         frame_adjust_client_area(self->frame);
2493
2494         XResizeWindow(ob_display, self->window, w, h);
2495     }
2496
2497     XFlush(ob_display);
2498 }
2499
2500 void client_fullscreen(ObClient *self, gboolean fs)
2501 {
2502     gint x, y, w, h;
2503
2504     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2505         self->fullscreen == fs) return;                   /* already done */
2506
2507     self->fullscreen = fs;
2508     client_change_state(self); /* change the state hints on the client */
2509     client_calc_layer(self);   /* and adjust out layer/stacking */
2510
2511     if (fs) {
2512         self->pre_fullscreen_area = self->area;
2513         /* if the window is maximized, its area isn't all that meaningful.
2514            save it's premax area instead. */
2515         if (self->max_horz) {
2516             self->pre_fullscreen_area.x = self->pre_max_area.x;
2517             self->pre_fullscreen_area.width = self->pre_max_area.width;
2518         }
2519         if (self->max_vert) {
2520             self->pre_fullscreen_area.y = self->pre_max_area.y;
2521             self->pre_fullscreen_area.height = self->pre_max_area.height;
2522         }
2523
2524         /* these are not actually used cuz client_configure will set them
2525            as appropriate when the window is fullscreened */
2526         x = y = w = h = 0;
2527     } else {
2528         Rect *a;
2529
2530         if (self->pre_fullscreen_area.width > 0 &&
2531             self->pre_fullscreen_area.height > 0)
2532         {
2533             x = self->pre_fullscreen_area.x;
2534             y = self->pre_fullscreen_area.y;
2535             w = self->pre_fullscreen_area.width;
2536             h = self->pre_fullscreen_area.height;
2537             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2538         } else {
2539             /* pick some fallbacks... */
2540             a = screen_area_monitor(self->desktop, 0);
2541             x = a->x + a->width / 4;
2542             y = a->y + a->height / 4;
2543             w = a->width / 2;
2544             h = a->height / 2;
2545         }
2546     }
2547
2548     client_setup_decor_and_functions(self);
2549
2550     client_move_resize(self, x, y, w, h);
2551
2552     /* try focus us when we go into fullscreen mode */
2553     client_focus(self);
2554 }
2555
2556 static void client_iconify_recursive(ObClient *self,
2557                                      gboolean iconic, gboolean curdesk)
2558 {
2559     GSList *it;
2560     gboolean changed = FALSE;
2561
2562
2563     if (self->iconic != iconic) {
2564         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2565                  self->window);
2566
2567         if (iconic) {
2568             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2569                 self->iconic = iconic;
2570
2571                 /* update the focus lists.. iconic windows go to the bottom of
2572                    the list, put the new iconic window at the 'top of the
2573                    bottom'. */
2574                 focus_order_to_top(self);
2575
2576                 changed = TRUE;
2577             }
2578         } else {
2579             self->iconic = iconic;
2580
2581             if (curdesk)
2582                 client_set_desktop(self, screen_desktop, FALSE);
2583
2584             /* this puts it after the current focused window */
2585             focus_order_remove(self);
2586             focus_order_add_new(self);
2587
2588             changed = TRUE;
2589         }
2590     }
2591
2592     if (changed) {
2593         client_change_state(self);
2594         client_showhide(self);
2595         if (STRUT_EXISTS(self->strut))
2596             screen_update_areas();
2597     }
2598
2599     /* iconify all direct transients */
2600     for (it = self->transients; it; it = g_slist_next(it))
2601         if (it->data != self)
2602             if (client_is_direct_child(self, it->data))
2603                 client_iconify_recursive(it->data, iconic, curdesk);
2604 }
2605
2606 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2607 {
2608     /* move up the transient chain as far as possible first */
2609     self = client_search_top_parent(self);
2610     client_iconify_recursive(self, iconic, curdesk);
2611 }
2612
2613 void client_maximize(ObClient *self, gboolean max, gint dir)
2614 {
2615     gint x, y, w, h;
2616      
2617     g_assert(dir == 0 || dir == 1 || dir == 2);
2618     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2619
2620     /* check if already done */
2621     if (max) {
2622         if (dir == 0 && self->max_horz && self->max_vert) return;
2623         if (dir == 1 && self->max_horz) return;
2624         if (dir == 2 && self->max_vert) return;
2625     } else {
2626         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2627         if (dir == 1 && !self->max_horz) return;
2628         if (dir == 2 && !self->max_vert) return;
2629     }
2630
2631     /* we just tell it to configure in the same place and client_configure
2632        worries about filling the screen with the window */
2633     x = self->area.x;
2634     y = self->area.y;
2635     w = self->area.width;
2636     h = self->area.height;
2637
2638     if (max) {
2639         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2640             RECT_SET(self->pre_max_area,
2641                      self->area.x, self->pre_max_area.y,
2642                      self->area.width, self->pre_max_area.height);
2643         }
2644         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2645             RECT_SET(self->pre_max_area,
2646                      self->pre_max_area.x, self->area.y,
2647                      self->pre_max_area.width, self->area.height);
2648         }
2649     } else {
2650         Rect *a;
2651
2652         a = screen_area_monitor(self->desktop, 0);
2653         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2654             if (self->pre_max_area.width > 0) {
2655                 x = self->pre_max_area.x;
2656                 w = self->pre_max_area.width;
2657
2658                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2659                          0, self->pre_max_area.height);
2660             } else {
2661                 /* pick some fallbacks... */
2662                 x = a->x + a->width / 4;
2663                 w = a->width / 2;
2664             }
2665         }
2666         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2667             if (self->pre_max_area.height > 0) {
2668                 y = self->pre_max_area.y;
2669                 h = self->pre_max_area.height;
2670
2671                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2672                          self->pre_max_area.width, 0);
2673             } else {
2674                 /* pick some fallbacks... */
2675                 y = a->y + a->height / 4;
2676                 h = a->height / 2;
2677             }
2678         }
2679     }
2680
2681     if (dir == 0 || dir == 1) /* horz */
2682         self->max_horz = max;
2683     if (dir == 0 || dir == 2) /* vert */
2684         self->max_vert = max;
2685
2686     client_change_state(self); /* change the state hints on the client */
2687
2688     client_setup_decor_and_functions(self);
2689
2690     client_move_resize(self, x, y, w, h);
2691 }
2692
2693 void client_shade(ObClient *self, gboolean shade)
2694 {
2695     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2696          shade) ||                         /* can't shade */
2697         self->shaded == shade) return;     /* already done */
2698
2699     self->shaded = shade;
2700     client_change_state(self);
2701     client_change_wm_state(self); /* the window is being hidden/shown */
2702     /* resize the frame to just the titlebar */
2703     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2704 }
2705
2706 void client_close(ObClient *self)
2707 {
2708     XEvent ce;
2709
2710     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2711
2712     /* in the case that the client provides no means to requesting that it
2713        close, we just kill it */
2714     if (!self->delete_window)
2715         client_kill(self);
2716     
2717     /*
2718       XXX: itd be cool to do timeouts and shit here for killing the client's
2719       process off
2720       like... if the window is around after 5 seconds, then the close button
2721       turns a nice red, and if this function is called again, the client is
2722       explicitly killed.
2723     */
2724
2725     ce.xclient.type = ClientMessage;
2726     ce.xclient.message_type =  prop_atoms.wm_protocols;
2727     ce.xclient.display = ob_display;
2728     ce.xclient.window = self->window;
2729     ce.xclient.format = 32;
2730     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2731     ce.xclient.data.l[1] = event_curtime;
2732     ce.xclient.data.l[2] = 0l;
2733     ce.xclient.data.l[3] = 0l;
2734     ce.xclient.data.l[4] = 0l;
2735     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2736 }
2737
2738 void client_kill(ObClient *self)
2739 {
2740     XKillClient(ob_display, self->window);
2741 }
2742
2743 void client_hilite(ObClient *self, gboolean hilite)
2744 {
2745     if (self->demands_attention == hilite)
2746         return; /* no change */
2747
2748     /* don't allow focused windows to hilite */
2749     self->demands_attention = hilite && !client_focused(self);
2750     if (self->demands_attention)
2751         frame_flash_start(self->frame);
2752     else
2753         frame_flash_stop(self->frame);
2754     client_change_state(self);
2755 }
2756
2757 void client_set_desktop_recursive(ObClient *self,
2758                                   guint target, gboolean donthide)
2759 {
2760     guint old;
2761     GSList *it;
2762
2763     if (target != self->desktop) {
2764
2765         ob_debug("Setting desktop %u\n", target+1);
2766
2767         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2768
2769         /* remove from the old desktop(s) */
2770         focus_order_remove(self);
2771
2772         old = self->desktop;
2773         self->desktop = target;
2774         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2775         /* the frame can display the current desktop state */
2776         frame_adjust_state(self->frame);
2777         /* 'move' the window to the new desktop */
2778         if (!donthide)
2779             client_showhide(self);
2780         /* raise if it was not already on the desktop */
2781         if (old != DESKTOP_ALL)
2782             client_raise(self);
2783         if (STRUT_EXISTS(self->strut))
2784             screen_update_areas();
2785
2786         /* add to the new desktop(s) */
2787         if (config_focus_new)
2788             focus_order_to_top(self);
2789         else
2790             focus_order_to_bottom(self);
2791     }
2792
2793     /* move all transients */
2794     for (it = self->transients; it; it = g_slist_next(it))
2795         if (it->data != self)
2796             if (client_is_direct_child(self, it->data))
2797                 client_set_desktop_recursive(it->data, target, donthide);
2798 }
2799
2800 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2801 {
2802     self = client_search_top_parent(self);
2803     client_set_desktop_recursive(self, target, donthide);
2804 }
2805
2806 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
2807 {
2808     while (child != parent &&
2809            child->transient_for && child->transient_for != OB_TRAN_GROUP)
2810         child = child->transient_for;
2811     return child == parent;
2812 }
2813
2814 ObClient *client_search_modal_child(ObClient *self)
2815 {
2816     GSList *it;
2817     ObClient *ret;
2818   
2819     for (it = self->transients; it; it = g_slist_next(it)) {
2820         ObClient *c = it->data;
2821         if ((ret = client_search_modal_child(c))) return ret;
2822         if (c->modal) return c;
2823     }
2824     return NULL;
2825 }
2826
2827 gboolean client_validate(ObClient *self)
2828 {
2829     XEvent e; 
2830
2831     XSync(ob_display, FALSE); /* get all events on the server */
2832
2833     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2834         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2835         XPutBackEvent(ob_display, &e);
2836         return FALSE;
2837     }
2838
2839     return TRUE;
2840 }
2841
2842 void client_set_wm_state(ObClient *self, glong state)
2843 {
2844     if (state == self->wmstate) return; /* no change */
2845   
2846     switch (state) {
2847     case IconicState:
2848         client_iconify(self, TRUE, TRUE);
2849         break;
2850     case NormalState:
2851         client_iconify(self, FALSE, TRUE);
2852         break;
2853     }
2854 }
2855
2856 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2857 {
2858     gboolean shaded = self->shaded;
2859     gboolean fullscreen = self->fullscreen;
2860     gboolean undecorated = self->undecorated;
2861     gboolean max_horz = self->max_horz;
2862     gboolean max_vert = self->max_vert;
2863     gboolean modal = self->modal;
2864     gboolean iconic = self->iconic;
2865     gboolean demands_attention = self->demands_attention;
2866     gint i;
2867
2868     if (!(action == prop_atoms.net_wm_state_add ||
2869           action == prop_atoms.net_wm_state_remove ||
2870           action == prop_atoms.net_wm_state_toggle))
2871         /* an invalid action was passed to the client message, ignore it */
2872         return; 
2873
2874     for (i = 0; i < 2; ++i) {
2875         Atom state = i == 0 ? data1 : data2;
2876     
2877         if (!state) continue;
2878
2879         /* if toggling, then pick whether we're adding or removing */
2880         if (action == prop_atoms.net_wm_state_toggle) {
2881             if (state == prop_atoms.net_wm_state_modal)
2882                 action = modal ? prop_atoms.net_wm_state_remove :
2883                     prop_atoms.net_wm_state_add;
2884             else if (state == prop_atoms.net_wm_state_maximized_vert)
2885                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2886                     prop_atoms.net_wm_state_add;
2887             else if (state == prop_atoms.net_wm_state_maximized_horz)
2888                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2889                     prop_atoms.net_wm_state_add;
2890             else if (state == prop_atoms.net_wm_state_shaded)
2891                 action = shaded ? prop_atoms.net_wm_state_remove :
2892                     prop_atoms.net_wm_state_add;
2893             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2894                 action = self->skip_taskbar ?
2895                     prop_atoms.net_wm_state_remove :
2896                     prop_atoms.net_wm_state_add;
2897             else if (state == prop_atoms.net_wm_state_skip_pager)
2898                 action = self->skip_pager ?
2899                     prop_atoms.net_wm_state_remove :
2900                     prop_atoms.net_wm_state_add;
2901             else if (state == prop_atoms.net_wm_state_hidden)
2902                 action = self->iconic ?
2903                     prop_atoms.net_wm_state_remove :
2904                     prop_atoms.net_wm_state_add;
2905             else if (state == prop_atoms.net_wm_state_fullscreen)
2906                 action = fullscreen ?
2907                     prop_atoms.net_wm_state_remove :
2908                     prop_atoms.net_wm_state_add;
2909             else if (state == prop_atoms.net_wm_state_above)
2910                 action = self->above ? prop_atoms.net_wm_state_remove :
2911                     prop_atoms.net_wm_state_add;
2912             else if (state == prop_atoms.net_wm_state_below)
2913                 action = self->below ? prop_atoms.net_wm_state_remove :
2914                     prop_atoms.net_wm_state_add;
2915             else if (state == prop_atoms.net_wm_state_demands_attention)
2916                 action = self->demands_attention ?
2917                     prop_atoms.net_wm_state_remove :
2918                     prop_atoms.net_wm_state_add;
2919             else if (state == prop_atoms.ob_wm_state_undecorated)
2920                 action = undecorated ? prop_atoms.net_wm_state_remove :
2921                     prop_atoms.net_wm_state_add;
2922         }
2923     
2924         if (action == prop_atoms.net_wm_state_add) {
2925             if (state == prop_atoms.net_wm_state_modal) {
2926                 modal = TRUE;
2927             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2928                 max_vert = TRUE;
2929             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2930                 max_horz = TRUE;
2931             } else if (state == prop_atoms.net_wm_state_shaded) {
2932                 shaded = TRUE;
2933             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2934                 self->skip_taskbar = TRUE;
2935             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2936                 self->skip_pager = TRUE;
2937             } else if (state == prop_atoms.net_wm_state_hidden) {
2938                 iconic = TRUE;
2939             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2940                 fullscreen = TRUE;
2941             } else if (state == prop_atoms.net_wm_state_above) {
2942                 self->above = TRUE;
2943                 self->below = FALSE;
2944             } else if (state == prop_atoms.net_wm_state_below) {
2945                 self->above = FALSE;
2946                 self->below = TRUE;
2947             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2948                 demands_attention = TRUE;
2949             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2950                 undecorated = TRUE;
2951             }
2952
2953         } else { /* action == prop_atoms.net_wm_state_remove */
2954             if (state == prop_atoms.net_wm_state_modal) {
2955                 modal = FALSE;
2956             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2957                 max_vert = FALSE;
2958             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2959                 max_horz = FALSE;
2960             } else if (state == prop_atoms.net_wm_state_shaded) {
2961                 shaded = FALSE;
2962             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2963                 self->skip_taskbar = FALSE;
2964             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2965                 self->skip_pager = FALSE;
2966             } else if (state == prop_atoms.net_wm_state_hidden) {
2967                 iconic = FALSE;
2968             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2969                 fullscreen = FALSE;
2970             } else if (state == prop_atoms.net_wm_state_above) {
2971                 self->above = FALSE;
2972             } else if (state == prop_atoms.net_wm_state_below) {
2973                 self->below = FALSE;
2974             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2975                 demands_attention = FALSE;
2976             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2977                 undecorated = FALSE;
2978             }
2979         }
2980     }
2981     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2982         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2983             /* toggling both */
2984             if (max_horz == max_vert) { /* both going the same way */
2985                 client_maximize(self, max_horz, 0);
2986             } else {
2987                 client_maximize(self, max_horz, 1);
2988                 client_maximize(self, max_vert, 2);
2989             }
2990         } else {
2991             /* toggling one */
2992             if (max_horz != self->max_horz)
2993                 client_maximize(self, max_horz, 1);
2994             else
2995                 client_maximize(self, max_vert, 2);
2996         }
2997     }
2998     /* change fullscreen state before shading, as it will affect if the window
2999        can shade or not */
3000     if (fullscreen != self->fullscreen)
3001         client_fullscreen(self, fullscreen);
3002     if (shaded != self->shaded)
3003         client_shade(self, shaded);
3004     if (undecorated != self->undecorated)
3005         client_set_undecorated(self, undecorated);
3006     if (modal != self->modal) {
3007         self->modal = modal;
3008         /* when a window changes modality, then its stacking order with its
3009            transients needs to change */
3010         client_raise(self);
3011     }
3012     if (iconic != self->iconic)
3013         client_iconify(self, iconic, FALSE);
3014
3015     if (demands_attention != self->demands_attention)
3016         client_hilite(self, demands_attention);
3017
3018     client_change_state(self); /* change the hint to reflect these changes */
3019 }
3020
3021 ObClient *client_focus_target(ObClient *self)
3022 {
3023     ObClient *child = NULL;
3024
3025     child = client_search_modal_child(self);
3026     if (child) return child;
3027     return self;
3028 }
3029
3030 gboolean client_can_focus(ObClient *self)
3031 {
3032     XEvent ev;
3033
3034     /* choose the correct target */
3035     self = client_focus_target(self);
3036
3037     if (!self->frame->visible)
3038         return FALSE;
3039
3040     if (!(self->can_focus || self->focus_notify))
3041         return FALSE;
3042
3043     /* do a check to see if the window has already been unmapped or destroyed
3044        do this intelligently while watching out for unmaps we've generated
3045        (ignore_unmaps > 0) */
3046     if (XCheckTypedWindowEvent(ob_display, self->window,
3047                                DestroyNotify, &ev)) {
3048         XPutBackEvent(ob_display, &ev);
3049         return FALSE;
3050     }
3051     while (XCheckTypedWindowEvent(ob_display, self->window,
3052                                   UnmapNotify, &ev)) {
3053         if (self->ignore_unmaps) {
3054             self->ignore_unmaps--;
3055         } else {
3056             XPutBackEvent(ob_display, &ev);
3057             return FALSE;
3058         }
3059     }
3060
3061     return TRUE;
3062 }
3063
3064 gboolean client_focus(ObClient *self)
3065 {
3066     /* choose the correct target */
3067     self = client_focus_target(self);
3068
3069     if (!client_can_focus(self)) {
3070         if (!self->frame->visible) {
3071             /* update the focus lists */
3072             focus_order_to_top(self);
3073         }
3074         return FALSE;
3075     }
3076
3077     ob_debug_type(OB_DEBUG_FOCUS,
3078                   "Focusing client \"%s\" at time %u\n",
3079                   self->title, event_curtime);
3080
3081     if (self->can_focus) {
3082         /* This can cause a BadMatch error with CurrentTime, or if an app
3083            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3084         xerror_set_ignore(TRUE);
3085         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3086                        event_curtime);
3087         xerror_set_ignore(FALSE);
3088     }
3089
3090     if (self->focus_notify) {
3091         XEvent ce;
3092         ce.xclient.type = ClientMessage;
3093         ce.xclient.message_type = prop_atoms.wm_protocols;
3094         ce.xclient.display = ob_display;
3095         ce.xclient.window = self->window;
3096         ce.xclient.format = 32;
3097         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3098         ce.xclient.data.l[1] = event_curtime;
3099         ce.xclient.data.l[2] = 0l;
3100         ce.xclient.data.l[3] = 0l;
3101         ce.xclient.data.l[4] = 0l;
3102         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3103     }
3104
3105 #ifdef DEBUG_FOCUS
3106     ob_debug("%sively focusing %lx at %d\n",
3107              (self->can_focus ? "act" : "pass"),
3108              self->window, (gint) event_curtime);
3109 #endif
3110
3111     /* Cause the FocusIn to come back to us. Important for desktop switches,
3112        since otherwise we'll have no FocusIn on the queue and send it off to
3113        the focus_backup. */
3114     XSync(ob_display, FALSE);
3115     return TRUE;
3116 }
3117
3118 void client_activate(ObClient *self, gboolean here, gboolean user)
3119 {
3120     guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3121
3122     /* XXX do some stuff here if user is false to determine if we really want
3123        to activate it or not (a parent or group member is currently
3124        active)?
3125     */
3126     ob_debug_type(OB_DEBUG_FOCUS,
3127                   "Want to activate window 0x%x with time %u (last time %u), "
3128                   "source=%s\n",
3129                   self->window, event_curtime, last_time,
3130                   (user ? "user" : "application"));
3131
3132     if (!user && event_curtime && last_time &&
3133         !event_time_after(event_curtime, last_time))
3134     {
3135         client_hilite(self, TRUE);
3136     } else {
3137         if (event_curtime != CurrentTime)
3138             self->user_time = event_curtime;
3139
3140         /* if using focus_delay, stop the timer now so that focus doesn't
3141            go moving on us */
3142         event_halt_focus_delay();
3143
3144         if (client_normal(self) && screen_showing_desktop)
3145             screen_show_desktop(FALSE);
3146         if (self->iconic)
3147             client_iconify(self, FALSE, here);
3148         if (self->desktop != DESKTOP_ALL &&
3149             self->desktop != screen_desktop) {
3150             if (here)
3151                 client_set_desktop(self, screen_desktop, FALSE);
3152             else
3153                 screen_set_desktop(self->desktop);
3154         } else if (!self->frame->visible)
3155             /* if its not visible for other reasons, then don't mess
3156                with it */
3157             return;
3158         if (self->shaded)
3159             client_shade(self, FALSE);
3160
3161         client_focus(self);
3162
3163         /* we do this as an action here. this is rather important. this is
3164            because we want the results from the focus change to take place 
3165            BEFORE we go about raising the window. when a fullscreen window 
3166            loses focus, we need this or else the raise wont be able to raise 
3167            above the to-lose-focus fullscreen window. */
3168         client_raise(self);
3169     }
3170 }
3171
3172 void client_raise(ObClient *self)
3173 {
3174     action_run_string("Raise", self, CurrentTime);
3175 }
3176
3177 void client_lower(ObClient *self)
3178 {
3179     action_run_string("Lower", self, CurrentTime);
3180 }
3181
3182 gboolean client_focused(ObClient *self)
3183 {
3184     return self == focus_client;
3185 }
3186
3187 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3188 {
3189     guint i;
3190     /* si is the smallest image >= req */
3191     /* li is the largest image < req */
3192     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3193
3194     if (!self->nicons) {
3195         ObClientIcon *parent = NULL;
3196
3197         if (self->transient_for) {
3198             if (self->transient_for != OB_TRAN_GROUP)
3199                 parent = client_icon_recursive(self->transient_for, w, h);
3200             else {
3201                 GSList *it;
3202                 for (it = self->group->members; it; it = g_slist_next(it)) {
3203                     ObClient *c = it->data;
3204                     if (c != self && !c->transient_for) {
3205                         if ((parent = client_icon_recursive(c, w, h)))
3206                             break;
3207                     }
3208                 }
3209             }
3210         }
3211         
3212         return parent;
3213     }
3214
3215     for (i = 0; i < self->nicons; ++i) {
3216         size = self->icons[i].width * self->icons[i].height;
3217         if (size < smallest && size >= (unsigned)(w * h)) {
3218             smallest = size;
3219             si = i;
3220         }
3221         if (size > largest && size <= (unsigned)(w * h)) {
3222             largest = size;
3223             li = i;
3224         }
3225     }
3226     if (largest == 0) /* didnt find one smaller than the requested size */
3227         return &self->icons[si];
3228     return &self->icons[li];
3229 }
3230
3231 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3232 {
3233     ObClientIcon *ret;
3234     static ObClientIcon deficon;
3235
3236     if (!(ret = client_icon_recursive(self, w, h))) {
3237         deficon.width = deficon.height = 48;
3238         deficon.data = ob_rr_theme->def_win_icon;
3239         ret = &deficon;
3240     }
3241     return ret;
3242 }
3243
3244 void client_set_layer(ObClient *self, gint layer)
3245 {
3246     if (layer < 0) {
3247         self->below = TRUE;
3248         self->above = FALSE;
3249     } else if (layer == 0) {
3250         self->below = self->above = FALSE;
3251     } else {
3252         self->below = FALSE;
3253         self->above = TRUE;
3254     }
3255     client_calc_layer(self);
3256     client_change_state(self); /* reflect this in the state hints */
3257 }
3258
3259 void client_set_undecorated(ObClient *self, gboolean undecorated)
3260 {
3261     if (self->undecorated != undecorated) {
3262         self->undecorated = undecorated;
3263         client_setup_decor_and_functions(self);
3264         /* Make sure the client knows it might have moved. Maybe there is a
3265          * better way of doing this so only one client_configure is sent, but
3266          * since 125 of these are sent per second when moving the window (with
3267          * user = FALSE) i doubt it matters much.
3268          */
3269         client_configure(self, self->area.x, self->area.y,
3270                          self->area.width, self->area.height, TRUE, TRUE);
3271         client_change_state(self); /* reflect this in the state hints */
3272     }
3273 }
3274
3275 guint client_monitor(ObClient *self)
3276 {
3277     return screen_find_monitor(&self->frame->area);
3278 }
3279
3280 ObClient *client_search_top_parent(ObClient *self)
3281 {
3282     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3283            client_normal(self))
3284         self = self->transient_for;
3285     return self;
3286 }
3287
3288 GSList *client_search_all_top_parents(ObClient *self)
3289 {
3290     GSList *ret = NULL;
3291
3292     /* move up the direct transient chain as far as possible */
3293     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3294         self = self->transient_for;
3295
3296     if (!self->transient_for)
3297         ret = g_slist_prepend(ret, self);
3298     else {
3299             GSList *it;
3300
3301             g_assert(self->group);
3302
3303             for (it = self->group->members; it; it = g_slist_next(it)) {
3304                 ObClient *c = it->data;
3305
3306                 if (!c->transient_for && client_normal(c))
3307                     ret = g_slist_prepend(ret, c);
3308             }
3309
3310             if (ret == NULL) /* no group parents */
3311                 ret = g_slist_prepend(ret, self);
3312     }
3313
3314     return ret;
3315 }
3316
3317 ObClient *client_search_focus_parent(ObClient *self)
3318 {
3319     if (self->transient_for) {
3320         if (self->transient_for != OB_TRAN_GROUP) {
3321             if (client_focused(self->transient_for))
3322                 return self->transient_for;
3323         } else {
3324             GSList *it;
3325
3326             for (it = self->group->members; it; it = g_slist_next(it)) {
3327                 ObClient *c = it->data;
3328
3329                 /* checking transient_for prevents infinate loops! */
3330                 if (c != self && !c->transient_for)
3331                     if (client_focused(c))
3332                         return c;
3333             }
3334         }
3335     }
3336
3337     return NULL;
3338 }
3339
3340 ObClient *client_search_parent(ObClient *self, ObClient *search)
3341 {
3342     if (self->transient_for) {
3343         if (self->transient_for != OB_TRAN_GROUP) {
3344             if (self->transient_for == search)
3345                 return search;
3346         } else {
3347             GSList *it;
3348
3349             for (it = self->group->members; it; it = g_slist_next(it)) {
3350                 ObClient *c = it->data;
3351
3352                 /* checking transient_for prevents infinate loops! */
3353                 if (c != self && !c->transient_for)
3354                     if (c == search)
3355                         return search;
3356             }
3357         }
3358     }
3359
3360     return NULL;
3361 }
3362
3363 ObClient *client_search_transient(ObClient *self, ObClient *search)
3364 {
3365     GSList *sit;
3366
3367     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3368         if (sit->data == search)
3369             return search;
3370         if (client_search_transient(sit->data, search))
3371             return search;
3372     }
3373     return NULL;
3374 }
3375
3376 void client_update_sm_client_id(ObClient *self)
3377 {
3378     g_free(self->sm_client_id);
3379     self->sm_client_id = NULL;
3380
3381     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3382         self->group)
3383         PROP_GETS(self->group->leader, sm_client_id, locale,
3384                   &self->sm_client_id);
3385 }
3386
3387 #define WANT_EDGE(cur, c) \
3388             if(cur == c)                                                      \
3389                 continue;                                                     \
3390             if(!client_normal(cur))                                   \
3391                 continue;                                                     \
3392             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3393                 continue;                                                     \
3394             if(cur->iconic)                                                   \
3395                 continue;                                                     \
3396             if(cur->layer < c->layer && !config_resist_layers_below)          \
3397                 continue;
3398
3399 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3400             if ((his_edge_start >= my_edge_start && \
3401                  his_edge_start <= my_edge_end) ||  \
3402                 (my_edge_start >= his_edge_start && \
3403                  my_edge_start <= his_edge_end))    \
3404                 dest = his_offset;
3405
3406 /* finds the nearest edge in the given direction from the current client
3407  * note to self: the edge is the -frame- edge (the actual one), not the
3408  * client edge.
3409  */
3410 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3411 {
3412     gint dest, monitor_dest;
3413     gint my_edge_start, my_edge_end, my_offset;
3414     GList *it;
3415     Rect *a, *monitor;
3416     
3417     if(!client_list)
3418         return -1;
3419
3420     a = screen_area(c->desktop);
3421     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3422
3423     switch(dir) {
3424     case OB_DIRECTION_NORTH:
3425         my_edge_start = c->frame->area.x;
3426         my_edge_end = c->frame->area.x + c->frame->area.width;
3427         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3428         
3429         /* default: top of screen */
3430         dest = a->y + (hang ? c->frame->area.height : 0);
3431         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3432         /* if the monitor edge comes before the screen edge, */
3433         /* use that as the destination instead. (For xinerama) */
3434         if (monitor_dest != dest && my_offset > monitor_dest)
3435             dest = monitor_dest; 
3436
3437         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3438             gint his_edge_start, his_edge_end, his_offset;
3439             ObClient *cur = it->data;
3440
3441             WANT_EDGE(cur, c)
3442
3443             his_edge_start = cur->frame->area.x;
3444             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3445             his_offset = cur->frame->area.y + 
3446                          (hang ? 0 : cur->frame->area.height);
3447
3448             if(his_offset + 1 > my_offset)
3449                 continue;
3450
3451             if(his_offset < dest)
3452                 continue;
3453
3454             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3455         }
3456         break;
3457     case OB_DIRECTION_SOUTH:
3458         my_edge_start = c->frame->area.x;
3459         my_edge_end = c->frame->area.x + c->frame->area.width;
3460         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3461
3462         /* default: bottom of screen */
3463         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3464         monitor_dest = monitor->y + monitor->height -
3465                        (hang ? c->frame->area.height : 0);
3466         /* if the monitor edge comes before the screen edge, */
3467         /* use that as the destination instead. (For xinerama) */
3468         if (monitor_dest != dest && my_offset < monitor_dest)
3469             dest = monitor_dest; 
3470
3471         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3472             gint his_edge_start, his_edge_end, his_offset;
3473             ObClient *cur = it->data;
3474
3475             WANT_EDGE(cur, c)
3476
3477             his_edge_start = cur->frame->area.x;
3478             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3479             his_offset = cur->frame->area.y +
3480                          (hang ? cur->frame->area.height : 0);
3481
3482
3483             if(his_offset - 1 < my_offset)
3484                 continue;
3485             
3486             if(his_offset > dest)
3487                 continue;
3488
3489             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3490         }
3491         break;
3492     case OB_DIRECTION_WEST:
3493         my_edge_start = c->frame->area.y;
3494         my_edge_end = c->frame->area.y + c->frame->area.height;
3495         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3496
3497         /* default: leftmost egde of screen */
3498         dest = a->x + (hang ? c->frame->area.width : 0);
3499         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3500         /* if the monitor edge comes before the screen edge, */
3501         /* use that as the destination instead. (For xinerama) */
3502         if (monitor_dest != dest && my_offset > monitor_dest)
3503             dest = monitor_dest;            
3504
3505         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3506             gint his_edge_start, his_edge_end, his_offset;
3507             ObClient *cur = it->data;
3508
3509             WANT_EDGE(cur, c)
3510
3511             his_edge_start = cur->frame->area.y;
3512             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3513             his_offset = cur->frame->area.x +
3514                          (hang ? 0 : cur->frame->area.width);
3515
3516             if(his_offset + 1 > my_offset)
3517                 continue;
3518
3519             if(his_offset < dest)
3520                 continue;
3521
3522             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3523         }
3524        break;
3525     case OB_DIRECTION_EAST:
3526         my_edge_start = c->frame->area.y;
3527         my_edge_end = c->frame->area.y + c->frame->area.height;
3528         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3529         
3530         /* default: rightmost edge of screen */
3531         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3532         monitor_dest = monitor->x + monitor->width -
3533                        (hang ? c->frame->area.width : 0);
3534         /* if the monitor edge comes before the screen edge, */
3535         /* use that as the destination instead. (For xinerama) */
3536         if (monitor_dest != dest && my_offset < monitor_dest)
3537             dest = monitor_dest;            
3538
3539         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3540             gint his_edge_start, his_edge_end, his_offset;
3541             ObClient *cur = it->data;
3542
3543             WANT_EDGE(cur, c)
3544
3545             his_edge_start = cur->frame->area.y;
3546             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3547             his_offset = cur->frame->area.x +
3548                          (hang ? cur->frame->area.width : 0);
3549
3550             if(his_offset - 1 < my_offset)
3551                 continue;
3552             
3553             if(his_offset > dest)
3554                 continue;
3555
3556             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3557         }
3558         break;
3559     case OB_DIRECTION_NORTHEAST:
3560     case OB_DIRECTION_SOUTHEAST:
3561     case OB_DIRECTION_NORTHWEST:
3562     case OB_DIRECTION_SOUTHWEST:
3563         /* not implemented */
3564     default:
3565         g_assert_not_reached();
3566         dest = 0; /* suppress warning */
3567     }
3568     return dest;
3569 }
3570
3571 ObClient* client_under_pointer()
3572 {
3573     gint x, y;
3574     GList *it;
3575     ObClient *ret = NULL;
3576
3577     if (screen_pointer_pos(&x, &y)) {
3578         for (it = stacking_list; it; it = g_list_next(it)) {
3579             if (WINDOW_IS_CLIENT(it->data)) {
3580                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3581                 if (c->frame->visible &&
3582                     RECT_CONTAINS(c->frame->area, x, y)) {
3583                     ret = c;
3584                     break;
3585                 }
3586             }
3587         }
3588     }
3589     return ret;
3590 }
3591
3592 gboolean client_has_group_siblings(ObClient *self)
3593 {
3594     return self->group && self->group->members->next;
3595 }