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