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