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