]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
let transients take focus when anything in the transient tree _or the group_ is focused
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-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 "propwin.h"
36 #include "stacking.h"
37 #include "openbox.h"
38 #include "group.h"
39 #include "config.h"
40 #include "menuframe.h"
41 #include "keyboard.h"
42 #include "mouse.h"
43 #include "render/render.h"
44
45 #ifdef HAVE_UNISTD_H
46 #  include <unistd.h>
47 #endif
48
49 #include <glib.h>
50 #include <X11/Xutil.h>
51
52 /*! The event mask to grab on client windows */
53 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | \
54                           ColormapChangeMask)
55
56 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
57                                 ButtonMotionMask)
58
59 typedef struct
60 {
61     ObClientCallback func;
62     gpointer data;
63 } ClientCallback;
64
65 GList            *client_list          = NULL;
66
67 static GSList *client_destroy_notifies = NULL;
68
69 static void client_get_all(ObClient *self, gboolean real);
70 static void client_get_startup_id(ObClient *self);
71 static void client_get_session_ids(ObClient *self);
72 static void client_get_area(ObClient *self);
73 static void client_get_desktop(ObClient *self);
74 static void client_get_state(ObClient *self);
75 static void client_get_shaped(ObClient *self);
76 static void client_get_mwm_hints(ObClient *self);
77 static void client_get_colormap(ObClient *self);
78 static void client_change_allowed_actions(ObClient *self);
79 static void client_change_state(ObClient *self);
80 static void client_change_wm_state(ObClient *self);
81 static void client_apply_startup_state(ObClient *self,
82                                        gint x, gint y, gint w, gint h);
83 static void client_restore_session_state(ObClient *self);
84 static gboolean client_restore_session_stacking(ObClient *self);
85 static ObAppSettings *client_get_settings_state(ObClient *self);
86 static void client_update_transient_tree(ObClient *self,
87                                          ObGroup *oldgroup, ObGroup *newgroup,
88                                          ObClient* oldparent,
89                                          ObClient *newparent);
90 static void client_present(ObClient *self, gboolean here, gboolean raise);
91 static GSList *client_search_all_top_parents_internal(ObClient *self,
92                                                       gboolean bylayer,
93                                                       ObStackingLayer layer);
94 static void client_call_notifies(ObClient *self, GSList *list);
95
96 void client_startup(gboolean reconfig)
97 {
98     if (reconfig) return;
99
100     client_set_list();
101 }
102
103 void client_shutdown(gboolean reconfig)
104 {
105     if (reconfig) return;
106 }
107
108 static void client_call_notifies(ObClient *self, GSList *list)
109 {
110     GSList *it;
111
112     for (it = list; it; it = g_slist_next(it)) {
113         ClientCallback *d = it->data;
114         d->func(self, d->data);
115     }
116 }
117
118 void client_add_destroy_notify(ObClientCallback func, gpointer data)
119 {
120     ClientCallback *d = g_new(ClientCallback, 1);
121     d->func = func;
122     d->data = data;
123     client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d);
124 }
125
126 void client_remove_destroy_notify(ObClientCallback func)
127 {
128     GSList *it;
129
130     for (it = client_destroy_notifies; it; it = g_slist_next(it)) {
131         ClientCallback *d = it->data;
132         if (d->func == func) {
133             g_free(d);
134             client_destroy_notifies =
135                 g_slist_delete_link(client_destroy_notifies, it);
136             break;
137         }
138     }
139 }
140
141 void client_set_list()
142 {
143     Window *windows, *win_it;
144     GList *it;
145     guint size = g_list_length(client_list);
146
147     /* create an array of the window ids */
148     if (size > 0) {
149         windows = g_new(Window, size);
150         win_it = windows;
151         for (it = client_list; it; it = g_list_next(it), ++win_it)
152             *win_it = ((ObClient*)it->data)->window;
153     } else
154         windows = NULL;
155
156     PROP_SETA32(RootWindow(ob_display, ob_screen),
157                 net_client_list, window, (gulong*)windows, size);
158
159     if (windows)
160         g_free(windows);
161
162     stacking_set_list();
163 }
164
165 /*
166   void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
167   {
168   GSList *it;
169
170   for (it = self->transients; it; it = g_slist_next(it)) {
171   if (!func(it->data, data)) return;
172   client_foreach_transient(it->data, func, data);
173   }
174   }
175
176   void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
177   {
178   if (self->transient_for) {
179   if (self->transient_for != OB_TRAN_GROUP) {
180   if (!func(self->transient_for, data)) return;
181   client_foreach_ancestor(self->transient_for, func, data);
182   } else {
183   GSList *it;
184
185   for (it = self->group->members; it; it = g_slist_next(it))
186   if (it->data != self &&
187   !((ObClient*)it->data)->transient_for) {
188   if (!func(it->data, data)) return;
189   client_foreach_ancestor(it->data, func, data);
190   }
191   }
192   }
193   }
194 */
195
196 void client_manage_all()
197 {
198     guint i, j, nchild;
199     Window w, *children;
200     XWMHints *wmhints;
201     XWindowAttributes attrib;
202
203     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
204                &w, &w, &children, &nchild);
205
206     /* remove all icon windows from the list */
207     for (i = 0; i < nchild; i++) {
208         if (children[i] == None) continue;
209         wmhints = XGetWMHints(ob_display, children[i]);
210         if (wmhints) {
211             if ((wmhints->flags & IconWindowHint) &&
212                 (wmhints->icon_window != children[i]))
213                 for (j = 0; j < nchild; j++)
214                     if (children[j] == wmhints->icon_window) {
215                         children[j] = None;
216                         break;
217                     }
218             XFree(wmhints);
219         }
220     }
221
222     for (i = 0; i < nchild; ++i) {
223         if (children[i] == None)
224             continue;
225         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
226             if (attrib.override_redirect) continue;
227
228             if (attrib.map_state != IsUnmapped)
229                 client_manage(children[i]);
230         }
231     }
232     XFree(children);
233 }
234
235 void client_manage(Window window)
236 {
237     ObClient *self;
238     XEvent e;
239     XWindowAttributes attrib;
240     XSetWindowAttributes attrib_set;
241     XWMHints *wmhint;
242     gboolean activate = FALSE;
243     ObAppSettings *settings;
244     gint placex, placey, placew, placeh;
245     gboolean transient = FALSE;
246
247     grab_server(TRUE);
248
249     /* check if it has already been unmapped by the time we started
250        mapping. the grab does a sync so we don't have to here */
251     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
252         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e))
253     {
254         XPutBackEvent(ob_display, &e);
255
256         ob_debug("Trying to manage unmapped window. Aborting that.\n");
257         grab_server(FALSE);
258         return; /* don't manage it */
259     }
260
261     /* make sure it isn't an override-redirect window */
262     if (!XGetWindowAttributes(ob_display, window, &attrib) ||
263         attrib.override_redirect)
264     {
265         grab_server(FALSE);
266         return; /* don't manage it */
267     }
268   
269     /* is the window a docking app */
270     if ((wmhint = XGetWMHints(ob_display, window))) {
271         if ((wmhint->flags & StateHint) &&
272             wmhint->initial_state == WithdrawnState)
273         {
274             dock_add(window, wmhint);
275             grab_server(FALSE);
276             XFree(wmhint);
277             return;
278         }
279         XFree(wmhint);
280     }
281
282     ob_debug("Managing window: 0x%lx\n", window);
283
284     /* choose the events we want to receive on the CLIENT window */
285     attrib_set.event_mask = CLIENT_EVENTMASK;
286     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
287     XChangeWindowAttributes(ob_display, window,
288                             CWEventMask|CWDontPropagate, &attrib_set);
289
290     /* create the ObClient struct, and populate it from the hints on the
291        window */
292     self = g_new0(ObClient, 1);
293     self->obwin.type = Window_Client;
294     self->window = window;
295
296     /* non-zero defaults */
297     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
298     self->gravity = NorthWestGravity;
299     self->desktop = screen_num_desktops; /* always an invalid value */
300     self->user_time = focus_client ? focus_client->user_time : CurrentTime;
301
302     /* get all the stuff off the window */
303     client_get_all(self, TRUE);
304
305     /* specify that if we exit, the window should not be destroyed and
306        should be reparented back to root automatically */
307     XChangeSaveSet(ob_display, window, SetModeInsert);
308
309     /* create the decoration frame for the client window */
310     self->frame = frame_new(self);
311
312     frame_grab_client(self->frame);
313
314     /* we've grabbed everything and set everything that we need to at mapping
315        time now */
316     grab_server(FALSE);
317
318     /* per-app settings override stuff from client_get_all, and return the
319        settings for other uses too. the returned settings is a shallow copy,
320        that needs to be freed with g_free(). */
321     settings = client_get_settings_state(self);
322     /* the session should get the last say though */
323     client_restore_session_state(self);
324
325     /* now we have all of the window's information so we can set this up */
326     client_setup_decor_and_functions(self, FALSE);
327
328     {
329         Time t = sn_app_started(self->startup_id, self->class);
330         if (t) self->user_time = t;
331     }
332
333     /* do this after we have a frame.. it uses the frame to help determine the
334        WM_STATE to apply. */
335     client_change_state(self);
336
337     /* add ourselves to the focus order */
338     focus_order_add_new(self);
339
340     /* do this to add ourselves to the stacking list in a non-intrusive way */
341     client_calc_layer(self);
342
343     /* focus the new window? */
344     if (ob_state() != OB_STATE_STARTING &&
345         (!self->session || self->session->focused) &&
346         !self->iconic &&
347         /* this means focus=true for window is same as config_focus_new=true */
348         ((config_focus_new || (settings && settings->focus == 1)) ||
349          client_search_focus_tree_full(self)) &&
350         /* this checks for focus=false for the window */
351         (!settings || settings->focus != 0) &&
352         /* note the check against type Normal/Dialog/Utility,
353            not client_normal(self), which would also include other types.
354            in this case we want more strict rules for focus */
355         (self->type == OB_CLIENT_TYPE_NORMAL ||
356          self->type == OB_CLIENT_TYPE_UTILITY ||
357          self->type == OB_CLIENT_TYPE_DIALOG))
358     {
359         activate = TRUE;
360     }
361
362     /* remove the client's border */
363     XSetWindowBorderWidth(ob_display, self->window, 0);
364
365     /* adjust the frame to the client's size before showing or placing
366        the window */
367     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
368     frame_adjust_client_area(self->frame);
369
370     /* where the frame was placed is where the window was originally */
371     placex = self->area.x;
372     placey = self->area.y;
373     placew = self->area.width;
374     placeh = self->area.height;
375
376     /* figure out placement for the window if the window is new */
377     if (ob_state() == OB_STATE_RUNNING) {
378         ob_debug("Positioned: %s @ %d %d\n",
379                  (!self->positioned ? "no" :
380                   (self->positioned == PPosition ? "program specified" :
381                    (self->positioned == USPosition ? "user specified" :
382                     (self->positioned == (PPosition | USPosition) ?
383                      "program + user specified" :
384                      "BADNESS !?")))), placex, placey);
385
386         ob_debug("Sized: %s @ %d %d\n",
387                  (!self->sized ? "no" :
388                   (self->sized == PSize ? "program specified" :
389                    (self->sized == USSize ? "user specified" :
390                     (self->sized == (PSize | USSize) ?
391                      "program + user specified" :
392                      "BADNESS !?")))), placew, placeh);
393
394         /* splash screens are also returned as TRUE for transient,
395            and so will be forced on screen below */
396         transient = place_client(self, &placex, &placey, settings);
397
398         /* make sure the window is visible. */
399         client_find_onscreen(self, &placex, &placey, placew, placeh,
400                              /* non-normal clients has less rules, and
401                                 windows that are being restored from a
402                                 session do also. we can assume you want
403                                 it back where you saved it. Clients saying
404                                 they placed themselves are subjected to
405                                 harder rules, ones that are placed by
406                                 place.c or by the user are allowed partially
407                                 off-screen and on xinerama divides (ie,
408                                 it is up to the placement routines to avoid
409                                 the xinerama divides)
410
411                                 splash screens get "transient" set to TRUE by
412                                 the place_client call
413                              */
414                              ob_state() == OB_STATE_RUNNING &&
415                              (transient ||
416                               (!(self->positioned & USPosition) &&
417                                client_normal(self) &&
418                                !self->session)));
419     }
420
421     /* if the window isn't user-sized, then make it fit inside
422        the visible screen area on its monitor. Use basically the same rules
423        for forcing the window on screen in the client_find_onscreen call.
424
425        do this after place_client, it chooses the monitor!
426
427        splash screens get "transient" set to TRUE by
428        the place_client call
429     */
430     if (ob_state() == OB_STATE_RUNNING &&
431         (transient ||
432          (!(self->sized & USSize) &&
433           client_normal(self) &&
434           !self->session)))
435     {
436         /* make a copy to modify */
437         Rect a = *screen_area_monitor(self->desktop, client_monitor(self));
438
439         /* shrink by the frame's area */
440         a.width -= self->frame->size.left + self->frame->size.right;
441         a.height -= self->frame->size.top + self->frame->size.bottom;
442
443         /* fit the window inside the area */
444         if (placew > a.width || self->area.height > a.height) {
445             placew = MIN(self->area.width, a.width);
446             placeh = MIN(self->area.height, a.height);
447
448             ob_debug("setting window size to %dx%d\n",
449                      self->area.width, self->area.height);
450         }
451     }
452
453
454     ob_debug("placing window 0x%x at %d, %d with size %d x %d. "
455              "some restrictions may apply\n",
456              self->window, placex, placey, placew, placeh);
457     if (self->session)
458         ob_debug("  but session requested %d, %d  %d x %d instead, "
459                  "overriding\n",
460                  self->session->x, self->session->y,
461                  self->session->w, self->session->h);
462
463     /* do this after the window is placed, so the premax/prefullscreen numbers
464        won't be all wacko!!
465
466        this also places the window
467     */
468     client_apply_startup_state(self, placex, placey, placew, placeh);
469
470     if (activate) {
471         guint32 last_time = focus_client ?
472             focus_client->user_time : CurrentTime;
473
474         /* This is focus stealing prevention */
475         ob_debug_type(OB_DEBUG_FOCUS,
476                       "Want to focus new window 0x%x with time %u "
477                       "(last time %u)\n",
478                       self->window, self->user_time, last_time);
479
480         /* if it's on another desktop */
481         if (!(self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
482             && /* the timestamp is from before you changed desktops */
483             self->user_time && screen_desktop_user_time &&
484             !event_time_after(self->user_time, screen_desktop_user_time))
485         {
486             activate = FALSE;
487             ob_debug_type(OB_DEBUG_FOCUS,
488                           "Not focusing the window because its on another "
489                           "desktop\n");
490         }
491         /* If something is focused, and it's not our relative... */
492         else if (focus_client && client_search_focus_tree_full(self) == NULL)
493         {
494             /* If time stamp is old, don't steal focus */
495             if (self->user_time && last_time &&
496                 !event_time_after(self->user_time, last_time))
497             {
498                 activate = FALSE;
499                 ob_debug_type(OB_DEBUG_FOCUS,
500                               "Not focusing the window because the time is "
501                               "too old\n");
502             }
503             /* If its a transient (and parents aren't focused) and the time
504                is ambiguous (either the current focus target doesn't have
505                a timestamp, or they are the same (we probably inherited it
506                from them) */
507             else if (client_has_parent(self) &&
508                      (!last_time || self->user_time == last_time))
509             {
510                 activate = FALSE;
511                 ob_debug_type(OB_DEBUG_FOCUS,
512                               "Not focusing the window because it is a "
513                               "transient, and the time is very ambiguous\n");
514             }
515             /* Don't steal focus from globally active clients.
516                I stole this idea from KWin. It seems nice.
517              */
518             else if (!(focus_client->can_focus ||
519                        focus_client->focus_notify))
520             {
521                 activate = FALSE;
522                 ob_debug_type(OB_DEBUG_FOCUS,
523                               "Not focusing the window because a globally "
524                               "active client has focus\n");
525             }
526             /* Don't move focus if it's not going to go to this window
527                anyway */
528             else if (client_focus_target(self) != self) {
529                 activate = FALSE;
530                 ob_debug_type(OB_DEBUG_FOCUS,
531                               "Not focusing the window because another window "
532                               "would get the focus anyway\n");
533             }
534         }
535
536         if (!activate) {
537             ob_debug_type(OB_DEBUG_FOCUS,
538                           "Focus stealing prevention activated for %s with "
539                           "time %u (last time %u)\n",
540                           self->title, self->user_time, last_time);
541             /* if the client isn't focused, then hilite it so the user
542                knows it is there */
543             client_hilite(self, TRUE);
544         }
545     }
546     else {
547         /* This may look rather odd. Well it's because new windows are added
548            to the stacking order non-intrusively. If we're not going to focus
549            the new window or hilite it, then we raise it to the top. This will
550            take affect for things that don't get focused like splash screens.
551            Also if you don't have focus_new enabled, then it's going to get
552            raised to the top. Legacy begets legacy I guess?
553         */
554         if (!client_restore_session_stacking(self))
555             stacking_raise(CLIENT_AS_WINDOW(self));
556     }
557
558     mouse_grab_for_client(self, TRUE);
559
560     /* this has to happen before we try focus the window, but we want it to
561        happen after the client's stacking has been determined or it looks bad
562     */
563     client_show(self);
564
565     if (activate) {
566         gboolean stacked = client_restore_session_stacking(self);
567         client_present(self, FALSE, !stacked);
568     }
569
570     /* add to client list/map */
571     client_list = g_list_append(client_list, self);
572     g_hash_table_insert(window_map, &self->window, self);
573
574     /* this has to happen after we're in the client_list */
575     if (STRUT_EXISTS(self->strut))
576         screen_update_areas();
577
578     /* update the list hints */
579     client_set_list();
580
581     /* free the ObAppSettings shallow copy */
582     g_free(settings);
583
584     ob_debug("Managed window 0x%lx plate 0x%x (%s)\n",
585              window, self->frame->window, self->class);
586
587     return;
588 }
589
590
591 ObClient *client_fake_manage(Window window)
592 {
593     ObClient *self;
594     ObAppSettings *settings;
595
596     ob_debug("Pretend-managing window: %lx\n", window);
597
598     /* do this minimal stuff to figure out the client's decorations */
599
600     self = g_new0(ObClient, 1);
601     self->window = window;
602
603     client_get_all(self, FALSE);
604     /* per-app settings override stuff, and return the settings for other
605        uses too. this returns a shallow copy that needs to be freed */
606     settings = client_get_settings_state(self);
607
608     client_setup_decor_and_functions(self, FALSE);
609
610     /* create the decoration frame for the client window and adjust its size */
611     self->frame = frame_new(self);
612     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
613
614     ob_debug("gave extents left %d right %d top %d bottom %d\n",
615              self->frame->size.left, self->frame->size.right, 
616              self->frame->size.top, self->frame->size.bottom);
617
618     /* free the ObAppSettings shallow copy */
619     g_free(settings);
620
621     return self;
622 }
623
624 void client_unmanage_all()
625 {
626     while (client_list != NULL)
627         client_unmanage(client_list->data);
628 }
629
630 void client_unmanage(ObClient *self)
631 {
632     guint j;
633     GSList *it;
634     gulong ignore_start;
635
636     ob_debug("Unmanaging window: 0x%x plate 0x%x (%s) (%s)\n",
637              self->window, self->frame->window,
638              self->class, self->title ? self->title : "");
639
640     g_assert(self != NULL);
641
642     /* we dont want events no more. do this before hiding the frame so we
643        don't generate more events */
644     XSelectInput(ob_display, self->window, NoEventMask);
645
646     /* ignore enter events from the unmap so it doesnt mess with the focus */
647     if (!client_focused(self) || !config_focus_under_mouse)
648         ignore_start = event_start_ignore_all_enters();
649
650     frame_hide(self->frame);
651     /* flush to send the hide to the server quickly */
652     XFlush(ob_display);
653
654     if (!client_focused(self) || !config_focus_under_mouse)
655         event_end_ignore_all_enters(ignore_start);
656
657     mouse_grab_for_client(self, FALSE);
658
659     /* remove the window from our save set */
660     XChangeSaveSet(ob_display, self->window, SetModeDelete);
661
662     /* kill the property windows */
663     propwin_remove(self->user_time_window, OB_PROPWIN_USER_TIME, self);
664
665     /* update the focus lists */
666     focus_order_remove(self);
667     if (client_focused(self)) {
668         /* don't leave an invalid focus_client */
669         focus_client = NULL;
670     }
671
672     client_list = g_list_remove(client_list, self);
673     stacking_remove(self);
674     g_hash_table_remove(window_map, &self->window);
675
676     /* once the client is out of the list, update the struts to remove its
677        influence */
678     if (STRUT_EXISTS(self->strut))
679         screen_update_areas();
680
681     client_call_notifies(self, client_destroy_notifies);
682
683     /* tell our parent(s) that we're gone */
684     if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
685         for (it = self->group->members; it; it = g_slist_next(it))
686             if (it->data != self)
687                 ((ObClient*)it->data)->transients =
688                     g_slist_remove(((ObClient*)it->data)->transients,self);
689     } else if (self->transient_for) {        /* transient of window */
690         self->transient_for->transients =
691             g_slist_remove(self->transient_for->transients, self);
692     }
693
694     /* tell our transients that we're gone */
695     for (it = self->transients; it; it = g_slist_next(it)) {
696         if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
697             ((ObClient*)it->data)->transient_for = NULL;
698             client_calc_layer(it->data);
699         }
700     }
701
702     /* remove from its group */
703     if (self->group) {
704         group_remove(self->group, self);
705         self->group = NULL;
706     }
707
708     /* restore the window's original geometry so it is not lost */
709     {
710         Rect a;
711
712         a = self->area;
713
714         if (self->fullscreen)
715             a = self->pre_fullscreen_area;
716         else if (self->max_horz || self->max_vert) {
717             if (self->max_horz) {
718                 a.x = self->pre_max_area.x;
719                 a.width = self->pre_max_area.width;
720             }
721             if (self->max_vert) {
722                 a.y = self->pre_max_area.y;
723                 a.height = self->pre_max_area.height;
724             }
725         }
726
727         self->fullscreen = self->max_horz = self->max_vert = FALSE;
728         /* let it be moved and resized no matter what */
729         self->functions = OB_CLIENT_FUNC_MOVE | OB_CLIENT_FUNC_RESIZE;
730         self->decorations = 0; /* unmanaged windows have no decor */
731
732         /* give the client its border back */
733         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
734
735         client_move_resize(self, a.x, a.y, a.width, a.height);
736     }
737
738     /* reparent the window out of the frame, and free the frame */
739     frame_release_client(self->frame);
740     frame_free(self->frame);
741     self->frame = NULL;
742
743     if (ob_state() != OB_STATE_EXITING) {
744         /* these values should not be persisted across a window
745            unmapping/mapping */
746         PROP_ERASE(self->window, net_wm_desktop);
747         PROP_ERASE(self->window, net_wm_state);
748         PROP_ERASE(self->window, wm_state);
749     } else {
750         /* if we're left in an unmapped state, the client wont be mapped.
751            this is bad, since we will no longer be managing the window on
752            restart */
753         XMapWindow(ob_display, self->window);
754     }
755
756     /* update the list hints */
757     client_set_list();
758
759     ob_debug("Unmanaged window 0x%lx\n", self->window);
760
761     /* free all data allocated in the client struct */
762     g_slist_free(self->transients);
763     for (j = 0; j < self->nicons; ++j)
764         g_free(self->icons[j].data);
765     if (self->nicons > 0)
766         g_free(self->icons);
767     g_free(self->wm_command);
768     g_free(self->title);
769     g_free(self->icon_title);
770     g_free(self->name);
771     g_free(self->class);
772     g_free(self->role);
773     g_free(self->client_machine);
774     g_free(self->sm_client_id);
775     g_free(self);
776 }
777
778 void client_fake_unmanage(ObClient *self)
779 {
780     /* this is all that got allocated to get the decorations */
781
782     frame_free(self->frame);
783     g_free(self);
784 }
785
786 /*! Returns a new structure containing the per-app settings for this client.
787   The returned structure needs to be freed with g_free. */
788 static ObAppSettings *client_get_settings_state(ObClient *self)
789 {
790     ObAppSettings *settings;
791     GSList *it;
792
793     settings = config_create_app_settings();
794
795     for (it = config_per_app_settings; it; it = g_slist_next(it)) {
796         ObAppSettings *app = it->data;
797         gboolean match = TRUE;
798
799         g_assert(app->name != NULL || app->class != NULL);
800
801         /* we know that either name or class is not NULL so it will have to
802            match to use the rule */
803         if (app->name &&
804             !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
805             match = FALSE;
806         else if (app->class &&
807                 !g_pattern_match(app->class,
808                                  strlen(self->class), self->class, NULL))
809             match = FALSE;
810         else if (app->role &&
811                  !g_pattern_match(app->role,
812                                   strlen(self->role), self->role, NULL))
813             match = FALSE;
814
815         if (match) {
816             ob_debug("Window matching: %s\n", app->name);
817
818             /* copy the settings to our struct, overriding the existing
819                settings if they are not defaults */
820             config_app_settings_copy_non_defaults(app, settings);
821         }
822     }
823
824     if (settings->shade != -1)
825         self->shaded = !!settings->shade;
826     if (settings->decor != -1)
827         self->undecorated = !settings->decor;
828     if (settings->iconic != -1)
829         self->iconic = !!settings->iconic;
830     if (settings->skip_pager != -1)
831         self->skip_pager = !!settings->skip_pager;
832     if (settings->skip_taskbar != -1)
833         self->skip_taskbar = !!settings->skip_taskbar;
834
835     if (settings->max_vert != -1)
836         self->max_vert = !!settings->max_vert;
837     if (settings->max_horz != -1)
838         self->max_horz = !!settings->max_horz;
839
840     if (settings->fullscreen != -1)
841         self->fullscreen = !!settings->fullscreen;
842
843     if (settings->desktop) {
844         if (settings->desktop == DESKTOP_ALL)
845             self->desktop = settings->desktop;
846         else if (settings->desktop > 0 &&
847                  settings->desktop <= screen_num_desktops)
848             self->desktop = settings->desktop - 1;
849     }
850
851     if (settings->layer == -1) {
852         self->below = TRUE;
853         self->above = FALSE;
854     }
855     else if (settings->layer == 0) {
856         self->below = FALSE;
857         self->above = FALSE;
858     }
859     else if (settings->layer == 1) {
860         self->below = FALSE;
861         self->above = TRUE;
862     }
863     return settings;
864 }
865
866 static void client_restore_session_state(ObClient *self)
867 {
868     GList *it;
869
870     ob_debug_type(OB_DEBUG_SM,
871                   "Restore session for client %s\n", self->title);
872
873     if (!(it = session_state_find(self))) {
874         ob_debug_type(OB_DEBUG_SM,
875                       "Session data not found for client %s\n", self->title);
876         return;
877     }
878
879     self->session = it->data;
880
881     ob_debug_type(OB_DEBUG_SM, "Session data loaded for client %s\n",
882                   self->title);
883
884     RECT_SET_POINT(self->area, self->session->x, self->session->y);
885     self->positioned = USPosition;
886     self->sized = USSize;
887     if (self->session->w > 0)
888         self->area.width = self->session->w;
889     if (self->session->h > 0)
890         self->area.height = self->session->h;
891     XResizeWindow(ob_display, self->window,
892                   self->area.width, self->area.height);
893
894     self->desktop = (self->session->desktop == DESKTOP_ALL ?
895                      self->session->desktop :
896                      MIN(screen_num_desktops - 1, self->session->desktop));
897     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
898
899     self->shaded = self->session->shaded;
900     self->iconic = self->session->iconic;
901     self->skip_pager = self->session->skip_pager;
902     self->skip_taskbar = self->session->skip_taskbar;
903     self->fullscreen = self->session->fullscreen;
904     self->above = self->session->above;
905     self->below = self->session->below;
906     self->max_horz = self->session->max_horz;
907     self->max_vert = self->session->max_vert;
908     self->undecorated = self->session->undecorated;
909 }
910
911 static gboolean client_restore_session_stacking(ObClient *self)
912 {
913     GList *it, *mypos;
914
915     if (!self->session) return FALSE;
916
917     mypos = g_list_find(session_saved_state, self->session);
918     if (!mypos) return FALSE;
919
920     /* start above me and look for the first client */
921     for (it = g_list_previous(mypos); it; it = g_list_previous(it)) {
922         GList *cit;
923
924         for (cit = client_list; cit; cit = g_list_next(cit)) {
925             ObClient *c = cit->data;
926             /* found a client that was in the session, so go below it */
927             if (c->session == it->data) {
928                 stacking_below(CLIENT_AS_WINDOW(self),
929                                CLIENT_AS_WINDOW(cit->data));
930                 return TRUE;
931             }
932         }
933     }
934     return FALSE;
935 }
936
937 void client_move_onscreen(ObClient *self, gboolean rude)
938 {
939     gint x = self->area.x;
940     gint y = self->area.y;
941     if (client_find_onscreen(self, &x, &y,
942                              self->area.width,
943                              self->area.height, rude)) {
944         client_move(self, x, y);
945     }
946 }
947
948 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
949                               gboolean rude)
950 {
951     Rect *mon_a, *all_a;
952     gint ox = *x, oy = *y;
953     gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude;
954     gint fw, fh;
955     Rect desired;
956
957     RECT_SET(desired, *x, *y, w, h);
958     all_a = screen_area(self->desktop);
959     mon_a = screen_area_monitor(self->desktop, screen_find_monitor(&desired));
960
961     /* get where the frame would be */
962     frame_client_gravity(self->frame, x, y, w, h);
963
964     /* get the requested size of the window with decorations */
965     fw = self->frame->size.left + w + self->frame->size.right;
966     fh = self->frame->size.top + h + self->frame->size.bottom;
967
968     /* This makes sure windows aren't entirely outside of the screen so you
969        can't see them at all.
970        It makes sure 10% of the window is on the screen at least. At don't let
971        it move itself off the top of the screen, which would hide the titlebar
972        on you. (The user can still do this if they want too, it's only limiting
973        the application.
974
975        XXX watch for xinerama dead areas...
976     */
977     if (client_normal(self)) {
978         if (!self->strut.right && *x + fw/10 >= all_a->x + all_a->width - 1)
979             *x = all_a->x + all_a->width - fw/10;
980         if (!self->strut.bottom && *y + fh/10 >= all_a->y + all_a->height - 1)
981             *y = all_a->y + all_a->height - fh/10;
982         if (!self->strut.left && *x + fw*9/10 - 1 < all_a->x)
983             *x = all_a->x - fw*9/10;
984         if (!self->strut.top && *y + fh*9/10 - 1 < all_a->y)
985             *y = all_a->y - fw*9/10;
986     }
987
988     /* If rudeness wasn't requested, then figure out of the client is currently
989        entirely on the screen. If it is, and the position isn't changing by
990        request, and it is enlarging, then be rude even though it wasn't
991        requested */
992     if (!rude) {
993         Point oldtl, oldtr, oldbl, oldbr;
994         Point newtl, newtr, newbl, newbr;
995         gboolean stationary_l, stationary_r, stationary_t, stationary_b;
996
997         POINT_SET(oldtl, self->frame->area.x, self->frame->area.y);
998         POINT_SET(oldbr, self->frame->area.x + self->frame->area.width - 1,
999                   self->frame->area.y + self->frame->area.height - 1);
1000         POINT_SET(oldtr, oldbr.x, oldtl.y);
1001         POINT_SET(oldbl, oldtl.x, oldbr.y);
1002
1003         POINT_SET(newtl, *x, *y);
1004         POINT_SET(newbr, *x + fw - 1, *y + fh - 1);
1005         POINT_SET(newtr, newbr.x, newtl.y);
1006         POINT_SET(newbl, newtl.x, newbr.y);
1007
1008         /* is it moving or just resizing from some corner? */
1009         stationary_l = oldtl.x == newtl.x;
1010         stationary_r = oldtr.x == newtr.x;
1011         stationary_t = oldtl.y == newtl.y;
1012         stationary_b = oldbl.y == newbl.y;
1013
1014         /* if left edge is growing and didnt move right edge */
1015         if (stationary_r && newtl.x < oldtl.x)
1016             rudel = TRUE;
1017         /* if right edge is growing and didnt move left edge */
1018         if (stationary_l && newtr.x > oldtr.x)
1019             ruder = TRUE;
1020         /* if top edge is growing and didnt move bottom edge */
1021         if (stationary_b && newtl.y < oldtl.y)
1022             rudet = TRUE;
1023         /* if bottom edge is growing and didnt move top edge */
1024         if (stationary_t && newbl.y > oldbl.y)
1025             rudeb = TRUE;
1026     }
1027
1028     /* This here doesn't let windows even a pixel outside the struts/screen.
1029      * When called from client_manage, programs placing themselves are
1030      * forced completely onscreen, while things like
1031      * xterm -geometry resolution-width/2 will work fine. Trying to
1032      * place it completely offscreen will be handled in the above code.
1033      * Sorry for this confused comment, i am tired. */
1034     if (rudel && !self->strut.left && *x < mon_a->x) *x = mon_a->x;
1035     if (ruder && !self->strut.right && *x + fw > mon_a->x + mon_a->width)
1036         *x = mon_a->x + MAX(0, mon_a->width - fw);
1037
1038     if (rudet && !self->strut.top && *y < mon_a->y) *y = mon_a->y;
1039     if (rudeb && !self->strut.bottom && *y + fh > mon_a->y + mon_a->height)
1040         *y = mon_a->y + MAX(0, mon_a->height - fh);
1041
1042     /* get where the client should be */
1043     frame_frame_gravity(self->frame, x, y, w, h);
1044
1045     return ox != *x || oy != *y;
1046 }
1047
1048 static void client_get_all(ObClient *self, gboolean real)
1049 {
1050     /* this is needed for the frame to set itself up */
1051     client_get_area(self);
1052
1053     /* these things can change the decor and functions of the window */
1054
1055     client_get_mwm_hints(self);
1056     /* this can change the mwmhints for special cases */
1057     client_get_type_and_transientness(self);
1058     client_get_state(self);
1059     client_update_normal_hints(self);
1060
1061     /* get the session related properties, these can change decorations
1062        from per-app settings */
1063     client_get_session_ids(self);
1064
1065     /* now we got everything that can affect the decorations */
1066     if (!real)
1067         return;
1068
1069     /* get this early so we have it for debugging */
1070     client_update_title(self);
1071
1072     client_update_protocols(self);
1073
1074     client_update_wmhints(self);
1075     /* this may have already been called from client_update_wmhints */
1076     if (self->transient_for == NULL)
1077         client_update_transient_for(self);
1078
1079     client_get_startup_id(self);
1080     client_get_desktop(self);/* uses transient data/group/startup id if a
1081                                 desktop is not specified */
1082     client_get_shaped(self);
1083
1084     {
1085         /* a couple type-based defaults for new windows */
1086
1087         /* this makes sure that these windows appear on all desktops */
1088         if (self->type == OB_CLIENT_TYPE_DESKTOP)
1089             self->desktop = DESKTOP_ALL;
1090     }
1091   
1092 #ifdef SYNC
1093     client_update_sync_request_counter(self);
1094 #endif
1095
1096     client_get_colormap(self);
1097     client_update_strut(self);
1098     client_update_icons(self);
1099     client_update_user_time_window(self);
1100     if (!self->user_time_window) /* check if this would have been called */
1101         client_update_user_time(self);
1102     client_update_icon_geometry(self);
1103 }
1104
1105 static void client_get_startup_id(ObClient *self)
1106 {
1107     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
1108         if (self->group)
1109             PROP_GETS(self->group->leader,
1110                       net_startup_id, utf8, &self->startup_id);
1111 }
1112
1113 static void client_get_area(ObClient *self)
1114 {
1115     XWindowAttributes wattrib;
1116     Status ret;
1117   
1118     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1119     g_assert(ret != BadWindow);
1120
1121     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
1122     POINT_SET(self->root_pos, wattrib.x, wattrib.y);
1123     self->border_width = wattrib.border_width;
1124
1125     ob_debug("client area: %d %d  %d %d  bw %d\n", wattrib.x, wattrib.y,
1126              wattrib.width, wattrib.height, wattrib.border_width);
1127 }
1128
1129 static void client_get_desktop(ObClient *self)
1130 {
1131     guint32 d = screen_num_desktops; /* an always-invalid value */
1132
1133     if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
1134         if (d >= screen_num_desktops && d != DESKTOP_ALL)
1135             self->desktop = screen_num_desktops - 1;
1136         else
1137             self->desktop = d;
1138         ob_debug("client requested desktop 0x%x\n", self->desktop); 
1139     } else {
1140         gboolean trdesk = FALSE;
1141
1142         if (self->transient_for) {
1143             if (self->transient_for != OB_TRAN_GROUP) {
1144                 if (self->transient_for->desktop != DESKTOP_ALL) {
1145                     self->desktop = self->transient_for->desktop;
1146                     trdesk = TRUE;
1147                 }
1148             } else {
1149                 /* if all the group is on one desktop, then open it on the
1150                    same desktop */
1151                 GSList *it;
1152                 gboolean first = TRUE;
1153                 guint all = screen_num_desktops; /* not a valid value */
1154
1155                 for (it = self->group->members; it; it = g_slist_next(it)) {
1156                     ObClient *c = it->data;
1157
1158                     if (c->desktop == DESKTOP_ALL) continue;
1159
1160                     if (c != self) {
1161                         if (first) {
1162                             all = c->desktop;
1163                             first = FALSE;
1164                         }
1165                         else if (all != c->desktop)
1166                             all = screen_num_desktops; /* make it invalid */
1167                     }
1168                 }
1169                 if (all != screen_num_desktops) {
1170                     self->desktop = all;
1171                     trdesk = TRUE;
1172                 }
1173             }
1174         }
1175         if (!trdesk) {
1176             /* try get from the startup-notification protocol */
1177             if (sn_get_desktop(self->startup_id, &self->desktop)) {
1178                 if (self->desktop >= screen_num_desktops &&
1179                     self->desktop != DESKTOP_ALL)
1180                     self->desktop = screen_num_desktops - 1;
1181             } else
1182                 /* defaults to the current desktop */
1183                 self->desktop = screen_desktop;
1184         }
1185     }
1186 }
1187
1188 static void client_get_state(ObClient *self)
1189 {
1190     guint32 *state;
1191     guint num;
1192   
1193     if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
1194         gulong i;
1195         for (i = 0; i < num; ++i) {
1196             if (state[i] == prop_atoms.net_wm_state_modal)
1197                 self->modal = TRUE;
1198             else if (state[i] == prop_atoms.net_wm_state_shaded)
1199                 self->shaded = TRUE;
1200             else if (state[i] == prop_atoms.net_wm_state_hidden)
1201                 self->iconic = TRUE;
1202             else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
1203                 self->skip_taskbar = TRUE;
1204             else if (state[i] == prop_atoms.net_wm_state_skip_pager)
1205                 self->skip_pager = TRUE;
1206             else if (state[i] == prop_atoms.net_wm_state_fullscreen)
1207                 self->fullscreen = TRUE;
1208             else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
1209                 self->max_vert = TRUE;
1210             else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
1211                 self->max_horz = TRUE;
1212             else if (state[i] == prop_atoms.net_wm_state_above)
1213                 self->above = TRUE;
1214             else if (state[i] == prop_atoms.net_wm_state_below)
1215                 self->below = TRUE;
1216             else if (state[i] == prop_atoms.net_wm_state_demands_attention)
1217                 self->demands_attention = TRUE;
1218             else if (state[i] == prop_atoms.ob_wm_state_undecorated)
1219                 self->undecorated = TRUE;
1220         }
1221
1222         g_free(state);
1223     }
1224 }
1225
1226 static void client_get_shaped(ObClient *self)
1227 {
1228     self->shaped = FALSE;
1229 #ifdef   SHAPE
1230     if (extensions_shape) {
1231         gint foo;
1232         guint ufoo;
1233         gint s;
1234
1235         XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
1236
1237         XShapeQueryExtents(ob_display, self->window, &s, &foo,
1238                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1239                            &ufoo);
1240         self->shaped = (s != 0);
1241     }
1242 #endif
1243 }
1244
1245 void client_update_transient_for(ObClient *self)
1246 {
1247     Window t = None;
1248     ObClient *target = NULL;
1249
1250     if (XGetTransientForHint(ob_display, self->window, &t)) {
1251         if (t != self->window) { /* cant be transient to itself! */
1252             target = g_hash_table_lookup(window_map, &t);
1253             /* if this happens then we need to check for it*/
1254             g_assert(target != self);
1255             if (target && !WINDOW_IS_CLIENT(target)) {
1256                 /* this can happen when a dialog is a child of
1257                    a dockapp, for example */
1258                 target = NULL;
1259             }
1260
1261             /* THIS IS SO ANNOYING ! ! ! ! Let me explain.... have a seat..
1262
1263                Setting the transient_for to Root is actually illegal, however
1264                applications from time have done this to specify transient for
1265                their group.
1266
1267                Now you can do that by being a TYPE_DIALOG and not setting
1268                the transient_for hint at all on your window. But people still
1269                use Root, and Kwin is very strange in this regard.
1270
1271                KWin 3.0 will not consider windows with transient_for set to
1272                Root as transient for their group *UNLESS* they are also modal.
1273                In that case, it will make them transient for the group. This
1274                leads to all sorts of weird behavior from KDE apps which are
1275                only tested in KWin. I'd like to follow their behavior just to
1276                make this work right with KDE stuff, but that seems wrong.
1277             */
1278             if (!target && self->group) {
1279                 /* not transient to a client, see if it is transient for a
1280                    group */
1281                 if (t == RootWindow(ob_display, ob_screen)) {
1282                     /* window is a transient for its group! */
1283                     target = OB_TRAN_GROUP;
1284                 }
1285             }
1286         }
1287     } else if (self->transient && self->group)
1288         target = OB_TRAN_GROUP;
1289
1290     client_update_transient_tree(self, self->group, self->group,
1291                                  self->transient_for, target);
1292     self->transient_for = target;
1293                           
1294 }
1295
1296 static void client_update_transient_tree(ObClient *self,
1297                                          ObGroup *oldgroup, ObGroup *newgroup,
1298                                          ObClient* oldparent,
1299                                          ObClient *newparent)
1300 {
1301     GSList *it, *next;
1302     ObClient *c;
1303
1304     /* * *
1305       Group transient windows are not allowed to have other group
1306       transient windows as their children.
1307       * * */
1308
1309
1310     /* No change has occured */
1311     if (oldgroup == newgroup && oldparent == newparent) return;
1312
1313     /** Remove the client from the transient tree wherever it has changed **/
1314
1315     /* If the window is becoming a direct transient for a window in its group
1316        then any group transients which were our children and are now becoming
1317        our parents need to stop being our children.
1318
1319        Group transients can't be children of group transients already, but
1320        we could have any number of direct parents above up, any of which could
1321        be transient for the group, and we need to remove it from our children.
1322     */
1323     if (oldparent != newparent &&
1324         newparent != NULL && newparent != OB_TRAN_GROUP &&
1325         newgroup != NULL && newgroup == oldgroup)
1326     {
1327         ObClient *look = newparent;
1328         do {
1329             self->transients = g_slist_remove(self->transients, look);
1330             look = look->transient_for;
1331         } while (look != NULL && look != OB_TRAN_GROUP);
1332     }
1333             
1334
1335     /* If the group changed, or if we are just becoming transient for the
1336        group, then we need to remove any old group transient windows
1337        from our children. But if we were already transient for the group, then
1338        other group transients are not our children. */
1339     if ((oldgroup != newgroup ||
1340          (newparent == OB_TRAN_GROUP && oldparent != newparent)) &&
1341         oldgroup != NULL && oldparent != OB_TRAN_GROUP)
1342     {
1343         for (it = self->transients; it; it = next) {
1344             next = g_slist_next(it);
1345             c = it->data;
1346             if (c->group == oldgroup)
1347                 self->transients = g_slist_delete_link(self->transients, it);
1348         }
1349     }
1350
1351     /* If we used to be transient for a group and now we are not, or we're
1352        transient for a new group, then we need to remove ourselves from all
1353        our ex-parents */
1354     if (oldparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1355                                        oldparent != newparent))
1356     {
1357         for (it = oldgroup->members; it; it = g_slist_next(it)) {
1358             c = it->data;
1359             if (c != self && (!c->transient_for ||
1360                               c->transient_for != OB_TRAN_GROUP))
1361                 c->transients = g_slist_remove(c->transients, self);
1362         }
1363     }
1364     /* If we used to be transient for a single window and we are no longer
1365        transient for it, then we need to remove ourself from its children */
1366     else if (oldparent != NULL && oldparent != OB_TRAN_GROUP &&
1367              oldparent != newparent)
1368         oldparent->transients = g_slist_remove(oldparent->transients, self);
1369
1370
1371     /** Re-add the client to the transient tree wherever it has changed **/
1372
1373     /* If we're now transient for a group and we weren't transient for it
1374        before then we need to add ourselves to all our new parents */
1375     if (newparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1376                                        oldparent != newparent))
1377     {
1378         for (it = oldgroup->members; it; it = g_slist_next(it)) {
1379             c = it->data;
1380             if (c != self && (!c->transient_for ||
1381                               c->transient_for != OB_TRAN_GROUP))
1382                 c->transients = g_slist_prepend(c->transients, self);
1383         }
1384     }
1385     /* If we are now transient for a single window which we weren't before,
1386        we need to add ourselves to its children
1387
1388        WARNING: Cyclical transient ness is possible if two windows are
1389        transient for eachother.
1390     */
1391     else if (newparent != NULL && newparent != OB_TRAN_GROUP &&
1392              newparent != oldparent &&
1393              /* don't make ourself its child if it is already our child */
1394              !client_is_direct_child(self, newparent))
1395         newparent->transients = g_slist_prepend(newparent->transients, self);
1396
1397     /* If the group changed then we need to add any new group transient
1398        windows to our children. But if we're transient for the group, then
1399        other group transients are not our children.
1400
1401        WARNING: Cyclical transient-ness is possible. For e.g. if:
1402        A is transient for the group
1403        B is transient for A
1404        C is transient for B
1405        A can't be transient for C or we have a cycle
1406     */
1407     if (oldgroup != newgroup && newgroup != NULL &&
1408         newparent != OB_TRAN_GROUP)
1409     {
1410         for (it = newgroup->members; it; it = g_slist_next(it)) {
1411             c = it->data;
1412             if (c != self && c->transient_for == OB_TRAN_GROUP &&
1413                 /* Don't make it our child if it is already our parent */
1414                 !client_is_direct_child(c, self))
1415             {
1416                 self->transients = g_slist_prepend(self->transients, c);
1417             }
1418         }
1419     }
1420 }
1421
1422 static void client_get_mwm_hints(ObClient *self)
1423 {
1424     guint num;
1425     guint32 *hints;
1426
1427     self->mwmhints.flags = 0; /* default to none */
1428
1429     if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1430                     &hints, &num)) {
1431         if (num >= OB_MWM_ELEMENTS) {
1432             self->mwmhints.flags = hints[0];
1433             self->mwmhints.functions = hints[1];
1434             self->mwmhints.decorations = hints[2];
1435         }
1436         g_free(hints);
1437     }
1438 }
1439
1440 void client_get_type_and_transientness(ObClient *self)
1441 {
1442     guint num, i;
1443     guint32 *val;
1444     Window t;
1445
1446     self->type = -1;
1447     self->transient = FALSE;
1448   
1449     if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1450         /* use the first value that we know about in the array */
1451         for (i = 0; i < num; ++i) {
1452             if (val[i] == prop_atoms.net_wm_window_type_desktop)
1453                 self->type = OB_CLIENT_TYPE_DESKTOP;
1454             else if (val[i] == prop_atoms.net_wm_window_type_dock)
1455                 self->type = OB_CLIENT_TYPE_DOCK;
1456             else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1457                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1458             else if (val[i] == prop_atoms.net_wm_window_type_menu)
1459                 self->type = OB_CLIENT_TYPE_MENU;
1460             else if (val[i] == prop_atoms.net_wm_window_type_utility)
1461                 self->type = OB_CLIENT_TYPE_UTILITY;
1462             else if (val[i] == prop_atoms.net_wm_window_type_splash)
1463                 self->type = OB_CLIENT_TYPE_SPLASH;
1464             else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1465                 self->type = OB_CLIENT_TYPE_DIALOG;
1466             else if (val[i] == prop_atoms.net_wm_window_type_normal)
1467                 self->type = OB_CLIENT_TYPE_NORMAL;
1468             else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1469                 /* prevent this window from getting any decor or
1470                    functionality */
1471                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1472                                          OB_MWM_FLAG_DECORATIONS);
1473                 self->mwmhints.decorations = 0;
1474                 self->mwmhints.functions = 0;
1475             }
1476             if (self->type != (ObClientType) -1)
1477                 break; /* grab the first legit type */
1478         }
1479         g_free(val);
1480     }
1481
1482     if (XGetTransientForHint(ob_display, self->window, &t))
1483         self->transient = TRUE;
1484             
1485     if (self->type == (ObClientType) -1) {
1486         /*the window type hint was not set, which means we either classify
1487           ourself as a normal window or a dialog, depending on if we are a
1488           transient. */
1489         if (self->transient)
1490             self->type = OB_CLIENT_TYPE_DIALOG;
1491         else
1492             self->type = OB_CLIENT_TYPE_NORMAL;
1493     }
1494
1495     /* then, based on our type, we can update our transientness.. */
1496     if (self->type == OB_CLIENT_TYPE_DIALOG ||
1497         self->type == OB_CLIENT_TYPE_TOOLBAR ||
1498         self->type == OB_CLIENT_TYPE_MENU ||
1499         self->type == OB_CLIENT_TYPE_UTILITY)
1500     {
1501         self->transient = TRUE;
1502     }
1503 }
1504
1505 void client_update_protocols(ObClient *self)
1506 {
1507     guint32 *proto;
1508     guint num_return, i;
1509
1510     self->focus_notify = FALSE;
1511     self->delete_window = FALSE;
1512
1513     if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1514         for (i = 0; i < num_return; ++i) {
1515             if (proto[i] == prop_atoms.wm_delete_window)
1516                 /* this means we can request the window to close */
1517                 self->delete_window = TRUE;
1518             else if (proto[i] == prop_atoms.wm_take_focus)
1519                 /* if this protocol is requested, then the window will be
1520                    notified whenever we want it to receive focus */
1521                 self->focus_notify = TRUE;
1522 #ifdef SYNC
1523             else if (proto[i] == prop_atoms.net_wm_sync_request) 
1524                 /* if this protocol is requested, then resizing the
1525                    window will be synchronized between the frame and the
1526                    client */
1527                 self->sync_request = TRUE;
1528 #endif
1529         }
1530         g_free(proto);
1531     }
1532 }
1533
1534 #ifdef SYNC
1535 void client_update_sync_request_counter(ObClient *self)
1536 {
1537     guint32 i;
1538
1539     if (PROP_GET32(self->window, net_wm_sync_request_counter, cardinal, &i)) {
1540         self->sync_counter = i;
1541     } else
1542         self->sync_counter = None;
1543 }
1544 #endif
1545
1546 void client_get_colormap(ObClient *self)
1547 {
1548     XWindowAttributes wa;
1549
1550     if (XGetWindowAttributes(ob_display, self->window, &wa))
1551         client_update_colormap(self, wa.colormap);
1552 }
1553
1554 void client_update_colormap(ObClient *self, Colormap colormap)
1555 {
1556     if (colormap == self->colormap) return;
1557
1558     ob_debug("Setting client %s colormap: 0x%x\n", self->title, colormap);
1559
1560     if (client_focused(self)) {
1561         screen_install_colormap(self, FALSE); /* uninstall old one */
1562         self->colormap = colormap;
1563         screen_install_colormap(self, FALSE); /* install new one */
1564     } else
1565         self->colormap = colormap;
1566 }
1567
1568 void client_update_normal_hints(ObClient *self)
1569 {
1570     XSizeHints size;
1571     glong ret;
1572
1573     /* defaults */
1574     self->min_ratio = 0.0f;
1575     self->max_ratio = 0.0f;
1576     SIZE_SET(self->size_inc, 1, 1);
1577     SIZE_SET(self->base_size, 0, 0);
1578     SIZE_SET(self->min_size, 0, 0);
1579     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1580
1581     /* get the hints from the window */
1582     if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1583         /* normal windows can't request placement! har har
1584         if (!client_normal(self))
1585         */
1586         self->positioned = (size.flags & (PPosition|USPosition));
1587         self->sized = (size.flags & (PSize|USSize));
1588
1589         if (size.flags & PWinGravity)
1590             self->gravity = size.win_gravity;
1591
1592         if (size.flags & PAspect) {
1593             if (size.min_aspect.y)
1594                 self->min_ratio =
1595                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1596             if (size.max_aspect.y)
1597                 self->max_ratio =
1598                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1599         }
1600
1601         if (size.flags & PMinSize)
1602             SIZE_SET(self->min_size, size.min_width, size.min_height);
1603     
1604         if (size.flags & PMaxSize)
1605             SIZE_SET(self->max_size, size.max_width, size.max_height);
1606     
1607         if (size.flags & PBaseSize)
1608             SIZE_SET(self->base_size, size.base_width, size.base_height);
1609     
1610         if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1611             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1612     }
1613 }
1614
1615 void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
1616 {
1617     /* start with everything (cept fullscreen) */
1618     self->decorations =
1619         (OB_FRAME_DECOR_TITLEBAR |
1620          OB_FRAME_DECOR_HANDLE |
1621          OB_FRAME_DECOR_GRIPS |
1622          OB_FRAME_DECOR_BORDER |
1623          OB_FRAME_DECOR_ICON |
1624          OB_FRAME_DECOR_ALLDESKTOPS |
1625          OB_FRAME_DECOR_ICONIFY |
1626          OB_FRAME_DECOR_MAXIMIZE |
1627          OB_FRAME_DECOR_SHADE |
1628          OB_FRAME_DECOR_CLOSE);
1629     self->functions =
1630         (OB_CLIENT_FUNC_RESIZE |
1631          OB_CLIENT_FUNC_MOVE |
1632          OB_CLIENT_FUNC_ICONIFY |
1633          OB_CLIENT_FUNC_MAXIMIZE |
1634          OB_CLIENT_FUNC_SHADE |
1635          OB_CLIENT_FUNC_CLOSE |
1636          OB_CLIENT_FUNC_BELOW |
1637          OB_CLIENT_FUNC_ABOVE |
1638          OB_CLIENT_FUNC_UNDECORATE);
1639
1640     if (!(self->min_size.width < self->max_size.width ||
1641           self->min_size.height < self->max_size.height))
1642         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1643
1644     switch (self->type) {
1645     case OB_CLIENT_TYPE_NORMAL:
1646         /* normal windows retain all of the possible decorations and
1647            functionality, and are the only windows that you can fullscreen */
1648         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1649         break;
1650
1651     case OB_CLIENT_TYPE_DIALOG:
1652     case OB_CLIENT_TYPE_UTILITY:
1653         /* these windows don't have anything added or removed by default */
1654         break;
1655
1656     case OB_CLIENT_TYPE_MENU:
1657     case OB_CLIENT_TYPE_TOOLBAR:
1658         /* these windows can't iconify or maximize */
1659         self->decorations &= ~(OB_FRAME_DECOR_ICONIFY |
1660                                OB_FRAME_DECOR_MAXIMIZE);
1661         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY |
1662                              OB_CLIENT_FUNC_MAXIMIZE);
1663         break;
1664
1665     case OB_CLIENT_TYPE_SPLASH:
1666         /* these don't get get any decorations, and the only thing you can
1667            do with them is move them */
1668         self->decorations = 0;
1669         self->functions = OB_CLIENT_FUNC_MOVE;
1670         break;
1671
1672     case OB_CLIENT_TYPE_DESKTOP:
1673         /* these windows are not manipulated by the window manager */
1674         self->decorations = 0;
1675         self->functions = 0;
1676         break;
1677
1678     case OB_CLIENT_TYPE_DOCK:
1679         /* these windows are not manipulated by the window manager, but they
1680            can set below layer which has a special meaning */
1681         self->decorations = 0;
1682         self->functions = OB_CLIENT_FUNC_BELOW;
1683         break;
1684     }
1685
1686     /* Mwm Hints are applied subtractively to what has already been chosen for
1687        decor and functionality */
1688     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1689         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1690             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1691                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1692             {
1693                 /* if the mwm hints request no handle or title, then all
1694                    decorations are disabled, but keep the border if that's
1695                    specified */
1696                 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1697                     self->decorations = OB_FRAME_DECOR_BORDER;
1698                 else
1699                     self->decorations = 0;
1700             }
1701         }
1702     }
1703
1704     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1705         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1706             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1707                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1708             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1709                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1710             /* dont let mwm hints kill any buttons
1711                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1712                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1713                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1714                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1715             */
1716             /* dont let mwm hints kill the close button
1717                if (! (self->mwmhints.functions & MwmFunc_Close))
1718                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1719         }
1720     }
1721
1722     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1723         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1724     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1725         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1726     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1727         self->decorations &= ~(OB_FRAME_DECOR_GRIPS | OB_FRAME_DECOR_HANDLE);
1728
1729     /* can't maximize without moving/resizing */
1730     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1731           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1732           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1733         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1734         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1735     }
1736
1737     if (self->max_horz && self->max_vert) {
1738         /* you can't resize fully maximized windows */
1739         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1740         /* kill the handle on fully maxed windows */
1741         self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS);
1742     }
1743
1744     /* If there are no decorations to remove, don't allow the user to try
1745        toggle the state */
1746     if (self->decorations == 0)
1747         self->functions &= ~OB_CLIENT_FUNC_UNDECORATE;
1748
1749     /* finally, the user can have requested no decorations, which overrides
1750        everything (but doesnt give it a border if it doesnt have one) */
1751     if (self->undecorated) {
1752         if (config_theme_keepborder)
1753             self->decorations &= OB_FRAME_DECOR_BORDER;
1754         else
1755             self->decorations = 0;
1756     }
1757
1758     /* if we don't have a titlebar, then we cannot shade! */
1759     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1760         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1761
1762     /* now we need to check against rules for the client's current state */
1763     if (self->fullscreen) {
1764         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1765                             OB_CLIENT_FUNC_FULLSCREEN |
1766                             OB_CLIENT_FUNC_ICONIFY);
1767         self->decorations = 0;
1768     }
1769
1770     client_change_allowed_actions(self);
1771
1772     if (reconfig)
1773         client_reconfigure(self);
1774 }
1775
1776 static void client_change_allowed_actions(ObClient *self)
1777 {
1778     gulong actions[12];
1779     gint num = 0;
1780
1781     /* desktop windows are kept on all desktops */
1782     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1783         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1784
1785     if (self->functions & OB_CLIENT_FUNC_SHADE)
1786         actions[num++] = prop_atoms.net_wm_action_shade;
1787     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1788         actions[num++] = prop_atoms.net_wm_action_close;
1789     if (self->functions & OB_CLIENT_FUNC_MOVE)
1790         actions[num++] = prop_atoms.net_wm_action_move;
1791     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1792         actions[num++] = prop_atoms.net_wm_action_minimize;
1793     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1794         actions[num++] = prop_atoms.net_wm_action_resize;
1795     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1796         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1797     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1798         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1799         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1800     }
1801     if (self->functions & OB_CLIENT_FUNC_ABOVE)
1802         actions[num++] = prop_atoms.net_wm_action_above;
1803     if (self->functions & OB_CLIENT_FUNC_BELOW)
1804         actions[num++] = prop_atoms.net_wm_action_below;
1805     if (self->functions & OB_CLIENT_FUNC_UNDECORATE)
1806         actions[num++] = prop_atoms.ob_wm_action_undecorate;
1807
1808     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1809
1810     /* make sure the window isn't breaking any rules now */
1811
1812     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1813         if (self->frame) client_shade(self, FALSE);
1814         else self->shaded = FALSE;
1815     }
1816     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1817         if (self->frame) client_iconify(self, FALSE, TRUE, FALSE);
1818         else self->iconic = FALSE;
1819     }
1820     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1821         if (self->frame) client_fullscreen(self, FALSE);
1822         else self->fullscreen = FALSE;
1823     }
1824     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1825                                                          self->max_vert)) {
1826         if (self->frame) client_maximize(self, FALSE, 0);
1827         else self->max_vert = self->max_horz = FALSE;
1828     }
1829 }
1830
1831 void client_reconfigure(ObClient *self)
1832 {
1833     client_configure(self, self->area.x, self->area.y,
1834                      self->area.width, self->area.height,
1835                      FALSE, TRUE);
1836 }
1837
1838 void client_update_wmhints(ObClient *self)
1839 {
1840     XWMHints *hints;
1841
1842     /* assume a window takes input if it doesnt specify */
1843     self->can_focus = TRUE;
1844   
1845     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1846         gboolean ur;
1847
1848         if (hints->flags & InputHint)
1849             self->can_focus = hints->input;
1850
1851         /* only do this when first managing the window *AND* when we aren't
1852            starting up! */
1853         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1854             if (hints->flags & StateHint)
1855                 self->iconic = hints->initial_state == IconicState;
1856
1857         ur = self->urgent;
1858         self->urgent = (hints->flags & XUrgencyHint);
1859         if (self->urgent && !ur)
1860             client_hilite(self, TRUE);
1861         else if (!self->urgent && ur && self->demands_attention)
1862             client_hilite(self, FALSE);
1863
1864         if (!(hints->flags & WindowGroupHint))
1865             hints->window_group = None;
1866
1867         /* did the group state change? */
1868         if (hints->window_group !=
1869             (self->group ? self->group->leader : None))
1870         {
1871             ObGroup *oldgroup = self->group;
1872
1873             /* remove from the old group if there was one */
1874             if (self->group != NULL) {
1875                 group_remove(self->group, self);
1876                 self->group = NULL;
1877             }
1878
1879             /* add ourself to the group if we have one */
1880             if (hints->window_group != None) {
1881                 self->group = group_add(hints->window_group, self);
1882             }
1883
1884             /* Put ourselves into the new group's transient tree, and remove
1885                ourselves from the old group's */
1886             client_update_transient_tree(self, oldgroup, self->group,
1887                                          self->transient_for,
1888                                          self->transient_for);
1889
1890             /* Lastly, being in a group, or not, can change if the window is
1891                transient for anything.
1892
1893                The logic for this is:
1894                self->transient = TRUE always if the window wants to be
1895                transient for something, even if transient_for was NULL because
1896                it wasn't in a group before.
1897
1898                If transient_for was NULL and oldgroup was NULL we can assume
1899                that when we add the new group, it will become transient for
1900                something.
1901
1902                If transient_for was OB_TRAN_GROUP, then it must have already
1903                had a group. If it is getting a new group, the above call to
1904                client_update_transient_tree has already taken care of
1905                everything ! If it is losing all group status then it will
1906                no longer be transient for anything and that needs to be
1907                updated.
1908             */
1909             if (self->transient &&
1910                 ((self->transient_for == NULL && oldgroup == NULL) ||
1911                  (self->transient_for == OB_TRAN_GROUP && !self->group)))
1912                 client_update_transient_for(self);
1913         }
1914
1915         /* the WM_HINTS can contain an icon */
1916         if (hints->flags & IconPixmapHint)
1917             client_update_icons(self);
1918
1919         XFree(hints);
1920     }
1921 }
1922
1923 void client_update_title(ObClient *self)
1924 {
1925     gchar *data = NULL;
1926     gchar *visible = NULL;
1927
1928     g_free(self->title);
1929      
1930     /* try netwm */
1931     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1932         /* try old x stuff */
1933         if (!(PROP_GETS(self->window, wm_name, locale, &data)
1934               || PROP_GETS(self->window, wm_name, utf8, &data))) {
1935             if (self->transient) {
1936                 /*
1937                   GNOME alert windows are not given titles:
1938                   http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1939                 */
1940                 data = g_strdup("");
1941             } else
1942                 data = g_strdup("Unnamed Window");
1943         }
1944     }
1945
1946     if (self->client_machine) {
1947         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1948         g_free(data);
1949     } else
1950         visible = data;
1951
1952     PROP_SETS(self->window, net_wm_visible_name, visible);
1953     self->title = visible;
1954
1955     if (self->frame)
1956         frame_adjust_title(self->frame);
1957
1958     /* update the icon title */
1959     data = NULL;
1960     g_free(self->icon_title);
1961
1962     /* try netwm */
1963     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1964         /* try old x stuff */
1965         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data) ||
1966               PROP_GETS(self->window, wm_icon_name, utf8, &data)))
1967             data = g_strdup(self->title);
1968
1969     if (self->client_machine) {
1970         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1971         g_free(data);
1972     } else
1973         visible = data;
1974
1975     PROP_SETS(self->window, net_wm_visible_icon_name, visible);
1976     self->icon_title = visible;
1977 }
1978
1979 void client_update_strut(ObClient *self)
1980 {
1981     guint num;
1982     guint32 *data;
1983     gboolean got = FALSE;
1984     StrutPartial strut;
1985
1986     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1987                     &data, &num)) {
1988         if (num == 12) {
1989             got = TRUE;
1990             STRUT_PARTIAL_SET(strut,
1991                               data[0], data[2], data[1], data[3],
1992                               data[4], data[5], data[8], data[9],
1993                               data[6], data[7], data[10], data[11]);
1994         }
1995         g_free(data);
1996     }
1997
1998     if (!got &&
1999         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
2000         if (num == 4) {
2001             const Rect *a;
2002
2003             got = TRUE;
2004
2005             /* use the screen's width/height */
2006             a = screen_physical_area();
2007
2008             STRUT_PARTIAL_SET(strut,
2009                               data[0], data[2], data[1], data[3],
2010                               a->y, a->y + a->height - 1,
2011                               a->x, a->x + a->width - 1,
2012                               a->y, a->y + a->height - 1,
2013                               a->x, a->x + a->width - 1);
2014         }
2015         g_free(data);
2016     }
2017
2018     if (!got)
2019         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
2020                           0, 0, 0, 0, 0, 0, 0, 0);
2021
2022     if (!STRUT_EQUAL(strut, self->strut)) {
2023         self->strut = strut;
2024
2025         /* updating here is pointless while we're being mapped cuz we're not in
2026            the client list yet */
2027         if (self->frame)
2028             screen_update_areas();
2029     }
2030 }
2031
2032 void client_update_icons(ObClient *self)
2033 {
2034     guint num;
2035     guint32 *data;
2036     guint w, h, i, j;
2037
2038     for (i = 0; i < self->nicons; ++i)
2039         g_free(self->icons[i].data);
2040     if (self->nicons > 0)
2041         g_free(self->icons);
2042     self->nicons = 0;
2043
2044     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
2045         /* figure out how many valid icons are in here */
2046         i = 0;
2047         while (num - i > 2) {
2048             w = data[i++];
2049             h = data[i++];
2050             i += w * h;
2051             if (i > num || w*h == 0) break;
2052             ++self->nicons;
2053         }
2054
2055         self->icons = g_new(ObClientIcon, self->nicons);
2056     
2057         /* store the icons */
2058         i = 0;
2059         for (j = 0; j < self->nicons; ++j) {
2060             guint x, y, t;
2061
2062             w = self->icons[j].width = data[i++];
2063             h = self->icons[j].height = data[i++];
2064
2065             if (w*h == 0) continue;
2066
2067             self->icons[j].data = g_new(RrPixel32, w * h);
2068             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
2069                 if (x >= w) {
2070                     x = 0;
2071                     ++y;
2072                 }
2073                 self->icons[j].data[t] =
2074                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
2075                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
2076                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
2077                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
2078             }
2079             g_assert(i <= num);
2080         }
2081
2082         g_free(data);
2083     } else {
2084         XWMHints *hints;
2085
2086         if ((hints = XGetWMHints(ob_display, self->window))) {
2087             if (hints->flags & IconPixmapHint) {
2088                 self->nicons++;
2089                 self->icons = g_new(ObClientIcon, self->nicons);
2090                 xerror_set_ignore(TRUE);
2091                 if (!RrPixmapToRGBA(ob_rr_inst,
2092                                     hints->icon_pixmap,
2093                                     (hints->flags & IconMaskHint ?
2094                                      hints->icon_mask : None),
2095                                     &self->icons[self->nicons-1].width,
2096                                     &self->icons[self->nicons-1].height,
2097                                     &self->icons[self->nicons-1].data)){
2098                     g_free(&self->icons[self->nicons-1]);
2099                     self->nicons--;
2100                 }
2101                 xerror_set_ignore(FALSE);
2102             }
2103             XFree(hints);
2104         }
2105     }
2106
2107     /* set the default icon onto the window
2108        in theory, this could be a race, but if a window doesn't set an icon
2109        or removes it entirely, it's not very likely it is going to set one
2110        right away afterwards */
2111     if (self->nicons == 0) {
2112         RrPixel32 *icon = ob_rr_theme->def_win_icon;
2113         gulong *data;
2114
2115         data = g_new(gulong, 48*48+2);
2116         data[0] = data[1] =  48;
2117         for (i = 0; i < 48*48; ++i)
2118             data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2119                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2120                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2121                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2122         PROP_SETA32(self->window, net_wm_icon, cardinal, data, 48*48+2);
2123         g_free(data);
2124     } else if (self->frame)
2125         /* don't draw the icon empty if we're just setting one now anyways,
2126            we'll get the property change any second */
2127         frame_adjust_icon(self->frame);
2128 }
2129
2130 void client_update_user_time(ObClient *self)
2131 {
2132     guint32 time;
2133     gboolean got = FALSE;
2134
2135     if (self->user_time_window)
2136         got = PROP_GET32(self->user_time_window,
2137                          net_wm_user_time, cardinal, &time);
2138     if (!got)
2139         got = PROP_GET32(self->window, net_wm_user_time, cardinal, &time);
2140
2141     if (got) {
2142         /* we set this every time, not just when it grows, because in practice
2143            sometimes time goes backwards! (ntpdate.. yay....) so.. if it goes
2144            backward we don't want all windows to stop focusing. we'll just
2145            assume noone is setting times older than the last one, cuz that
2146            would be pretty stupid anyways
2147         */
2148         self->user_time = time;
2149
2150         /*ob_debug("window %s user time %u\n", self->title, time);*/
2151     }
2152 }
2153
2154 void client_update_user_time_window(ObClient *self)
2155 {
2156     guint32 w;
2157
2158     if (!PROP_GET32(self->window, net_wm_user_time_window, window, &w))
2159         w = None;
2160
2161     if (w != self->user_time_window) {
2162         /* remove the old window */
2163         propwin_remove(self->user_time_window, OB_PROPWIN_USER_TIME, self);
2164         self->user_time_window = None;
2165
2166         if (self->group && self->group->leader == w) {
2167             ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2168                           "_NET_WM_USER_TYPE_WINDOW to its group leader\n");
2169             /* do it anyways..? */
2170         }
2171         else if (w == self->window) {
2172             ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2173                           "_NET_WM_USER_TIME_WINDOW to itself\n");
2174             w = None; /* don't do it */
2175         }
2176
2177         /* add the new window */
2178         propwin_add(w, OB_PROPWIN_USER_TIME, self);
2179         self->user_time_window = w;
2180
2181         /* and update from it */
2182         client_update_user_time(self);
2183     }
2184 }
2185
2186 void client_update_icon_geometry(ObClient *self)
2187 {
2188     guint num;
2189     guint32 *data;
2190
2191     RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2192
2193     if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num)
2194         && num == 4)
2195     {
2196         /* don't let them set it with an area < 0 */
2197         RECT_SET(self->icon_geometry, data[0], data[1],
2198                  MAX(data[2],0), MAX(data[3],0));
2199     }
2200 }
2201
2202 static void client_get_session_ids(ObClient *self)
2203 {
2204     guint32 leader;
2205     gboolean got;
2206     gchar *s;
2207     gchar **ss;
2208
2209     if (!PROP_GET32(self->window, wm_client_leader, window, &leader))
2210         leader = None;
2211
2212     /* get the SM_CLIENT_ID */
2213     got = FALSE;
2214     if (leader)
2215         got = PROP_GETS(leader, sm_client_id, locale, &self->sm_client_id);
2216     if (!got)
2217         PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id);
2218
2219     /* get the WM_CLASS (name and class). make them "" if they are not
2220        provided */
2221     got = FALSE;
2222     if (leader)
2223         got = PROP_GETSS(leader, wm_class, locale, &ss);
2224     if (!got)
2225         got = PROP_GETSS(self->window, wm_class, locale, &ss);
2226
2227     if (got) {
2228         if (ss[0]) {
2229             self->name = g_strdup(ss[0]);
2230             if (ss[1])
2231                 self->class = g_strdup(ss[1]);
2232         }
2233         g_strfreev(ss);
2234     }
2235
2236     if (self->name == NULL) self->name = g_strdup("");
2237     if (self->class == NULL) self->class = g_strdup("");
2238
2239     /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2240     got = FALSE;
2241     if (leader)
2242         got = PROP_GETS(leader, wm_window_role, locale, &s);
2243     if (!got)
2244         got = PROP_GETS(self->window, wm_window_role, locale, &s);
2245
2246     if (got)
2247         self->role = s;
2248     else
2249         self->role = g_strdup("");
2250
2251     /* get the WM_COMMAND */
2252     got = FALSE;
2253
2254     if (leader)
2255         got = PROP_GETSS(leader, wm_command, locale, &ss);
2256     if (!got)
2257         got = PROP_GETSS(self->window, wm_command, locale, &ss);
2258
2259     if (got) {
2260         /* merge/mash them all together */
2261         gchar *merge = NULL;
2262         gint i;
2263
2264         for (i = 0; ss[i]; ++i) {
2265             gchar *tmp = merge;
2266             if (merge)
2267                 merge = g_strconcat(merge, ss[i], NULL);
2268             else
2269                 merge = g_strconcat(ss[i], NULL);
2270             g_free(tmp);
2271         }
2272         g_strfreev(ss);
2273
2274         self->wm_command = merge;
2275     }
2276
2277     /* get the WM_CLIENT_MACHINE */
2278     got = FALSE;
2279     if (leader)
2280         got = PROP_GETS(leader, wm_client_machine, locale, &s);
2281     if (!got)
2282         got = PROP_GETS(self->window, wm_client_machine, locale, &s);
2283
2284     if (got) {
2285         gchar localhost[128];
2286
2287         gethostname(localhost, 127);
2288         localhost[127] = '\0';
2289         if (strcmp(localhost, s) != 0)
2290             self->client_machine = s;
2291         else
2292             g_free(s);
2293     }
2294 }
2295
2296 static void client_change_wm_state(ObClient *self)
2297 {
2298     gulong state[2];
2299     glong old;
2300
2301     old = self->wmstate;
2302
2303     if (self->shaded || self->iconic ||
2304         (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2305     {
2306         self->wmstate = IconicState;
2307     } else
2308         self->wmstate = NormalState;
2309
2310     if (old != self->wmstate) {
2311         PROP_MSG(self->window, kde_wm_change_state,
2312                  self->wmstate, 1, 0, 0);
2313
2314         state[0] = self->wmstate;
2315         state[1] = None;
2316         PROP_SETA32(self->window, wm_state, wm_state, state, 2);
2317     }
2318 }
2319
2320 static void client_change_state(ObClient *self)
2321 {
2322     gulong netstate[12];
2323     guint num;
2324
2325     num = 0;
2326     if (self->modal)
2327         netstate[num++] = prop_atoms.net_wm_state_modal;
2328     if (self->shaded)
2329         netstate[num++] = prop_atoms.net_wm_state_shaded;
2330     if (self->iconic)
2331         netstate[num++] = prop_atoms.net_wm_state_hidden;
2332     if (self->skip_taskbar)
2333         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
2334     if (self->skip_pager)
2335         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
2336     if (self->fullscreen)
2337         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
2338     if (self->max_vert)
2339         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
2340     if (self->max_horz)
2341         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
2342     if (self->above)
2343         netstate[num++] = prop_atoms.net_wm_state_above;
2344     if (self->below)
2345         netstate[num++] = prop_atoms.net_wm_state_below;
2346     if (self->demands_attention)
2347         netstate[num++] = prop_atoms.net_wm_state_demands_attention;
2348     if (self->undecorated)
2349         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
2350     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
2351
2352     if (self->frame)
2353         frame_adjust_state(self->frame);
2354 }
2355
2356 ObClient *client_search_focus_tree(ObClient *self)
2357 {
2358     GSList *it;
2359     ObClient *ret;
2360
2361     for (it = self->transients; it; it = g_slist_next(it)) {
2362         if (client_focused(it->data)) return it->data;
2363         if ((ret = client_search_focus_tree(it->data))) return ret;
2364     }
2365     return NULL;
2366 }
2367
2368 ObClient *client_search_focus_tree_full(ObClient *self)
2369 {
2370     GSList *it;
2371     ObClient *c;
2372
2373     if (self->transient_for && self->transient_for != OB_TRAN_GROUP) {
2374         if ((c = client_search_focus_tree_full(self->transient_for)))
2375             return c;
2376     }
2377         
2378     for (it = self->group->members; it; it = g_slist_next(it)) {
2379         if (it->data != self) {
2380             c = it->data;
2381             if (client_focused(c)) return c;
2382             if ((c = client_search_focus_tree(it->data))) return c;
2383         }
2384     }
2385
2386     /* this function checks the whole tree, the client_search_focus_tree
2387        does not, so we need to check this window */
2388     if (client_focused(self))
2389         return self;
2390     return client_search_focus_tree(self);
2391 }
2392
2393 gboolean client_has_parent(ObClient *self)
2394 {
2395     if (self->transient_for) {
2396         if (self->transient_for != OB_TRAN_GROUP) {
2397             if (client_normal(self->transient_for))
2398                 return TRUE;
2399         }
2400         else if (self->group) {
2401             GSList *it;
2402
2403             for (it = self->group->members; it; it = g_slist_next(it)) {
2404                 if (it->data != self && client_normal(it->data))
2405                     return TRUE;
2406             }
2407         }
2408     }
2409     return FALSE;
2410 }
2411
2412 static ObStackingLayer calc_layer(ObClient *self)
2413 {
2414     ObStackingLayer l;
2415
2416     if (self->type == OB_CLIENT_TYPE_DESKTOP)
2417         l = OB_STACKING_LAYER_DESKTOP;
2418     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2419         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2420         else l = OB_STACKING_LAYER_ABOVE;
2421     }
2422     else if ((self->fullscreen ||
2423               /* No decorations and fills the monitor = oldskool fullscreen.
2424                  But not for maximized windows.
2425               */
2426               (self->decorations == 0 &&
2427                !(self->max_horz && self->max_vert) &&
2428                RECT_EQUAL(self->area,
2429                           *screen_physical_area_monitor
2430                           (client_monitor(self))))) &&
2431              (client_focused(self) || client_search_focus_tree(self)))
2432         l = OB_STACKING_LAYER_FULLSCREEN;
2433     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2434     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2435     else l = OB_STACKING_LAYER_NORMAL;
2436
2437     return l;
2438 }
2439
2440 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2441                                         ObStackingLayer min)
2442 {
2443     ObStackingLayer old, own;
2444     GSList *it;
2445
2446     old = self->layer;
2447     own = calc_layer(self);
2448     self->layer = MAX(own, min);
2449
2450     if (self->layer != old) {
2451         stacking_remove(CLIENT_AS_WINDOW(self));
2452         stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
2453     }
2454
2455     for (it = self->transients; it; it = g_slist_next(it))
2456         client_calc_layer_recursive(it->data, orig,
2457                                     self->layer);
2458 }
2459
2460 void client_calc_layer(ObClient *self)
2461 {
2462     ObClient *orig;
2463     GSList *it;
2464
2465     orig = self;
2466
2467     /* transients take on the layer of their parents */
2468     it = client_search_all_top_parents(self);
2469
2470     for (; it; it = g_slist_next(it))
2471         client_calc_layer_recursive(it->data, orig, 0);
2472 }
2473
2474 gboolean client_should_show(ObClient *self)
2475 {
2476     if (self->iconic)
2477         return FALSE;
2478     if (client_normal(self) && screen_showing_desktop)
2479         return FALSE;
2480     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2481         return TRUE;
2482     
2483     return FALSE;
2484 }
2485
2486 gboolean client_show(ObClient *self)
2487 {
2488     gboolean show = FALSE;
2489
2490     if (client_should_show(self)) {
2491         frame_show(self->frame);
2492         show = TRUE;
2493
2494         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2495            it needs to be in IconicState. This includes when it is on another
2496            desktop!
2497         */
2498         client_change_wm_state(self);
2499     }
2500     return show;
2501 }
2502
2503 gboolean client_hide(ObClient *self)
2504 {
2505     gboolean hide = FALSE;
2506
2507     if (!client_should_show(self)) {
2508         if (self == focus_client) {
2509             /* if there is a grab going on, then we need to cancel it. if we
2510                move focus during the grab, applications will get
2511                NotifyWhileGrabbed events and ignore them !
2512
2513                actions should not rely on being able to move focus during an
2514                interactive grab.
2515             */
2516             event_cancel_all_key_grabs();
2517         }
2518
2519         frame_hide(self->frame);
2520         hide = TRUE;
2521
2522         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2523            it needs to be in IconicState. This includes when it is on another
2524            desktop!
2525         */
2526         client_change_wm_state(self);
2527     }
2528     return hide;
2529 }
2530
2531 void client_showhide(ObClient *self)
2532 {
2533     if (!client_show(self))
2534         client_hide(self);
2535 }
2536
2537 gboolean client_normal(ObClient *self) {
2538     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2539               self->type == OB_CLIENT_TYPE_DOCK ||
2540               self->type == OB_CLIENT_TYPE_SPLASH);
2541 }
2542
2543 gboolean client_helper(ObClient *self)
2544 {
2545     return (self->type == OB_CLIENT_TYPE_UTILITY ||
2546             self->type == OB_CLIENT_TYPE_MENU ||
2547             self->type == OB_CLIENT_TYPE_TOOLBAR);
2548 }
2549
2550 gboolean client_mouse_focusable(ObClient *self)
2551 {
2552     return !(self->type == OB_CLIENT_TYPE_MENU ||
2553              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2554              self->type == OB_CLIENT_TYPE_SPLASH ||
2555              self->type == OB_CLIENT_TYPE_DOCK);
2556 }
2557
2558 gboolean client_enter_focusable(ObClient *self)
2559 {
2560     /* you can focus desktops but it shouldn't on enter */
2561     return (client_mouse_focusable(self) &&
2562             self->type != OB_CLIENT_TYPE_DESKTOP);
2563 }
2564
2565
2566 static void client_apply_startup_state(ObClient *self,
2567                                        gint x, gint y, gint w, gint h)
2568 {
2569     /* save the states that we are going to apply */
2570     gboolean iconic = self->iconic;
2571     gboolean fullscreen = self->fullscreen;
2572     gboolean undecorated = self->undecorated;
2573     gboolean shaded = self->shaded;
2574     gboolean demands_attention = self->demands_attention;
2575     gboolean max_horz = self->max_horz;
2576     gboolean max_vert = self->max_vert;
2577     Rect oldarea;
2578     gint l;
2579
2580     /* turn them all off in the client, so they won't affect the window
2581        being placed */
2582     self->iconic = self->fullscreen = self->undecorated = self->shaded =
2583         self->demands_attention = self->max_horz = self->max_vert = FALSE;
2584
2585     /* move the client to its placed position, or it it's already there,
2586        generate a ConfigureNotify telling the client where it is.
2587
2588        do this after adjusting the frame. otherwise it gets all weird and
2589        clients don't work right
2590
2591        do this before applying the states so they have the correct
2592        pre-max/pre-fullscreen values
2593     */
2594     client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE);
2595     ob_debug("placed window 0x%x at %d, %d with size %d x %d\n",
2596              self->window, self->area.x, self->area.y,
2597              self->area.width, self->area.height);
2598     oldarea = self->area;              /* save the area */
2599     RECT_SET(self->area, x, y, w, h);  /* put where it should be for the premax stuff */
2600
2601     /* apply the states. these are in a carefully crafted order.. */
2602
2603     if (iconic)
2604         client_iconify(self, TRUE, FALSE, TRUE);
2605     if (fullscreen)
2606         client_fullscreen(self, TRUE);
2607     if (undecorated)
2608         client_set_undecorated(self, TRUE);
2609     if (shaded)
2610         client_shade(self, TRUE);
2611     if (demands_attention)
2612         client_hilite(self, TRUE);
2613   
2614     if (max_vert && max_horz)
2615         client_maximize(self, TRUE, 0);
2616     else if (max_vert)
2617         client_maximize(self, TRUE, 2);
2618     else if (max_horz)
2619         client_maximize(self, TRUE, 1);
2620
2621     /* if the window hasn't been configured yet, then do so now */
2622     if (!fullscreen && !max_vert && !max_horz) {
2623         self->area = oldarea;
2624         client_configure(self, x, y, w, h, FALSE, TRUE);
2625     }
2626
2627     /* set the desktop hint, to make sure that it always exists */
2628     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
2629
2630     /* nothing to do for the other states:
2631        skip_taskbar
2632        skip_pager
2633        modal
2634        above
2635        below
2636     */
2637 }
2638
2639 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2640 {
2641     /* these should be the current values. this is for when you're not moving,
2642        just resizing */
2643     g_assert(*x == self->area.x);
2644     g_assert(oldw == self->area.width);
2645
2646     /* horizontal */
2647     switch (self->gravity) {
2648     default:
2649     case NorthWestGravity:
2650     case WestGravity:
2651     case SouthWestGravity:
2652     case StaticGravity:
2653     case ForgetGravity:
2654         break;
2655     case NorthGravity:
2656     case CenterGravity:
2657     case SouthGravity:
2658         *x -= (neww - oldw) / 2;
2659         break;
2660     case NorthEastGravity:
2661     case EastGravity:
2662     case SouthEastGravity:
2663         *x -= neww - oldw;
2664         break;
2665     }
2666 }
2667
2668 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2669 {
2670     /* these should be the current values. this is for when you're not moving,
2671        just resizing */
2672     g_assert(*y == self->area.y);
2673     g_assert(oldh == self->area.height);
2674
2675     /* vertical */
2676     switch (self->gravity) {
2677     default:
2678     case NorthWestGravity:
2679     case NorthGravity:
2680     case NorthEastGravity:
2681     case StaticGravity:
2682     case ForgetGravity:
2683         break;
2684     case WestGravity:
2685     case CenterGravity:
2686     case EastGravity:
2687         *y -= (newh - oldh) / 2;
2688         break;
2689     case SouthWestGravity:
2690     case SouthGravity:
2691     case SouthEastGravity:
2692         *y -= newh - oldh;
2693         break;
2694     }
2695 }
2696
2697 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2698                           gint *logicalw, gint *logicalh,
2699                           gboolean user)
2700 {
2701     Rect desired_area = {*x, *y, *w, *h};
2702
2703     /* make the frame recalculate its dimentions n shit without changing
2704        anything visible for real, this way the constraints below can work with
2705        the updated frame dimensions. */
2706     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
2707
2708     /* work within the prefered sizes given by the window */
2709     if (!(*w == self->area.width && *h == self->area.height)) {
2710         gint basew, baseh, minw, minh;
2711
2712         /* base size is substituted with min size if not specified */
2713         if (self->base_size.width || self->base_size.height) {
2714             basew = self->base_size.width;
2715             baseh = self->base_size.height;
2716         } else {
2717             basew = self->min_size.width;
2718             baseh = self->min_size.height;
2719         }
2720         /* min size is substituted with base size if not specified */
2721         if (self->min_size.width || self->min_size.height) {
2722             minw = self->min_size.width;
2723             minh = self->min_size.height;
2724         } else {
2725             minw = self->base_size.width;
2726             minh = self->base_size.height;
2727         }
2728
2729         /* if this is a user-requested resize, then check against min/max
2730            sizes */
2731
2732         /* smaller than min size or bigger than max size? */
2733         if (*w > self->max_size.width) *w = self->max_size.width;
2734         if (*w < minw) *w = minw;
2735         if (*h > self->max_size.height) *h = self->max_size.height;
2736         if (*h < minh) *h = minh;
2737
2738         *w -= basew;
2739         *h -= baseh;
2740
2741         /* keep to the increments */
2742         *w /= self->size_inc.width;
2743         *h /= self->size_inc.height;
2744
2745         /* you cannot resize to nothing */
2746         if (basew + *w < 1) *w = 1 - basew;
2747         if (baseh + *h < 1) *h = 1 - baseh;
2748   
2749         /* save the logical size */
2750         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2751         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2752
2753         *w *= self->size_inc.width;
2754         *h *= self->size_inc.height;
2755
2756         *w += basew;
2757         *h += baseh;
2758
2759         /* adjust the height to match the width for the aspect ratios.
2760            for this, min size is not substituted for base size ever. */
2761         *w -= self->base_size.width;
2762         *h -= self->base_size.height;
2763
2764         if (!self->fullscreen) {
2765             if (self->min_ratio)
2766                 if (*h * self->min_ratio > *w) {
2767                     *h = (gint)(*w / self->min_ratio);
2768
2769                     /* you cannot resize to nothing */
2770                     if (*h < 1) {
2771                         *h = 1;
2772                         *w = (gint)(*h * self->min_ratio);
2773                     }
2774                 }
2775             if (self->max_ratio)
2776                 if (*h * self->max_ratio < *w) {
2777                     *h = (gint)(*w / self->max_ratio);
2778
2779                     /* you cannot resize to nothing */
2780                     if (*h < 1) {
2781                         *h = 1;
2782                         *w = (gint)(*h * self->min_ratio);
2783                     }
2784                 }
2785         }
2786
2787         *w += self->base_size.width;
2788         *h += self->base_size.height;
2789     }
2790
2791     /* gets the frame's position */
2792     frame_client_gravity(self->frame, x, y, *w, *h);
2793
2794     /* these positions are frame positions, not client positions */
2795
2796     /* set the size and position if fullscreen */
2797     if (self->fullscreen) {
2798         Rect *a;
2799         guint i;
2800
2801         i = screen_find_monitor(&desired_area);
2802         a = screen_physical_area_monitor(i);
2803
2804         *x = a->x;
2805         *y = a->y;
2806         *w = a->width;
2807         *h = a->height;
2808
2809         user = FALSE; /* ignore if the client can't be moved/resized when it
2810                          is fullscreening */
2811     } else if (self->max_horz || self->max_vert) {
2812         Rect *a;
2813         guint i;
2814
2815         i = screen_find_monitor(&desired_area);
2816         a = screen_area_monitor(self->desktop, i);
2817
2818         /* set the size and position if maximized */
2819         if (self->max_horz) {
2820             *x = a->x;
2821             *w = a->width - self->frame->size.left - self->frame->size.right;
2822         }
2823         if (self->max_vert) {
2824             *y = a->y;
2825             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2826         }
2827
2828         user = FALSE; /* ignore if the client can't be moved/resized when it
2829                          is maximizing */
2830     }
2831
2832     /* gets the client's position */
2833     frame_frame_gravity(self->frame, x, y, *w, *h);
2834
2835     /* these override the above states! if you cant move you can't move! */
2836     if (user) {
2837         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2838             *x = self->area.x;
2839             *y = self->area.y;
2840         }
2841         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2842             *w = self->area.width;
2843             *h = self->area.height;
2844         }
2845     }
2846
2847     g_assert(*w > 0);
2848     g_assert(*h > 0);
2849 }
2850
2851
2852 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
2853                       gboolean user, gboolean final)
2854 {
2855     gint oldw, oldh;
2856     gboolean send_resize_client;
2857     gboolean moved = FALSE, resized = FALSE;
2858     gboolean fmoved, fresized;
2859     guint fdecor = self->frame->decorations;
2860     gboolean fhorz = self->frame->max_horz;
2861     gboolean fvert = self->frame->max_vert;
2862     gint logicalw, logicalh;
2863
2864     /* find the new x, y, width, and height (and logical size) */
2865     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2866
2867     /* set the logical size if things changed */
2868     if (!(w == self->area.width && h == self->area.height))
2869         SIZE_SET(self->logical_size, logicalw, logicalh);
2870
2871     /* figure out if we moved or resized or what */
2872     moved = (x != self->area.x || y != self->area.y);
2873     resized = (w != self->area.width || h != self->area.height);
2874
2875     oldw = self->area.width;
2876     oldh = self->area.height;
2877     RECT_SET(self->area, x, y, w, h);
2878
2879     /* for app-requested resizes, always resize if 'resized' is true.
2880        for user-requested ones, only resize if final is true, or when
2881        resizing in redraw mode */
2882     send_resize_client = ((!user && resized) ||
2883                           (user && (final ||
2884                                     (resized && config_resize_redraw))));
2885
2886     /* if the client is enlarging, then resize the client before the frame */
2887     if (send_resize_client && (w > oldw || h > oldh)) {
2888         XMoveResizeWindow(ob_display, self->window,
2889                           self->frame->size.left, self->frame->size.top,
2890                           MAX(w, oldw), MAX(h, oldh));
2891         frame_adjust_client_area(self->frame);
2892     }
2893
2894     /* find the frame's dimensions and move/resize it */
2895     fmoved = moved;
2896     fresized = resized;
2897
2898     /* if decorations changed, then readjust everything for the frame */
2899     if (self->decorations != fdecor ||
2900         self->max_horz != fhorz || self->max_vert != fvert)
2901     {
2902         fmoved = fresized = TRUE;
2903     }
2904
2905     /* adjust the frame */
2906     if (fmoved || fresized)
2907         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
2908
2909     /* This is kinda tricky and should not be changed.. let me explain!
2910
2911        When user = FALSE, then the request is coming from the application
2912        itself, and we are more strict about when to send a synthetic
2913        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
2914        in this case.
2915
2916        When user = TRUE, then the request is coming from "us", like when we
2917        maximize a window or sometihng.  In this case we are more lenient.  We
2918        used to follow the same rules as above, but _Java_ Swing can't handle
2919        this. So just to appease Swing, when user = TRUE, we always send
2920        a synthetic ConfigureNotify to give the window its root coordinates.
2921     */
2922     if ((!user && !resized) || (user && final))
2923     {
2924         XEvent event;
2925
2926         /* we have reset the client to 0 border width, so don't include
2927            it in these coords */
2928         POINT_SET(self->root_pos,
2929                   self->frame->area.x + self->frame->size.left -
2930                   self->border_width,
2931                   self->frame->area.y + self->frame->size.top -
2932                   self->border_width);
2933
2934         event.type = ConfigureNotify;
2935         event.xconfigure.display = ob_display;
2936         event.xconfigure.event = self->window;
2937         event.xconfigure.window = self->window;
2938
2939         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d\n",
2940                  self->title, self->root_pos.x, self->root_pos.y, w, h);
2941
2942         /* root window real coords */
2943         event.xconfigure.x = self->root_pos.x;
2944         event.xconfigure.y = self->root_pos.y;
2945         event.xconfigure.width = w;
2946         event.xconfigure.height = h;
2947         event.xconfigure.border_width = self->border_width;
2948         event.xconfigure.above = None;
2949         event.xconfigure.override_redirect = FALSE;
2950         XSendEvent(event.xconfigure.display, event.xconfigure.window,
2951                    FALSE, StructureNotifyMask, &event);
2952     }
2953
2954     /* if the client is shrinking, then resize the frame before the client.
2955
2956        both of these resize sections may run, because the top one only resizes
2957        in the direction that is growing
2958      */
2959     if (send_resize_client && (w <= oldw || h <= oldh)) {
2960         frame_adjust_client_area(self->frame);
2961         XMoveResizeWindow(ob_display, self->window,
2962                           self->frame->size.left, self->frame->size.top, w, h);
2963     }
2964
2965     XFlush(ob_display);
2966 }
2967
2968 void client_fullscreen(ObClient *self, gboolean fs)
2969 {
2970     gint x, y, w, h;
2971
2972     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2973         self->fullscreen == fs) return;                   /* already done */
2974
2975     self->fullscreen = fs;
2976     client_change_state(self); /* change the state hints on the client */
2977
2978     if (fs) {
2979         self->pre_fullscreen_area = self->area;
2980         /* if the window is maximized, its area isn't all that meaningful.
2981            save it's premax area instead. */
2982         if (self->max_horz) {
2983             self->pre_fullscreen_area.x = self->pre_max_area.x;
2984             self->pre_fullscreen_area.width = self->pre_max_area.width;
2985         }
2986         if (self->max_vert) {
2987             self->pre_fullscreen_area.y = self->pre_max_area.y;
2988             self->pre_fullscreen_area.height = self->pre_max_area.height;
2989         }
2990
2991         /* these will help configure_full figure out where to fullscreen
2992            the window */
2993         x = self->area.x;
2994         y = self->area.y;
2995         w = self->area.width;
2996         h = self->area.height;
2997     } else {
2998         g_assert(self->pre_fullscreen_area.width > 0 &&
2999                  self->pre_fullscreen_area.height > 0);
3000
3001         x = self->pre_fullscreen_area.x;
3002         y = self->pre_fullscreen_area.y;
3003         w = self->pre_fullscreen_area.width;
3004         h = self->pre_fullscreen_area.height;
3005         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3006     }
3007
3008     client_setup_decor_and_functions(self, FALSE);
3009     client_move_resize(self, x, y, w, h);
3010
3011     /* and adjust our layer/stacking. do this after resizing the window,
3012        and applying decorations, because windows which fill the screen are
3013        considered "fullscreen" and it affects their layer */
3014     client_calc_layer(self);
3015
3016     if (fs) {
3017         /* try focus us when we go into fullscreen mode */
3018         client_focus(self);
3019     }
3020 }
3021
3022 static void client_iconify_recursive(ObClient *self,
3023                                      gboolean iconic, gboolean curdesk,
3024                                      gboolean hide_animation)
3025 {
3026     GSList *it;
3027     gboolean changed = FALSE;
3028
3029
3030     if (self->iconic != iconic) {
3031         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
3032                  self->window);
3033
3034         if (iconic) {
3035             /* don't let non-normal windows iconify along with their parents
3036                or whatever */
3037             if (client_normal(self)) {
3038                 self->iconic = iconic;
3039
3040                 /* update the focus lists.. iconic windows go to the bottom of
3041                    the list */
3042                 focus_order_to_bottom(self);
3043
3044                 changed = TRUE;
3045             }
3046         } else {
3047             self->iconic = iconic;
3048
3049             if (curdesk && self->desktop != screen_desktop &&
3050                 self->desktop != DESKTOP_ALL)
3051                 client_set_desktop(self, screen_desktop, FALSE);
3052
3053             /* this puts it after the current focused window */
3054             focus_order_remove(self);
3055             focus_order_add_new(self);
3056
3057             changed = TRUE;
3058         }
3059     }
3060
3061     if (changed) {
3062         client_change_state(self);
3063         if (config_animate_iconify && !hide_animation)
3064             frame_begin_iconify_animation(self->frame, iconic);
3065         /* do this after starting the animation so it doesn't flash */
3066         client_showhide(self);
3067     }
3068
3069     /* iconify all direct transients, and deiconify all transients
3070        (non-direct too) */
3071     for (it = self->transients; it; it = g_slist_next(it))
3072         if (it->data != self)
3073             if (client_is_direct_child(self, it->data) || !iconic)
3074                 client_iconify_recursive(it->data, iconic, curdesk,
3075                                          hide_animation);
3076 }
3077
3078 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3079                     gboolean hide_animation)
3080 {
3081     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3082         /* move up the transient chain as far as possible first */
3083         self = client_search_top_normal_parent(self);
3084         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3085     }
3086 }
3087
3088 void client_maximize(ObClient *self, gboolean max, gint dir)
3089 {
3090     gint x, y, w, h;
3091      
3092     g_assert(dir == 0 || dir == 1 || dir == 2);
3093     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
3094
3095     /* check if already done */
3096     if (max) {
3097         if (dir == 0 && self->max_horz && self->max_vert) return;
3098         if (dir == 1 && self->max_horz) return;
3099         if (dir == 2 && self->max_vert) return;
3100     } else {
3101         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3102         if (dir == 1 && !self->max_horz) return;
3103         if (dir == 2 && !self->max_vert) return;
3104     }
3105
3106     /* these will help configure_full figure out which screen to fill with
3107        the window */
3108     x = self->area.x;
3109     y = self->area.y;
3110     w = self->area.width;
3111     h = self->area.height;
3112
3113     if (max) {
3114         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3115             RECT_SET(self->pre_max_area,
3116                      self->area.x, self->pre_max_area.y,
3117                      self->area.width, self->pre_max_area.height);
3118         }
3119         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3120             RECT_SET(self->pre_max_area,
3121                      self->pre_max_area.x, self->area.y,
3122                      self->pre_max_area.width, self->area.height);
3123         }
3124     } else {
3125         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3126             g_assert(self->pre_max_area.width > 0);
3127
3128             x = self->pre_max_area.x;
3129             w = self->pre_max_area.width;
3130
3131             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3132                      0, self->pre_max_area.height);
3133         }
3134         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3135             g_assert(self->pre_max_area.height > 0);
3136
3137             y = self->pre_max_area.y;
3138             h = self->pre_max_area.height;
3139
3140             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3141                      self->pre_max_area.width, 0);
3142         }
3143     }
3144
3145     if (dir == 0 || dir == 1) /* horz */
3146         self->max_horz = max;
3147     if (dir == 0 || dir == 2) /* vert */
3148         self->max_vert = max;
3149
3150     client_change_state(self); /* change the state hints on the client */
3151
3152     client_setup_decor_and_functions(self, FALSE);
3153     client_move_resize(self, x, y, w, h);
3154 }
3155
3156 void client_shade(ObClient *self, gboolean shade)
3157 {
3158     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3159          shade) ||                         /* can't shade */
3160         self->shaded == shade) return;     /* already done */
3161
3162     self->shaded = shade;
3163     client_change_state(self);
3164     client_change_wm_state(self); /* the window is being hidden/shown */
3165     /* resize the frame to just the titlebar */
3166     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
3167 }
3168
3169 void client_close(ObClient *self)
3170 {
3171     XEvent ce;
3172
3173     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3174
3175     /* in the case that the client provides no means to requesting that it
3176        close, we just kill it */
3177     if (!self->delete_window)
3178         client_kill(self);
3179     
3180     /*
3181       XXX: itd be cool to do timeouts and shit here for killing the client's
3182       process off
3183       like... if the window is around after 5 seconds, then the close button
3184       turns a nice red, and if this function is called again, the client is
3185       explicitly killed.
3186     */
3187
3188     ce.xclient.type = ClientMessage;
3189     ce.xclient.message_type =  prop_atoms.wm_protocols;
3190     ce.xclient.display = ob_display;
3191     ce.xclient.window = self->window;
3192     ce.xclient.format = 32;
3193     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
3194     ce.xclient.data.l[1] = event_curtime;
3195     ce.xclient.data.l[2] = 0l;
3196     ce.xclient.data.l[3] = 0l;
3197     ce.xclient.data.l[4] = 0l;
3198     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3199 }
3200
3201 void client_kill(ObClient *self)
3202 {
3203     XKillClient(ob_display, self->window);
3204 }
3205
3206 void client_hilite(ObClient *self, gboolean hilite)
3207 {
3208     if (self->demands_attention == hilite)
3209         return; /* no change */
3210
3211     /* don't allow focused windows to hilite */
3212     self->demands_attention = hilite && !client_focused(self);
3213     if (self->frame != NULL) { /* if we're mapping, just set the state */
3214         if (self->demands_attention)
3215             frame_flash_start(self->frame);
3216         else
3217             frame_flash_stop(self->frame);
3218         client_change_state(self);
3219     }
3220 }
3221
3222 void client_set_desktop_recursive(ObClient *self,
3223                                   guint target,
3224                                   gboolean donthide)
3225 {
3226     guint old;
3227     GSList *it;
3228
3229     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3230
3231         ob_debug("Setting desktop %u\n", target+1);
3232
3233         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3234
3235         old = self->desktop;
3236         self->desktop = target;
3237         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3238         /* the frame can display the current desktop state */
3239         frame_adjust_state(self->frame);
3240         /* 'move' the window to the new desktop */
3241         if (!donthide)
3242             client_showhide(self);
3243         /* raise if it was not already on the desktop */
3244         if (old != DESKTOP_ALL)
3245             stacking_raise(CLIENT_AS_WINDOW(self));
3246         if (STRUT_EXISTS(self->strut))
3247             screen_update_areas();
3248     }
3249
3250     /* move all transients */
3251     for (it = self->transients; it; it = g_slist_next(it))
3252         if (it->data != self)
3253             if (client_is_direct_child(self, it->data))
3254                 client_set_desktop_recursive(it->data, target, donthide);
3255 }
3256
3257 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
3258 {
3259     self = client_search_top_normal_parent(self);
3260     client_set_desktop_recursive(self, target, donthide);
3261 }
3262
3263 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3264 {
3265     while (child != parent &&
3266            child->transient_for && child->transient_for != OB_TRAN_GROUP)
3267         child = child->transient_for;
3268     return child == parent;
3269 }
3270
3271 ObClient *client_search_modal_child(ObClient *self)
3272 {
3273     GSList *it;
3274     ObClient *ret;
3275   
3276     for (it = self->transients; it; it = g_slist_next(it)) {
3277         ObClient *c = it->data;
3278         if ((ret = client_search_modal_child(c))) return ret;
3279         if (c->modal) return c;
3280     }
3281     return NULL;
3282 }
3283
3284 gboolean client_validate(ObClient *self)
3285 {
3286     XEvent e; 
3287
3288     XSync(ob_display, FALSE); /* get all events on the server */
3289
3290     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3291         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3292         XPutBackEvent(ob_display, &e);
3293         return FALSE;
3294     }
3295
3296     return TRUE;
3297 }
3298
3299 void client_set_wm_state(ObClient *self, glong state)
3300 {
3301     if (state == self->wmstate) return; /* no change */
3302   
3303     switch (state) {
3304     case IconicState:
3305         client_iconify(self, TRUE, TRUE, FALSE);
3306         break;
3307     case NormalState:
3308         client_iconify(self, FALSE, TRUE, FALSE);
3309         break;
3310     }
3311 }
3312
3313 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3314 {
3315     gboolean shaded = self->shaded;
3316     gboolean fullscreen = self->fullscreen;
3317     gboolean undecorated = self->undecorated;
3318     gboolean max_horz = self->max_horz;
3319     gboolean max_vert = self->max_vert;
3320     gboolean modal = self->modal;
3321     gboolean iconic = self->iconic;
3322     gboolean demands_attention = self->demands_attention;
3323     gboolean above = self->above;
3324     gboolean below = self->below;
3325     gint i;
3326
3327     if (!(action == prop_atoms.net_wm_state_add ||
3328           action == prop_atoms.net_wm_state_remove ||
3329           action == prop_atoms.net_wm_state_toggle))
3330         /* an invalid action was passed to the client message, ignore it */
3331         return; 
3332
3333     for (i = 0; i < 2; ++i) {
3334         Atom state = i == 0 ? data1 : data2;
3335     
3336         if (!state) continue;
3337
3338         /* if toggling, then pick whether we're adding or removing */
3339         if (action == prop_atoms.net_wm_state_toggle) {
3340             if (state == prop_atoms.net_wm_state_modal)
3341                 action = modal ? prop_atoms.net_wm_state_remove :
3342                     prop_atoms.net_wm_state_add;
3343             else if (state == prop_atoms.net_wm_state_maximized_vert)
3344                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3345                     prop_atoms.net_wm_state_add;
3346             else if (state == prop_atoms.net_wm_state_maximized_horz)
3347                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3348                     prop_atoms.net_wm_state_add;
3349             else if (state == prop_atoms.net_wm_state_shaded)
3350                 action = shaded ? prop_atoms.net_wm_state_remove :
3351                     prop_atoms.net_wm_state_add;
3352             else if (state == prop_atoms.net_wm_state_skip_taskbar)
3353                 action = self->skip_taskbar ?
3354                     prop_atoms.net_wm_state_remove :
3355                     prop_atoms.net_wm_state_add;
3356             else if (state == prop_atoms.net_wm_state_skip_pager)
3357                 action = self->skip_pager ?
3358                     prop_atoms.net_wm_state_remove :
3359                     prop_atoms.net_wm_state_add;
3360             else if (state == prop_atoms.net_wm_state_hidden)
3361                 action = self->iconic ?
3362                     prop_atoms.net_wm_state_remove :
3363                     prop_atoms.net_wm_state_add;
3364             else if (state == prop_atoms.net_wm_state_fullscreen)
3365                 action = fullscreen ?
3366                     prop_atoms.net_wm_state_remove :
3367                     prop_atoms.net_wm_state_add;
3368             else if (state == prop_atoms.net_wm_state_above)
3369                 action = self->above ? prop_atoms.net_wm_state_remove :
3370                     prop_atoms.net_wm_state_add;
3371             else if (state == prop_atoms.net_wm_state_below)
3372                 action = self->below ? prop_atoms.net_wm_state_remove :
3373                     prop_atoms.net_wm_state_add;
3374             else if (state == prop_atoms.net_wm_state_demands_attention)
3375                 action = self->demands_attention ?
3376                     prop_atoms.net_wm_state_remove :
3377                     prop_atoms.net_wm_state_add;
3378             else if (state == prop_atoms.ob_wm_state_undecorated)
3379                 action = undecorated ? prop_atoms.net_wm_state_remove :
3380                     prop_atoms.net_wm_state_add;
3381         }
3382     
3383         if (action == prop_atoms.net_wm_state_add) {
3384             if (state == prop_atoms.net_wm_state_modal) {
3385                 modal = TRUE;
3386             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3387                 max_vert = TRUE;
3388             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3389                 max_horz = TRUE;
3390             } else if (state == prop_atoms.net_wm_state_shaded) {
3391                 shaded = TRUE;
3392             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3393                 self->skip_taskbar = TRUE;
3394             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3395                 self->skip_pager = TRUE;
3396             } else if (state == prop_atoms.net_wm_state_hidden) {
3397                 iconic = TRUE;
3398             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3399                 fullscreen = TRUE;
3400             } else if (state == prop_atoms.net_wm_state_above) {
3401                 above = TRUE;
3402                 below = FALSE;
3403             } else if (state == prop_atoms.net_wm_state_below) {
3404                 above = FALSE;
3405                 below = TRUE;
3406             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3407                 demands_attention = TRUE;
3408             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3409                 undecorated = TRUE;
3410             }
3411
3412         } else { /* action == prop_atoms.net_wm_state_remove */
3413             if (state == prop_atoms.net_wm_state_modal) {
3414                 modal = FALSE;
3415             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3416                 max_vert = FALSE;
3417             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3418                 max_horz = FALSE;
3419             } else if (state == prop_atoms.net_wm_state_shaded) {
3420                 shaded = FALSE;
3421             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3422                 self->skip_taskbar = FALSE;
3423             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3424                 self->skip_pager = FALSE;
3425             } else if (state == prop_atoms.net_wm_state_hidden) {
3426                 iconic = FALSE;
3427             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3428                 fullscreen = FALSE;
3429             } else if (state == prop_atoms.net_wm_state_above) {
3430                 above = FALSE;
3431             } else if (state == prop_atoms.net_wm_state_below) {
3432                 below = FALSE;
3433             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3434                 demands_attention = FALSE;
3435             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3436                 undecorated = FALSE;
3437             }
3438         }
3439     }
3440
3441     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3442         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3443             /* toggling both */
3444             if (max_horz == max_vert) { /* both going the same way */
3445                 client_maximize(self, max_horz, 0);
3446             } else {
3447                 client_maximize(self, max_horz, 1);
3448                 client_maximize(self, max_vert, 2);
3449             }
3450         } else {
3451             /* toggling one */
3452             if (max_horz != self->max_horz)
3453                 client_maximize(self, max_horz, 1);
3454             else
3455                 client_maximize(self, max_vert, 2);
3456         }
3457     }
3458     /* change fullscreen state before shading, as it will affect if the window
3459        can shade or not */
3460     if (fullscreen != self->fullscreen)
3461         client_fullscreen(self, fullscreen);
3462     if (shaded != self->shaded)
3463         client_shade(self, shaded);
3464     if (undecorated != self->undecorated)
3465         client_set_undecorated(self, undecorated);
3466     if (above != self->above || below != self->below) {
3467         self->above = above;
3468         self->below = below;
3469         client_calc_layer(self);
3470     }
3471
3472     if (modal != self->modal) {
3473         self->modal = modal;
3474         /* when a window changes modality, then its stacking order with its
3475            transients needs to change */
3476         stacking_raise(CLIENT_AS_WINDOW(self));
3477
3478         /* it also may get focused. if something is focused that shouldn't
3479            be focused anymore, then move the focus */
3480         if (focus_client && client_focus_target(focus_client) != focus_client)
3481             client_focus(focus_client);
3482     }
3483
3484     if (iconic != self->iconic)
3485         client_iconify(self, iconic, FALSE, FALSE);
3486
3487     if (demands_attention != self->demands_attention)
3488         client_hilite(self, demands_attention);
3489
3490     client_change_state(self); /* change the hint to reflect these changes */
3491 }
3492
3493 ObClient *client_focus_target(ObClient *self)
3494 {
3495     ObClient *child = NULL;
3496
3497     child = client_search_modal_child(self);
3498     if (child) return child;
3499     return self;
3500 }
3501
3502 gboolean client_can_focus(ObClient *self)
3503 {
3504     /* choose the correct target */
3505     self = client_focus_target(self);
3506
3507     if (!self->frame->visible)
3508         return FALSE;
3509
3510     if (!(self->can_focus || self->focus_notify))
3511         return FALSE;
3512
3513     return TRUE;
3514 }
3515
3516 gboolean client_focus(ObClient *self)
3517 {
3518     /* we might not focus this window, so if we have modal children which would
3519        be focused instead, bring them to this desktop */
3520     client_bring_modal_windows(self);
3521
3522     /* choose the correct target */
3523     self = client_focus_target(self);
3524
3525     if (!client_can_focus(self)) {
3526         ob_debug_type(OB_DEBUG_FOCUS,
3527                       "Client %s can't be focused\n", self->title);
3528         return FALSE;
3529     }
3530
3531     ob_debug_type(OB_DEBUG_FOCUS,
3532                   "Focusing client \"%s\" (0x%x) at time %u\n",
3533                   self->title, self->window, event_curtime);
3534
3535     /* if there is a grab going on, then we need to cancel it. if we move
3536        focus during the grab, applications will get NotifyWhileGrabbed events
3537        and ignore them !
3538
3539        actions should not rely on being able to move focus during an
3540        interactive grab.
3541     */
3542     event_cancel_all_key_grabs();
3543
3544     xerror_set_ignore(TRUE);
3545     xerror_occured = FALSE;
3546
3547     if (self->can_focus) {
3548         /* This can cause a BadMatch error with CurrentTime, or if an app
3549            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3550         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3551                        event_curtime);
3552     }
3553
3554     if (self->focus_notify) {
3555         XEvent ce;
3556         ce.xclient.type = ClientMessage;
3557         ce.xclient.message_type = prop_atoms.wm_protocols;
3558         ce.xclient.display = ob_display;
3559         ce.xclient.window = self->window;
3560         ce.xclient.format = 32;
3561         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3562         ce.xclient.data.l[1] = event_curtime;
3563         ce.xclient.data.l[2] = 0l;
3564         ce.xclient.data.l[3] = 0l;
3565         ce.xclient.data.l[4] = 0l;
3566         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3567     }
3568
3569     xerror_set_ignore(FALSE);
3570
3571     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d\n", xerror_occured);
3572     return !xerror_occured;
3573 }
3574
3575 /*! Present the client to the user.
3576   @param raise If the client should be raised or not. You should only set
3577                raise to false if you don't care if the window is completely
3578                hidden.
3579 */
3580 static void client_present(ObClient *self, gboolean here, gboolean raise)
3581 {
3582     /* if using focus_delay, stop the timer now so that focus doesn't
3583        go moving on us */
3584     event_halt_focus_delay();
3585
3586     if (client_normal(self) && screen_showing_desktop)
3587         screen_show_desktop(FALSE, self);
3588     if (self->iconic)
3589         client_iconify(self, FALSE, here, FALSE);
3590     if (self->desktop != DESKTOP_ALL &&
3591         self->desktop != screen_desktop)
3592     {
3593         if (here)
3594             client_set_desktop(self, screen_desktop, FALSE);
3595         else
3596             screen_set_desktop(self->desktop, FALSE);
3597     } else if (!self->frame->visible)
3598         /* if its not visible for other reasons, then don't mess
3599            with it */
3600         return;
3601     if (self->shaded)
3602         client_shade(self, FALSE);
3603     if (raise)
3604         stacking_raise(CLIENT_AS_WINDOW(self));
3605
3606     client_focus(self);
3607 }
3608
3609 void client_activate(ObClient *self, gboolean here, gboolean user)
3610 {
3611     guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3612     gboolean allow = FALSE;
3613
3614     /* if the request came from the user, or if nothing is focused, then grant
3615        the request.
3616        if the currently focused app doesn't set a user_time, then it can't
3617        benefit from any focus stealing prevention.
3618     */
3619     if (user || !focus_client || !last_time)
3620         allow = TRUE;
3621     /* otherwise, if they didn't give a time stamp or if it is too old, they
3622        don't get focus */
3623     else
3624         allow = event_curtime && event_time_after(event_curtime, last_time);
3625
3626     ob_debug_type(OB_DEBUG_FOCUS,
3627                   "Want to activate window 0x%x with time %u (last time %u), "
3628                   "source=%s allowing? %d\n",
3629                   self->window, event_curtime, last_time,
3630                   (user ? "user" : "application"), allow);
3631
3632     if (allow)
3633         client_present(self, here, TRUE);
3634     else
3635         /* don't focus it but tell the user it wants attention */
3636         client_hilite(self, TRUE);
3637 }
3638
3639 static void client_bring_windows_recursive(ObClient *self,
3640                                            guint desktop,
3641                                            gboolean helpers,
3642                                            gboolean modals,
3643                                            gboolean iconic)
3644 {
3645     GSList *it;
3646
3647     for (it = self->transients; it; it = g_slist_next(it))
3648         client_bring_windows_recursive(it->data, desktop,
3649                                        helpers, modals, iconic);
3650
3651     if (((helpers && client_helper(self)) ||
3652          (modals && self->modal)) &&
3653         ((self->desktop != desktop && self->desktop != DESKTOP_ALL) ||
3654          (iconic && self->iconic)))
3655     {
3656         if (iconic && self->iconic)
3657             client_iconify(self, FALSE, TRUE, FALSE);
3658         else
3659             client_set_desktop(self, desktop, FALSE);
3660     }
3661 }
3662
3663 void client_bring_helper_windows(ObClient *self)
3664 {
3665     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
3666 }
3667
3668 void client_bring_modal_windows(ObClient *self)
3669 {
3670     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
3671 }
3672
3673 gboolean client_focused(ObClient *self)
3674 {
3675     return self == focus_client;
3676 }
3677
3678 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3679 {
3680     guint i;
3681     gulong min_diff, min_i;
3682
3683     if (!self->nicons) {
3684         ObClientIcon *parent = NULL;
3685
3686         if (self->transient_for) {
3687             if (self->transient_for != OB_TRAN_GROUP)
3688                 parent = client_icon_recursive(self->transient_for, w, h);
3689             else {
3690                 GSList *it;
3691                 for (it = self->group->members; it; it = g_slist_next(it)) {
3692                     ObClient *c = it->data;
3693                     if (c != self && !c->transient_for) {
3694                         if ((parent = client_icon_recursive(c, w, h)))
3695                             break;
3696                     }
3697                 }
3698             }
3699         }
3700         
3701         return parent;
3702     }
3703
3704     /* some kind of crappy approximation to find the icon closest in size to
3705        what we requested, but icons are generally all the same ratio as
3706        eachother so it's good enough. */
3707
3708     min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
3709     min_i = 0;
3710
3711     for (i = 1; i < self->nicons; ++i) {
3712         gulong diff;
3713
3714         diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
3715         if (diff < min_diff) {
3716             min_diff = diff;
3717             min_i = i;
3718         }
3719     }
3720     return &self->icons[min_i];
3721 }
3722
3723 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3724 {
3725     ObClientIcon *ret;
3726     static ObClientIcon deficon;
3727
3728     if (!(ret = client_icon_recursive(self, w, h))) {
3729         deficon.width = deficon.height = 48;
3730         deficon.data = ob_rr_theme->def_win_icon;
3731         ret = &deficon;
3732     }
3733     return ret;
3734 }
3735
3736 void client_set_layer(ObClient *self, gint layer)
3737 {
3738     if (layer < 0) {
3739         self->below = TRUE;
3740         self->above = FALSE;
3741     } else if (layer == 0) {
3742         self->below = self->above = FALSE;
3743     } else {
3744         self->below = FALSE;
3745         self->above = TRUE;
3746     }
3747     client_calc_layer(self);
3748     client_change_state(self); /* reflect this in the state hints */
3749 }
3750
3751 void client_set_undecorated(ObClient *self, gboolean undecorated)
3752 {
3753     if (self->undecorated != undecorated &&
3754         /* don't let it undecorate if the function is missing, but let 
3755            it redecorate */
3756         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3757     {
3758         self->undecorated = undecorated;
3759         client_setup_decor_and_functions(self, TRUE);
3760         client_change_state(self); /* reflect this in the state hints */
3761     }
3762 }
3763
3764 guint client_monitor(ObClient *self)
3765 {
3766     return screen_find_monitor(&self->frame->area);
3767 }
3768
3769 ObClient *client_search_top_normal_parent(ObClient *self)
3770 {
3771     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3772            client_normal(self->transient_for))
3773         self = self->transient_for;
3774     return self;
3775 }
3776
3777 static GSList *client_search_all_top_parents_internal(ObClient *self,
3778                                                       gboolean bylayer,
3779                                                       ObStackingLayer layer)
3780 {
3781     GSList *ret = NULL;
3782     
3783     /* move up the direct transient chain as far as possible */
3784     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3785            (!bylayer || self->transient_for->layer == layer) &&
3786            client_normal(self->transient_for))
3787         self = self->transient_for;
3788
3789     if (!self->transient_for)
3790         ret = g_slist_prepend(ret, self);
3791     else {
3792             GSList *it;
3793
3794             g_assert(self->group);
3795
3796             for (it = self->group->members; it; it = g_slist_next(it)) {
3797                 ObClient *c = it->data;
3798
3799                 if (!c->transient_for && client_normal(c) &&
3800                     (!bylayer || c->layer == layer))
3801                 {
3802                     ret = g_slist_prepend(ret, c);
3803                 }
3804             }
3805
3806             if (ret == NULL) /* no group parents */
3807                 ret = g_slist_prepend(ret, self);
3808     }
3809
3810     return ret;
3811 }
3812
3813 GSList *client_search_all_top_parents(ObClient *self)
3814 {
3815     return client_search_all_top_parents_internal(self, FALSE, 0);
3816 }
3817
3818 GSList *client_search_all_top_parents_layer(ObClient *self)
3819 {
3820     return client_search_all_top_parents_internal(self, TRUE, self->layer);
3821 }
3822
3823 ObClient *client_search_focus_parent(ObClient *self)
3824 {
3825     if (self->transient_for) {
3826         if (self->transient_for != OB_TRAN_GROUP) {
3827             if (client_focused(self->transient_for))
3828                 return self->transient_for;
3829         } else {
3830             GSList *it;
3831
3832             for (it = self->group->members; it; it = g_slist_next(it)) {
3833                 ObClient *c = it->data;
3834
3835                 /* checking transient_for prevents infinate loops! */
3836                 if (c != self && !c->transient_for)
3837                     if (client_focused(c))
3838                         return c;
3839             }
3840         }
3841     }
3842
3843     return NULL;
3844 }
3845
3846 ObClient *client_search_parent(ObClient *self, ObClient *search)
3847 {
3848     if (self->transient_for) {
3849         if (self->transient_for != OB_TRAN_GROUP) {
3850             if (self->transient_for == search)
3851                 return search;
3852         } else {
3853             GSList *it;
3854
3855             for (it = self->group->members; it; it = g_slist_next(it)) {
3856                 ObClient *c = it->data;
3857
3858                 /* checking transient_for prevents infinate loops! */
3859                 if (c != self && !c->transient_for)
3860                     if (c == search)
3861                         return search;
3862             }
3863         }
3864     }
3865
3866     return NULL;
3867 }
3868
3869 ObClient *client_search_transient(ObClient *self, ObClient *search)
3870 {
3871     GSList *sit;
3872
3873     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3874         if (sit->data == search)
3875             return search;
3876         if (client_search_transient(sit->data, search))
3877             return search;
3878     }
3879     return NULL;
3880 }
3881
3882 #define WANT_EDGE(cur, c) \
3883             if (cur == c)                                                     \
3884                 continue;                                                     \
3885             if (c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&  \
3886                 cur->desktop != screen_desktop)                               \
3887                 continue;                                                     \
3888             if (cur->iconic)                                                  \
3889                 continue;
3890
3891 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3892             if ((his_edge_start >= my_edge_start && \
3893                  his_edge_start <= my_edge_end) ||  \
3894                 (my_edge_start >= his_edge_start && \
3895                  my_edge_start <= his_edge_end))    \
3896                 dest = his_offset;
3897
3898 /* finds the nearest edge in the given direction from the current client
3899  * note to self: the edge is the -frame- edge (the actual one), not the
3900  * client edge.
3901  */
3902 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3903 {
3904     gint dest, monitor_dest;
3905     gint my_edge_start, my_edge_end, my_offset;
3906     GList *it;
3907     Rect *a, *monitor;
3908     
3909     if(!client_list)
3910         return -1;
3911
3912     a = screen_area(c->desktop);
3913     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3914
3915     switch(dir) {
3916     case OB_DIRECTION_NORTH:
3917         my_edge_start = c->frame->area.x;
3918         my_edge_end = c->frame->area.x + c->frame->area.width;
3919         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3920         
3921         /* default: top of screen */
3922         dest = a->y + (hang ? c->frame->area.height : 0);
3923         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3924         /* if the monitor edge comes before the screen edge, */
3925         /* use that as the destination instead. (For xinerama) */
3926         if (monitor_dest != dest && my_offset > monitor_dest)
3927             dest = monitor_dest; 
3928
3929         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3930             gint his_edge_start, his_edge_end, his_offset;
3931             ObClient *cur = it->data;
3932
3933             WANT_EDGE(cur, c)
3934
3935             his_edge_start = cur->frame->area.x;
3936             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3937             his_offset = cur->frame->area.y + 
3938                          (hang ? 0 : cur->frame->area.height);
3939
3940             if(his_offset + 1 > my_offset)
3941                 continue;
3942
3943             if(his_offset < dest)
3944                 continue;
3945
3946             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3947         }
3948         break;
3949     case OB_DIRECTION_SOUTH:
3950         my_edge_start = c->frame->area.x;
3951         my_edge_end = c->frame->area.x + c->frame->area.width;
3952         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3953
3954         /* default: bottom of screen */
3955         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3956         monitor_dest = monitor->y + monitor->height -
3957                        (hang ? c->frame->area.height : 0);
3958         /* if the monitor edge comes before the screen edge, */
3959         /* use that as the destination instead. (For xinerama) */
3960         if (monitor_dest != dest && my_offset < monitor_dest)
3961             dest = monitor_dest; 
3962
3963         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3964             gint his_edge_start, his_edge_end, his_offset;
3965             ObClient *cur = it->data;
3966
3967             WANT_EDGE(cur, c)
3968
3969             his_edge_start = cur->frame->area.x;
3970             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3971             his_offset = cur->frame->area.y +
3972                          (hang ? cur->frame->area.height : 0);
3973
3974
3975             if(his_offset - 1 < my_offset)
3976                 continue;
3977             
3978             if(his_offset > dest)
3979                 continue;
3980
3981             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3982         }
3983         break;
3984     case OB_DIRECTION_WEST:
3985         my_edge_start = c->frame->area.y;
3986         my_edge_end = c->frame->area.y + c->frame->area.height;
3987         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3988
3989         /* default: leftmost egde of screen */
3990         dest = a->x + (hang ? c->frame->area.width : 0);
3991         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3992         /* if the monitor edge comes before the screen edge, */
3993         /* use that as the destination instead. (For xinerama) */
3994         if (monitor_dest != dest && my_offset > monitor_dest)
3995             dest = monitor_dest;            
3996
3997         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3998             gint his_edge_start, his_edge_end, his_offset;
3999             ObClient *cur = it->data;
4000
4001             WANT_EDGE(cur, c)
4002
4003             his_edge_start = cur->frame->area.y;
4004             his_edge_end = cur->frame->area.y + cur->frame->area.height;
4005             his_offset = cur->frame->area.x +
4006                          (hang ? 0 : cur->frame->area.width);
4007
4008             if(his_offset + 1 > my_offset)
4009                 continue;
4010
4011             if(his_offset < dest)
4012                 continue;
4013
4014             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
4015         }
4016        break;
4017     case OB_DIRECTION_EAST:
4018         my_edge_start = c->frame->area.y;
4019         my_edge_end = c->frame->area.y + c->frame->area.height;
4020         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
4021         
4022         /* default: rightmost edge of screen */
4023         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
4024         monitor_dest = monitor->x + monitor->width -
4025                        (hang ? c->frame->area.width : 0);
4026         /* if the monitor edge comes before the screen edge, */
4027         /* use that as the destination instead. (For xinerama) */
4028         if (monitor_dest != dest && my_offset < monitor_dest)
4029             dest = monitor_dest;            
4030
4031         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
4032             gint his_edge_start, his_edge_end, his_offset;
4033             ObClient *cur = it->data;
4034
4035             WANT_EDGE(cur, c)
4036
4037             his_edge_start = cur->frame->area.y;
4038             his_edge_end = cur->frame->area.y + cur->frame->area.height;
4039             his_offset = cur->frame->area.x +
4040                          (hang ? cur->frame->area.width : 0);
4041
4042             if(his_offset - 1 < my_offset)
4043                 continue;
4044             
4045             if(his_offset > dest)
4046                 continue;
4047
4048             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
4049         }
4050         break;
4051     case OB_DIRECTION_NORTHEAST:
4052     case OB_DIRECTION_SOUTHEAST:
4053     case OB_DIRECTION_NORTHWEST:
4054     case OB_DIRECTION_SOUTHWEST:
4055         /* not implemented */
4056     default:
4057         g_assert_not_reached();
4058         dest = 0; /* suppress warning */
4059     }
4060     return dest;
4061 }
4062
4063 ObClient* client_under_pointer()
4064 {
4065     gint x, y;
4066     GList *it;
4067     ObClient *ret = NULL;
4068
4069     if (screen_pointer_pos(&x, &y)) {
4070         for (it = stacking_list; it; it = g_list_next(it)) {
4071             if (WINDOW_IS_CLIENT(it->data)) {
4072                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4073                 if (c->frame->visible &&
4074                     /* ignore all animating windows */
4075                     !frame_iconify_animating(c->frame) &&
4076                     RECT_CONTAINS(c->frame->area, x, y))
4077                 {
4078                     ret = c;
4079                     break;
4080                 }
4081             }
4082         }
4083     }
4084     return ret;
4085 }
4086
4087 gboolean client_has_group_siblings(ObClient *self)
4088 {
4089     return self->group && self->group->members->next;
4090 }