]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
?
[dana/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43
44 #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                 /* Fall back focus since we're disappearing */
2430                 if (focus_client == self)
2431                     client_unfocus(self);
2432
2433                 changed = TRUE;
2434             }
2435         } else {
2436             glong old;
2437
2438             if (curdesk)
2439                 client_set_desktop(self, screen_desktop, FALSE);
2440
2441             old = self->wmstate;
2442             self->wmstate = self->shaded ? IconicState : NormalState;
2443             if (old != self->wmstate)
2444                 PROP_MSG(self->window, kde_wm_change_state,
2445                          self->wmstate, 1, 0, 0);
2446
2447             /* this puts it after the current focused window */
2448             focus_order_remove(self);
2449             focus_order_add_new(self);
2450
2451             changed = TRUE;
2452         }
2453     }
2454
2455     if (changed) {
2456         client_change_state(self);
2457         client_showhide(self);
2458         if (STRUT_EXISTS(self->strut))
2459             screen_update_areas();
2460     }
2461
2462     /* iconify all direct transients */
2463     for (it = self->transients; it; it = g_slist_next(it))
2464         if (it->data != self)
2465             if (client_is_direct_child(self, it->data))
2466                 client_iconify_recursive(it->data, iconic, curdesk);
2467 }
2468
2469 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2470 {
2471     /* move up the transient chain as far as possible first */
2472     self = client_search_top_parent(self);
2473     client_iconify_recursive(self, iconic, curdesk);
2474 }
2475
2476 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2477 {
2478     gint x, y, w, h;
2479      
2480     g_assert(dir == 0 || dir == 1 || dir == 2);
2481     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2482
2483     /* check if already done */
2484     if (max) {
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     } else {
2489         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2490         if (dir == 1 && !self->max_horz) return;
2491         if (dir == 2 && !self->max_vert) return;
2492     }
2493
2494     /* we just tell it to configure in the same place and client_configure
2495        worries about filling the screen with the window */
2496     x = self->area.x;
2497     y = self->area.y;
2498     w = self->area.width;
2499     h = self->area.height;
2500
2501     if (max) {
2502         if (savearea) {
2503             if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2504                 RECT_SET(self->pre_max_area,
2505                          self->area.x, self->pre_max_area.y,
2506                          self->area.width, self->pre_max_area.height);
2507             }
2508             if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2509                 RECT_SET(self->pre_max_area,
2510                          self->pre_max_area.x, self->area.y,
2511                          self->pre_max_area.width, self->area.height);
2512             }
2513         }
2514     } else {
2515         Rect *a;
2516
2517         a = screen_area_monitor(self->desktop, 0);
2518         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2519             if (self->pre_max_area.width > 0) {
2520                 x = self->pre_max_area.x;
2521                 w = self->pre_max_area.width;
2522
2523                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2524                          0, self->pre_max_area.height);
2525             } else {
2526                 /* pick some fallbacks... */
2527                 x = a->x + a->width / 4;
2528                 w = a->width / 2;
2529             }
2530         }
2531         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2532             if (self->pre_max_area.height > 0) {
2533                 y = self->pre_max_area.y;
2534                 h = self->pre_max_area.height;
2535
2536                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2537                          self->pre_max_area.width, 0);
2538             } else {
2539                 /* pick some fallbacks... */
2540                 y = a->y + a->height / 4;
2541                 h = a->height / 2;
2542             }
2543         }
2544     }
2545
2546     if (dir == 0 || dir == 1) /* horz */
2547         self->max_horz = max;
2548     if (dir == 0 || dir == 2) /* vert */
2549         self->max_vert = max;
2550
2551     client_change_state(self); /* change the state hints on the client */
2552
2553     client_setup_decor_and_functions(self);
2554
2555     client_move_resize(self, x, y, w, h);
2556 }
2557
2558 void client_shade(ObClient *self, gboolean shade)
2559 {
2560     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2561          shade) ||                         /* can't shade */
2562         self->shaded == shade) return;     /* already done */
2563
2564     /* when we're iconic, don't change the wmstate */
2565     if (!self->iconic) {
2566         glong old;
2567
2568         old = self->wmstate;
2569         self->wmstate = shade ? IconicState : NormalState;
2570         if (old != self->wmstate)
2571             PROP_MSG(self->window, kde_wm_change_state,
2572                      self->wmstate, 1, 0, 0);
2573     }
2574
2575     self->shaded = shade;
2576     client_change_state(self);
2577     /* resize the frame to just the titlebar */
2578     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2579 }
2580
2581 void client_close(ObClient *self)
2582 {
2583     XEvent ce;
2584
2585     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2586
2587     /* in the case that the client provides no means to requesting that it
2588        close, we just kill it */
2589     if (!self->delete_window)
2590         client_kill(self);
2591     
2592     /*
2593       XXX: itd be cool to do timeouts and shit here for killing the client's
2594       process off
2595       like... if the window is around after 5 seconds, then the close button
2596       turns a nice red, and if this function is called again, the client is
2597       explicitly killed.
2598     */
2599
2600     ce.xclient.type = ClientMessage;
2601     ce.xclient.message_type =  prop_atoms.wm_protocols;
2602     ce.xclient.display = ob_display;
2603     ce.xclient.window = self->window;
2604     ce.xclient.format = 32;
2605     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2606     ce.xclient.data.l[1] = event_curtime;
2607     ce.xclient.data.l[2] = 0l;
2608     ce.xclient.data.l[3] = 0l;
2609     ce.xclient.data.l[4] = 0l;
2610     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2611 }
2612
2613 void client_kill(ObClient *self)
2614 {
2615     XKillClient(ob_display, self->window);
2616 }
2617
2618 void client_hilite(ObClient *self, gboolean hilite)
2619 {
2620     if (self->demands_attention == hilite)
2621         return; /* no change */
2622
2623     /* don't allow focused windows to hilite */
2624     self->demands_attention = hilite && !client_focused(self);
2625     if (self->demands_attention)
2626         frame_flash_start(self->frame);
2627     else
2628         frame_flash_stop(self->frame);
2629     client_change_state(self);
2630 }
2631
2632 void client_set_desktop_recursive(ObClient *self,
2633                                   guint target, gboolean donthide)
2634 {
2635     guint old;
2636     GSList *it;
2637
2638     if (target != self->desktop) {
2639
2640         ob_debug("Setting desktop %u\n", target+1);
2641
2642         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2643
2644         /* remove from the old desktop(s) */
2645         focus_order_remove(self);
2646
2647         old = self->desktop;
2648         self->desktop = target;
2649         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2650         /* the frame can display the current desktop state */
2651         frame_adjust_state(self->frame);
2652         /* 'move' the window to the new desktop */
2653         if (!donthide)
2654             client_showhide(self);
2655         /* raise if it was not already on the desktop */
2656         if (old != DESKTOP_ALL)
2657             client_raise(self);
2658         if (STRUT_EXISTS(self->strut))
2659             screen_update_areas();
2660
2661         /* add to the new desktop(s) */
2662         if (config_focus_new)
2663             focus_order_to_top(self);
2664         else
2665             focus_order_to_bottom(self);
2666     }
2667
2668     /* move all transients */
2669     for (it = self->transients; it; it = g_slist_next(it))
2670         if (it->data != self)
2671             if (client_is_direct_child(self, it->data))
2672                 client_set_desktop_recursive(it->data, target, donthide);
2673 }
2674
2675 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2676 {
2677     self = client_search_top_parent(self);
2678     client_set_desktop_recursive(self, target, donthide);
2679 }
2680
2681 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
2682 {
2683     while (child != parent &&
2684            child->transient_for && child->transient_for != OB_TRAN_GROUP)
2685         child = child->transient_for;
2686     return child == parent;
2687 }
2688
2689 ObClient *client_search_modal_child(ObClient *self)
2690 {
2691     GSList *it;
2692     ObClient *ret;
2693   
2694     for (it = self->transients; it; it = g_slist_next(it)) {
2695         ObClient *c = it->data;
2696         if ((ret = client_search_modal_child(c))) return ret;
2697         if (c->modal) return c;
2698     }
2699     return NULL;
2700 }
2701
2702 gboolean client_validate(ObClient *self)
2703 {
2704     XEvent e; 
2705
2706     XSync(ob_display, FALSE); /* get all events on the server */
2707
2708     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2709         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2710         XPutBackEvent(ob_display, &e);
2711         return FALSE;
2712     }
2713
2714     return TRUE;
2715 }
2716
2717 void client_set_wm_state(ObClient *self, glong state)
2718 {
2719     if (state == self->wmstate) return; /* no change */
2720   
2721     switch (state) {
2722     case IconicState:
2723         client_iconify(self, TRUE, TRUE);
2724         break;
2725     case NormalState:
2726         client_iconify(self, FALSE, TRUE);
2727         break;
2728     }
2729 }
2730
2731 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2732 {
2733     gboolean shaded = self->shaded;
2734     gboolean fullscreen = self->fullscreen;
2735     gboolean undecorated = self->undecorated;
2736     gboolean max_horz = self->max_horz;
2737     gboolean max_vert = self->max_vert;
2738     gboolean modal = self->modal;
2739     gboolean iconic = self->iconic;
2740     gboolean demands_attention = self->demands_attention;
2741     gint i;
2742
2743     if (!(action == prop_atoms.net_wm_state_add ||
2744           action == prop_atoms.net_wm_state_remove ||
2745           action == prop_atoms.net_wm_state_toggle))
2746         /* an invalid action was passed to the client message, ignore it */
2747         return; 
2748
2749     for (i = 0; i < 2; ++i) {
2750         Atom state = i == 0 ? data1 : data2;
2751     
2752         if (!state) continue;
2753
2754         /* if toggling, then pick whether we're adding or removing */
2755         if (action == prop_atoms.net_wm_state_toggle) {
2756             if (state == prop_atoms.net_wm_state_modal)
2757                 action = modal ? prop_atoms.net_wm_state_remove :
2758                     prop_atoms.net_wm_state_add;
2759             else if (state == prop_atoms.net_wm_state_maximized_vert)
2760                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2761                     prop_atoms.net_wm_state_add;
2762             else if (state == prop_atoms.net_wm_state_maximized_horz)
2763                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2764                     prop_atoms.net_wm_state_add;
2765             else if (state == prop_atoms.net_wm_state_shaded)
2766                 action = shaded ? prop_atoms.net_wm_state_remove :
2767                     prop_atoms.net_wm_state_add;
2768             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2769                 action = self->skip_taskbar ?
2770                     prop_atoms.net_wm_state_remove :
2771                     prop_atoms.net_wm_state_add;
2772             else if (state == prop_atoms.net_wm_state_skip_pager)
2773                 action = self->skip_pager ?
2774                     prop_atoms.net_wm_state_remove :
2775                     prop_atoms.net_wm_state_add;
2776             else if (state == prop_atoms.net_wm_state_hidden)
2777                 action = self->iconic ?
2778                     prop_atoms.net_wm_state_remove :
2779                     prop_atoms.net_wm_state_add;
2780             else if (state == prop_atoms.net_wm_state_fullscreen)
2781                 action = fullscreen ?
2782                     prop_atoms.net_wm_state_remove :
2783                     prop_atoms.net_wm_state_add;
2784             else if (state == prop_atoms.net_wm_state_above)
2785                 action = self->above ? prop_atoms.net_wm_state_remove :
2786                     prop_atoms.net_wm_state_add;
2787             else if (state == prop_atoms.net_wm_state_below)
2788                 action = self->below ? prop_atoms.net_wm_state_remove :
2789                     prop_atoms.net_wm_state_add;
2790             else if (state == prop_atoms.net_wm_state_demands_attention)
2791                 action = self->demands_attention ?
2792                     prop_atoms.net_wm_state_remove :
2793                     prop_atoms.net_wm_state_add;
2794             else if (state == prop_atoms.ob_wm_state_undecorated)
2795                 action = undecorated ? prop_atoms.net_wm_state_remove :
2796                     prop_atoms.net_wm_state_add;
2797         }
2798     
2799         if (action == prop_atoms.net_wm_state_add) {
2800             if (state == prop_atoms.net_wm_state_modal) {
2801                 modal = TRUE;
2802             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2803                 max_vert = TRUE;
2804             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2805                 max_horz = TRUE;
2806             } else if (state == prop_atoms.net_wm_state_shaded) {
2807                 shaded = TRUE;
2808             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2809                 self->skip_taskbar = TRUE;
2810             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2811                 self->skip_pager = TRUE;
2812             } else if (state == prop_atoms.net_wm_state_hidden) {
2813                 iconic = TRUE;
2814             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2815                 fullscreen = TRUE;
2816             } else if (state == prop_atoms.net_wm_state_above) {
2817                 self->above = TRUE;
2818                 self->below = FALSE;
2819             } else if (state == prop_atoms.net_wm_state_below) {
2820                 self->above = FALSE;
2821                 self->below = TRUE;
2822             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2823                 demands_attention = TRUE;
2824             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2825                 undecorated = TRUE;
2826             }
2827
2828         } else { /* action == prop_atoms.net_wm_state_remove */
2829             if (state == prop_atoms.net_wm_state_modal) {
2830                 modal = FALSE;
2831             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2832                 max_vert = FALSE;
2833             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2834                 max_horz = FALSE;
2835             } else if (state == prop_atoms.net_wm_state_shaded) {
2836                 shaded = FALSE;
2837             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2838                 self->skip_taskbar = FALSE;
2839             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2840                 self->skip_pager = FALSE;
2841             } else if (state == prop_atoms.net_wm_state_hidden) {
2842                 iconic = FALSE;
2843             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2844                 fullscreen = FALSE;
2845             } else if (state == prop_atoms.net_wm_state_above) {
2846                 self->above = FALSE;
2847             } else if (state == prop_atoms.net_wm_state_below) {
2848                 self->below = FALSE;
2849             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2850                 demands_attention = FALSE;
2851             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2852                 undecorated = FALSE;
2853             }
2854         }
2855     }
2856     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2857         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2858             /* toggling both */
2859             if (max_horz == max_vert) { /* both going the same way */
2860                 client_maximize(self, max_horz, 0, TRUE);
2861             } else {
2862                 client_maximize(self, max_horz, 1, TRUE);
2863                 client_maximize(self, max_vert, 2, TRUE);
2864             }
2865         } else {
2866             /* toggling one */
2867             if (max_horz != self->max_horz)
2868                 client_maximize(self, max_horz, 1, TRUE);
2869             else
2870                 client_maximize(self, max_vert, 2, TRUE);
2871         }
2872     }
2873     /* change fullscreen state before shading, as it will affect if the window
2874        can shade or not */
2875     if (fullscreen != self->fullscreen)
2876         client_fullscreen(self, fullscreen, TRUE);
2877     if (shaded != self->shaded)
2878         client_shade(self, shaded);
2879     if (undecorated != self->undecorated)
2880         client_set_undecorated(self, undecorated);
2881     if (modal != self->modal) {
2882         self->modal = modal;
2883         /* when a window changes modality, then its stacking order with its
2884            transients needs to change */
2885         client_raise(self);
2886     }
2887     if (iconic != self->iconic)
2888         client_iconify(self, iconic, FALSE);
2889
2890     if (demands_attention != self->demands_attention)
2891         client_hilite(self, demands_attention);
2892
2893     client_change_state(self); /* change the hint to reflect these changes */
2894 }
2895
2896 ObClient *client_focus_target(ObClient *self)
2897 {
2898     ObClient *child = NULL;
2899
2900     child = client_search_modal_child(self);
2901     if (child) return child;
2902     return self;
2903 }
2904
2905 gboolean client_can_focus(ObClient *self)
2906 {
2907     XEvent ev;
2908
2909     /* choose the correct target */
2910     self = client_focus_target(self);
2911
2912     if (!self->frame->visible)
2913         return FALSE;
2914
2915     if (!(self->can_focus || self->focus_notify))
2916         return FALSE;
2917
2918     /* do a check to see if the window has already been unmapped or destroyed
2919        do this intelligently while watching out for unmaps we've generated
2920        (ignore_unmaps > 0) */
2921     if (XCheckTypedWindowEvent(ob_display, self->window,
2922                                DestroyNotify, &ev)) {
2923         XPutBackEvent(ob_display, &ev);
2924         return FALSE;
2925     }
2926     while (XCheckTypedWindowEvent(ob_display, self->window,
2927                                   UnmapNotify, &ev)) {
2928         if (self->ignore_unmaps) {
2929             self->ignore_unmaps--;
2930         } else {
2931             XPutBackEvent(ob_display, &ev);
2932             return FALSE;
2933         }
2934     }
2935
2936     return TRUE;
2937 }
2938
2939 gboolean client_focus(ObClient *self)
2940 {
2941     /* choose the correct target */
2942     self = client_focus_target(self);
2943
2944 #if 0
2945     if (!client_validate(self))
2946         return FALSE;
2947 #endif
2948
2949     if (!client_can_focus(self)) {
2950         if (!self->frame->visible) {
2951             /* update the focus lists */
2952             focus_order_to_top(self);
2953         }
2954         return FALSE;
2955     }
2956
2957     ob_debug("Focusing client \"%s\" at time %u\n", self->title, event_curtime);
2958
2959     if (self->can_focus) {
2960         /* RevertToPointerRoot causes much more headache than RevertToNone, so
2961            I choose to use it always, hopefully to find errors quicker, if any
2962            are left. (I hate X. I hate focus events.)
2963            
2964            Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2965            #799. So now it is RevertToNone again.
2966         */
2967         XSetInputFocus(ob_display, self->window, RevertToNone,
2968                        event_curtime);
2969     }
2970
2971     if (self->focus_notify) {
2972         XEvent ce;
2973         ce.xclient.type = ClientMessage;
2974         ce.xclient.message_type = prop_atoms.wm_protocols;
2975         ce.xclient.display = ob_display;
2976         ce.xclient.window = self->window;
2977         ce.xclient.format = 32;
2978         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2979         ce.xclient.data.l[1] = event_curtime;
2980         ce.xclient.data.l[2] = 0l;
2981         ce.xclient.data.l[3] = 0l;
2982         ce.xclient.data.l[4] = 0l;
2983         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2984     }
2985
2986 #ifdef DEBUG_FOCUS
2987     ob_debug("%sively focusing %lx at %d\n",
2988              (self->can_focus ? "act" : "pass"),
2989              self->window, (gint) event_curtime);
2990 #endif
2991
2992     /* Cause the FocusIn to come back to us. Important for desktop switches,
2993        since otherwise we'll have no FocusIn on the queue and send it off to
2994        the focus_backup. */
2995     XSync(ob_display, FALSE);
2996     return TRUE;
2997 }
2998
2999 /* Used when the current client is closed or otherwise hidden, focus_last will
3000    then prevent focus from going to the mouse pointer
3001 */
3002 void client_unfocus(ObClient *self)
3003 {
3004     if (focus_client == self) {
3005 #ifdef DEBUG_FOCUS
3006         ob_debug("client_unfocus for %lx\n", self->window);
3007 #endif
3008         focus_fallback(OB_FOCUS_FALLBACK_CLOSED);
3009     }
3010 }
3011
3012 void client_activate(ObClient *self, gboolean here, gboolean user, Time time)
3013 {
3014     /* XXX do some stuff here if user is false to determine if we really want
3015        to activate it or not (a parent or group member is currently
3016        active)?
3017     */
3018     ob_debug("Want to activate window 0x%x with time %u (last time %u), "
3019              "source=%s\n",
3020              self->window, time, client_last_user_time,
3021              (user ? "user" : "application"));
3022     if (!user && time && time < client_last_user_time)
3023         client_hilite(self, TRUE);
3024     else {
3025         if (client_normal(self) && screen_showing_desktop)
3026             screen_show_desktop(FALSE);
3027         if (self->iconic)
3028             client_iconify(self, FALSE, here);
3029         if (self->desktop != DESKTOP_ALL &&
3030             self->desktop != screen_desktop) {
3031             if (here)
3032                 client_set_desktop(self, screen_desktop, FALSE);
3033             else
3034                 screen_set_desktop(self->desktop);
3035         } else if (!self->frame->visible)
3036             /* if its not visible for other reasons, then don't mess
3037                with it */
3038             return;
3039         if (self->shaded)
3040             client_shade(self, FALSE);
3041
3042         client_focus(self);
3043
3044         /* we do this an action here. this is rather important. this is because
3045            we want the results from the focus change to take place BEFORE we go
3046            about raising the window. when a fullscreen window loses focus, we
3047            need this or else the raise wont be able to raise above the
3048            to-lose-focus fullscreen window. */
3049         client_raise(self);
3050     }
3051 }
3052
3053 void client_raise(ObClient *self)
3054 {
3055     action_run_string("Raise", self, CurrentTime);
3056 }
3057
3058 void client_lower(ObClient *self)
3059 {
3060     action_run_string("Lower", self, CurrentTime);
3061 }
3062
3063 gboolean client_focused(ObClient *self)
3064 {
3065     return self == focus_client;
3066 }
3067
3068 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3069 {
3070     guint i;
3071     /* si is the smallest image >= req */
3072     /* li is the largest image < req */
3073     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3074
3075     if (!self->nicons) {
3076         ObClientIcon *parent = NULL;
3077
3078         if (self->transient_for) {
3079             if (self->transient_for != OB_TRAN_GROUP)
3080                 parent = client_icon_recursive(self->transient_for, w, h);
3081             else {
3082                 GSList *it;
3083                 for (it = self->group->members; it; it = g_slist_next(it)) {
3084                     ObClient *c = it->data;
3085                     if (c != self && !c->transient_for) {
3086                         if ((parent = client_icon_recursive(c, w, h)))
3087                             break;
3088                     }
3089                 }
3090             }
3091         }
3092         
3093         return parent;
3094     }
3095
3096     for (i = 0; i < self->nicons; ++i) {
3097         size = self->icons[i].width * self->icons[i].height;
3098         if (size < smallest && size >= (unsigned)(w * h)) {
3099             smallest = size;
3100             si = i;
3101         }
3102         if (size > largest && size <= (unsigned)(w * h)) {
3103             largest = size;
3104             li = i;
3105         }
3106     }
3107     if (largest == 0) /* didnt find one smaller than the requested size */
3108         return &self->icons[si];
3109     return &self->icons[li];
3110 }
3111
3112 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3113 {
3114     ObClientIcon *ret;
3115     static ObClientIcon deficon;
3116
3117     if (!(ret = client_icon_recursive(self, w, h))) {
3118         deficon.width = deficon.height = 48;
3119         deficon.data = ob_rr_theme->def_win_icon;
3120         ret = &deficon;
3121     }
3122     return ret;
3123 }
3124
3125 /* this be mostly ripped from fvwm */
3126 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
3127 {
3128     gint my_cx, my_cy, his_cx, his_cy;
3129     gint offset = 0;
3130     gint distance = 0;
3131     gint score, best_score;
3132     ObClient *best_client, *cur;
3133     GList *it;
3134
3135     if(!client_list)
3136         return NULL;
3137
3138     /* first, find the centre coords of the currently focused window */
3139     my_cx = c->frame->area.x + c->frame->area.width / 2;
3140     my_cy = c->frame->area.y + c->frame->area.height / 2;
3141
3142     best_score = -1;
3143     best_client = NULL;
3144
3145     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
3146         cur = it->data;
3147
3148         /* the currently selected window isn't interesting */
3149         if(cur == c)
3150             continue;
3151         if (!client_normal(cur))
3152             continue;
3153         /* using c->desktop instead of screen_desktop doesn't work if the
3154          * current window was omnipresent, hope this doesn't have any other
3155          * side effects */
3156         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3157             continue;
3158         if(cur->iconic)
3159             continue;
3160         if(!(client_focus_target(cur) == cur &&
3161              client_can_focus(cur)))
3162             continue;
3163
3164         /* find the centre coords of this window, from the
3165          * currently focused window's point of view */
3166         his_cx = (cur->frame->area.x - my_cx)
3167             + cur->frame->area.width / 2;
3168         his_cy = (cur->frame->area.y - my_cy)
3169             + cur->frame->area.height / 2;
3170
3171         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3172            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3173             gint tx;
3174             /* Rotate the diagonals 45 degrees counterclockwise.
3175              * To do this, multiply the matrix /+h +h\ with the
3176              * vector (x y).                   \-h +h/
3177              * h = sqrt(0.5). We can set h := 1 since absolute
3178              * distance doesn't matter here. */
3179             tx = his_cx + his_cy;
3180             his_cy = -his_cx + his_cy;
3181             his_cx = tx;
3182         }
3183
3184         switch(dir) {
3185         case OB_DIRECTION_NORTH:
3186         case OB_DIRECTION_SOUTH:
3187         case OB_DIRECTION_NORTHEAST:
3188         case OB_DIRECTION_SOUTHWEST:
3189             offset = (his_cx < 0) ? -his_cx : his_cx;
3190             distance = ((dir == OB_DIRECTION_NORTH ||
3191                          dir == OB_DIRECTION_NORTHEAST) ?
3192                         -his_cy : his_cy);
3193             break;
3194         case OB_DIRECTION_EAST:
3195         case OB_DIRECTION_WEST:
3196         case OB_DIRECTION_SOUTHEAST:
3197         case OB_DIRECTION_NORTHWEST:
3198             offset = (his_cy < 0) ? -his_cy : his_cy;
3199             distance = ((dir == OB_DIRECTION_WEST ||
3200                          dir == OB_DIRECTION_NORTHWEST) ?
3201                         -his_cx : his_cx);
3202             break;
3203         }
3204
3205         /* the target must be in the requested direction */
3206         if(distance <= 0)
3207             continue;
3208
3209         /* Calculate score for this window.  The smaller the better. */
3210         score = distance + offset;
3211
3212         /* windows more than 45 degrees off the direction are
3213          * heavily penalized and will only be chosen if nothing
3214          * else within a million pixels */
3215         if(offset > distance)
3216             score += 1000000;
3217
3218         if(best_score == -1 || score < best_score)
3219             best_client = cur,
3220                 best_score = score;
3221     }
3222
3223     return best_client;
3224 }
3225
3226 void client_set_layer(ObClient *self, gint layer)
3227 {
3228     if (layer < 0) {
3229         self->below = TRUE;
3230         self->above = FALSE;
3231     } else if (layer == 0) {
3232         self->below = self->above = FALSE;
3233     } else {
3234         self->below = FALSE;
3235         self->above = TRUE;
3236     }
3237     client_calc_layer(self);
3238     client_change_state(self); /* reflect this in the state hints */
3239 }
3240
3241 void client_set_undecorated(ObClient *self, gboolean undecorated)
3242 {
3243     if (self->undecorated != undecorated) {
3244         self->undecorated = undecorated;
3245         client_setup_decor_and_functions(self);
3246         /* Make sure the client knows it might have moved. Maybe there is a
3247          * better way of doing this so only one client_configure is sent, but
3248          * since 125 of these are sent per second when moving the window (with
3249          * user = FALSE) i doubt it matters much.
3250          */
3251         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3252                          self->area.width, self->area.height, TRUE, TRUE);
3253         client_change_state(self); /* reflect this in the state hints */
3254     }
3255 }
3256
3257 guint client_monitor(ObClient *self)
3258 {
3259     return screen_find_monitor(&self->frame->area);
3260 }
3261
3262 ObClient *client_search_top_parent(ObClient *self)
3263 {
3264     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3265         self = self->transient_for;
3266     return self;
3267 }
3268
3269 GSList *client_search_all_top_parents(ObClient *self)
3270 {
3271     GSList *ret = NULL;
3272
3273     /* move up the direct transient chain as far as possible */
3274     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3275         self = self->transient_for;
3276
3277     if (!self->transient_for)
3278         ret = g_slist_prepend(ret, self);
3279     else {
3280             GSList *it;
3281
3282             g_assert(self->group);
3283
3284             for (it = self->group->members; it; it = g_slist_next(it)) {
3285                 ObClient *c = it->data;
3286
3287                 if (!c->transient_for)
3288                     ret = g_slist_prepend(ret, c);
3289             }
3290
3291             if (ret == NULL) /* no group parents */
3292                 ret = g_slist_prepend(ret, self);
3293     }
3294
3295     return ret;
3296 }
3297
3298 ObClient *client_search_focus_parent(ObClient *self)
3299 {
3300     if (self->transient_for) {
3301         if (self->transient_for != OB_TRAN_GROUP) {
3302             if (client_focused(self->transient_for))
3303                 return self->transient_for;
3304         } else {
3305             GSList *it;
3306
3307             for (it = self->group->members; it; it = g_slist_next(it)) {
3308                 ObClient *c = it->data;
3309
3310                 /* checking transient_for prevents infinate loops! */
3311                 if (c != self && !c->transient_for)
3312                     if (client_focused(c))
3313                         return c;
3314             }
3315         }
3316     }
3317
3318     return NULL;
3319 }
3320
3321 ObClient *client_search_parent(ObClient *self, ObClient *search)
3322 {
3323     if (self->transient_for) {
3324         if (self->transient_for != OB_TRAN_GROUP) {
3325             if (self->transient_for == search)
3326                 return search;
3327         } else {
3328             GSList *it;
3329
3330             for (it = self->group->members; it; it = g_slist_next(it)) {
3331                 ObClient *c = it->data;
3332
3333                 /* checking transient_for prevents infinate loops! */
3334                 if (c != self && !c->transient_for)
3335                     if (c == search)
3336                         return search;
3337             }
3338         }
3339     }
3340
3341     return NULL;
3342 }
3343
3344 ObClient *client_search_transient(ObClient *self, ObClient *search)
3345 {
3346     GSList *sit;
3347
3348     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3349         if (sit->data == search)
3350             return search;
3351         if (client_search_transient(sit->data, search))
3352             return search;
3353     }
3354     return NULL;
3355 }
3356
3357 void client_update_sm_client_id(ObClient *self)
3358 {
3359     g_free(self->sm_client_id);
3360     self->sm_client_id = NULL;
3361
3362     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3363         self->group)
3364         PROP_GETS(self->group->leader, sm_client_id, locale,
3365                   &self->sm_client_id);
3366 }
3367
3368 #define WANT_EDGE(cur, c) \
3369             if(cur == c)                                                      \
3370                 continue;                                                     \
3371             if(!client_normal(cur))                                   \
3372                 continue;                                                     \
3373             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3374                 continue;                                                     \
3375             if(cur->iconic)                                                   \
3376                 continue;                                                     \
3377             if(cur->layer < c->layer && !config_resist_layers_below)          \
3378                 continue;
3379
3380 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3381             if ((his_edge_start >= my_edge_start && \
3382                  his_edge_start <= my_edge_end) ||  \
3383                 (my_edge_start >= his_edge_start && \
3384                  my_edge_start <= his_edge_end))    \
3385                 dest = his_offset;
3386
3387 /* finds the nearest edge in the given direction from the current client
3388  * note to self: the edge is the -frame- edge (the actual one), not the
3389  * client edge.
3390  */
3391 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3392 {
3393     gint dest, monitor_dest;
3394     gint my_edge_start, my_edge_end, my_offset;
3395     GList *it;
3396     Rect *a, *monitor;
3397     
3398     if(!client_list)
3399         return -1;
3400
3401     a = screen_area(c->desktop);
3402     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3403
3404     switch(dir) {
3405     case OB_DIRECTION_NORTH:
3406         my_edge_start = c->frame->area.x;
3407         my_edge_end = c->frame->area.x + c->frame->area.width;
3408         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3409         
3410         /* default: top of screen */
3411         dest = a->y + (hang ? c->frame->area.height : 0);
3412         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3413         /* if the monitor edge comes before the screen edge, */
3414         /* use that as the destination instead. (For xinerama) */
3415         if (monitor_dest != dest && my_offset > monitor_dest)
3416             dest = monitor_dest; 
3417
3418         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3419             gint his_edge_start, his_edge_end, his_offset;
3420             ObClient *cur = it->data;
3421
3422             WANT_EDGE(cur, c)
3423
3424             his_edge_start = cur->frame->area.x;
3425             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3426             his_offset = cur->frame->area.y + 
3427                          (hang ? 0 : cur->frame->area.height);
3428
3429             if(his_offset + 1 > my_offset)
3430                 continue;
3431
3432             if(his_offset < dest)
3433                 continue;
3434
3435             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3436         }
3437         break;
3438     case OB_DIRECTION_SOUTH:
3439         my_edge_start = c->frame->area.x;
3440         my_edge_end = c->frame->area.x + c->frame->area.width;
3441         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3442
3443         /* default: bottom of screen */
3444         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3445         monitor_dest = monitor->y + monitor->height -
3446                        (hang ? c->frame->area.height : 0);
3447         /* if the monitor edge comes before the screen edge, */
3448         /* use that as the destination instead. (For xinerama) */
3449         if (monitor_dest != dest && my_offset < monitor_dest)
3450             dest = monitor_dest; 
3451
3452         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3453             gint his_edge_start, his_edge_end, his_offset;
3454             ObClient *cur = it->data;
3455
3456             WANT_EDGE(cur, c)
3457
3458             his_edge_start = cur->frame->area.x;
3459             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3460             his_offset = cur->frame->area.y +
3461                          (hang ? cur->frame->area.height : 0);
3462
3463
3464             if(his_offset - 1 < my_offset)
3465                 continue;
3466             
3467             if(his_offset > dest)
3468                 continue;
3469
3470             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3471         }
3472         break;
3473     case OB_DIRECTION_WEST:
3474         my_edge_start = c->frame->area.y;
3475         my_edge_end = c->frame->area.y + c->frame->area.height;
3476         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3477
3478         /* default: leftmost egde of screen */
3479         dest = a->x + (hang ? c->frame->area.width : 0);
3480         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3481         /* if the monitor edge comes before the screen edge, */
3482         /* use that as the destination instead. (For xinerama) */
3483         if (monitor_dest != dest && my_offset > monitor_dest)
3484             dest = monitor_dest;            
3485
3486         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3487             gint his_edge_start, his_edge_end, his_offset;
3488             ObClient *cur = it->data;
3489
3490             WANT_EDGE(cur, c)
3491
3492             his_edge_start = cur->frame->area.y;
3493             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3494             his_offset = cur->frame->area.x +
3495                          (hang ? 0 : cur->frame->area.width);
3496
3497             if(his_offset + 1 > my_offset)
3498                 continue;
3499
3500             if(his_offset < dest)
3501                 continue;
3502
3503             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3504         }
3505        break;
3506     case OB_DIRECTION_EAST:
3507         my_edge_start = c->frame->area.y;
3508         my_edge_end = c->frame->area.y + c->frame->area.height;
3509         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3510         
3511         /* default: rightmost edge of screen */
3512         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3513         monitor_dest = monitor->x + monitor->width -
3514                        (hang ? c->frame->area.width : 0);
3515         /* if the monitor edge comes before the screen edge, */
3516         /* use that as the destination instead. (For xinerama) */
3517         if (monitor_dest != dest && my_offset < monitor_dest)
3518             dest = monitor_dest;            
3519
3520         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3521             gint his_edge_start, his_edge_end, his_offset;
3522             ObClient *cur = it->data;
3523
3524             WANT_EDGE(cur, c)
3525
3526             his_edge_start = cur->frame->area.y;
3527             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3528             his_offset = cur->frame->area.x +
3529                          (hang ? cur->frame->area.width : 0);
3530
3531             if(his_offset - 1 < my_offset)
3532                 continue;
3533             
3534             if(his_offset > dest)
3535                 continue;
3536
3537             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3538         }
3539         break;
3540     case OB_DIRECTION_NORTHEAST:
3541     case OB_DIRECTION_SOUTHEAST:
3542     case OB_DIRECTION_NORTHWEST:
3543     case OB_DIRECTION_SOUTHWEST:
3544         /* not implemented */
3545     default:
3546         g_assert_not_reached();
3547         dest = 0; /* suppress warning */
3548     }
3549     return dest;
3550 }
3551
3552 ObClient* client_under_pointer()
3553 {
3554     gint x, y;
3555     GList *it;
3556     ObClient *ret = NULL;
3557
3558     if (screen_pointer_pos(&x, &y)) {
3559         for (it = stacking_list; it; it = g_list_next(it)) {
3560             if (WINDOW_IS_CLIENT(it->data)) {
3561                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3562                 if (c->frame->visible &&
3563                     RECT_CONTAINS(c->frame->area, x, y)) {
3564                     ret = c;
3565                     break;
3566                 }
3567             }
3568         }
3569     }
3570     return ret;
3571 }
3572
3573 gboolean client_has_group_siblings(ObClient *self)
3574 {
3575     return self->group && self->group->members->next;
3576 }