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