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