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