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