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