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