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