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