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