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