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