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