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