better group_full func
[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     } else
2407         if (client_focused(c)) return c;
2408     return NULL;
2409 }
2410
2411 gboolean client_has_parent(ObClient *self)
2412 {
2413     if (self->transient_for) {
2414         if (self->transient_for != OB_TRAN_GROUP) {
2415             if (client_normal(self->transient_for))
2416                 return TRUE;
2417         }
2418         else if (self->group) {
2419             GSList *it;
2420
2421             for (it = self->group->members; it; it = g_slist_next(it)) {
2422                 if (it->data != self && client_normal(it->data))
2423                     return TRUE;
2424             }
2425         }
2426     }
2427     return FALSE;
2428 }
2429
2430 static ObStackingLayer calc_layer(ObClient *self)
2431 {
2432     ObStackingLayer l;
2433
2434     if (self->type == OB_CLIENT_TYPE_DESKTOP)
2435         l = OB_STACKING_LAYER_DESKTOP;
2436     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2437         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2438         else l = OB_STACKING_LAYER_ABOVE;
2439     }
2440     else if ((self->fullscreen ||
2441               /* No decorations and fills the monitor = oldskool fullscreen.
2442                  But not for maximized windows.
2443               */
2444               (self->decorations == 0 &&
2445                !(self->max_horz && self->max_vert) &&
2446                RECT_EQUAL(self->area,
2447                           *screen_physical_area_monitor
2448                           (client_monitor(self))))) &&
2449              (client_focused(self) || client_search_focus_tree(self)))
2450         l = OB_STACKING_LAYER_FULLSCREEN;
2451     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2452     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2453     else l = OB_STACKING_LAYER_NORMAL;
2454
2455     return l;
2456 }
2457
2458 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2459                                         ObStackingLayer min)
2460 {
2461     ObStackingLayer old, own;
2462     GSList *it;
2463
2464     old = self->layer;
2465     own = calc_layer(self);
2466     self->layer = MAX(own, min);
2467
2468     if (self->layer != old) {
2469         stacking_remove(CLIENT_AS_WINDOW(self));
2470         stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
2471     }
2472
2473     for (it = self->transients; it; it = g_slist_next(it))
2474         client_calc_layer_recursive(it->data, orig,
2475                                     self->layer);
2476 }
2477
2478 void client_calc_layer(ObClient *self)
2479 {
2480     ObClient *orig;
2481     GSList *it;
2482
2483     orig = self;
2484
2485     /* transients take on the layer of their parents */
2486     it = client_search_all_top_parents(self);
2487
2488     for (; it; it = g_slist_next(it))
2489         client_calc_layer_recursive(it->data, orig, 0);
2490 }
2491
2492 gboolean client_should_show(ObClient *self)
2493 {
2494     if (self->iconic)
2495         return FALSE;
2496     if (client_normal(self) && screen_showing_desktop)
2497         return FALSE;
2498     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2499         return TRUE;
2500     
2501     return FALSE;
2502 }
2503
2504 gboolean client_show(ObClient *self)
2505 {
2506     gboolean show = FALSE;
2507
2508     if (client_should_show(self)) {
2509         frame_show(self->frame);
2510         show = TRUE;
2511
2512         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2513            it needs to be in IconicState. This includes when it is on another
2514            desktop!
2515         */
2516         client_change_wm_state(self);
2517     }
2518     return show;
2519 }
2520
2521 gboolean client_hide(ObClient *self)
2522 {
2523     gboolean hide = FALSE;
2524
2525     if (!client_should_show(self)) {
2526         if (self == focus_client) {
2527             /* if there is a grab going on, then we need to cancel it. if we
2528                move focus during the grab, applications will get
2529                NotifyWhileGrabbed events and ignore them !
2530
2531                actions should not rely on being able to move focus during an
2532                interactive grab.
2533             */
2534             event_cancel_all_key_grabs();
2535         }
2536
2537         frame_hide(self->frame);
2538         hide = TRUE;
2539
2540         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2541            it needs to be in IconicState. This includes when it is on another
2542            desktop!
2543         */
2544         client_change_wm_state(self);
2545     }
2546     return hide;
2547 }
2548
2549 void client_showhide(ObClient *self)
2550 {
2551     if (!client_show(self))
2552         client_hide(self);
2553 }
2554
2555 gboolean client_normal(ObClient *self) {
2556     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2557               self->type == OB_CLIENT_TYPE_DOCK ||
2558               self->type == OB_CLIENT_TYPE_SPLASH);
2559 }
2560
2561 gboolean client_helper(ObClient *self)
2562 {
2563     return (self->type == OB_CLIENT_TYPE_UTILITY ||
2564             self->type == OB_CLIENT_TYPE_MENU ||
2565             self->type == OB_CLIENT_TYPE_TOOLBAR);
2566 }
2567
2568 gboolean client_mouse_focusable(ObClient *self)
2569 {
2570     return !(self->type == OB_CLIENT_TYPE_MENU ||
2571              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2572              self->type == OB_CLIENT_TYPE_SPLASH ||
2573              self->type == OB_CLIENT_TYPE_DOCK);
2574 }
2575
2576 gboolean client_enter_focusable(ObClient *self)
2577 {
2578     /* you can focus desktops but it shouldn't on enter */
2579     return (client_mouse_focusable(self) &&
2580             self->type != OB_CLIENT_TYPE_DESKTOP);
2581 }
2582
2583
2584 static void client_apply_startup_state(ObClient *self,
2585                                        gint x, gint y, gint w, gint h)
2586 {
2587     /* save the states that we are going to apply */
2588     gboolean iconic = self->iconic;
2589     gboolean fullscreen = self->fullscreen;
2590     gboolean undecorated = self->undecorated;
2591     gboolean shaded = self->shaded;
2592     gboolean demands_attention = self->demands_attention;
2593     gboolean max_horz = self->max_horz;
2594     gboolean max_vert = self->max_vert;
2595     Rect oldarea;
2596     gint l;
2597
2598     /* turn them all off in the client, so they won't affect the window
2599        being placed */
2600     self->iconic = self->fullscreen = self->undecorated = self->shaded =
2601         self->demands_attention = self->max_horz = self->max_vert = FALSE;
2602
2603     /* move the client to its placed position, or it it's already there,
2604        generate a ConfigureNotify telling the client where it is.
2605
2606        do this after adjusting the frame. otherwise it gets all weird and
2607        clients don't work right
2608
2609        do this before applying the states so they have the correct
2610        pre-max/pre-fullscreen values
2611     */
2612     client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE);
2613     ob_debug("placed window 0x%x at %d, %d with size %d x %d\n",
2614              self->window, self->area.x, self->area.y,
2615              self->area.width, self->area.height);
2616     oldarea = self->area;              /* save the area */
2617     RECT_SET(self->area, x, y, w, h);  /* put where it should be for the premax stuff */
2618
2619     /* apply the states. these are in a carefully crafted order.. */
2620
2621     if (iconic)
2622         client_iconify(self, TRUE, FALSE, TRUE);
2623     if (fullscreen)
2624         client_fullscreen(self, TRUE);
2625     if (undecorated)
2626         client_set_undecorated(self, TRUE);
2627     if (shaded)
2628         client_shade(self, TRUE);
2629     if (demands_attention)
2630         client_hilite(self, TRUE);
2631   
2632     if (max_vert && max_horz)
2633         client_maximize(self, TRUE, 0);
2634     else if (max_vert)
2635         client_maximize(self, TRUE, 2);
2636     else if (max_horz)
2637         client_maximize(self, TRUE, 1);
2638
2639     /* if the window hasn't been configured yet, then do so now */
2640     if (!fullscreen && !max_vert && !max_horz) {
2641         self->area = oldarea;
2642         client_configure(self, x, y, w, h, FALSE, TRUE);
2643     }
2644
2645     /* set the desktop hint, to make sure that it always exists */
2646     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
2647
2648     /* nothing to do for the other states:
2649        skip_taskbar
2650        skip_pager
2651        modal
2652        above
2653        below
2654     */
2655 }
2656
2657 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2658 {
2659     /* these should be the current values. this is for when you're not moving,
2660        just resizing */
2661     g_assert(*x == self->area.x);
2662     g_assert(oldw == self->area.width);
2663
2664     /* horizontal */
2665     switch (self->gravity) {
2666     default:
2667     case NorthWestGravity:
2668     case WestGravity:
2669     case SouthWestGravity:
2670     case StaticGravity:
2671     case ForgetGravity:
2672         break;
2673     case NorthGravity:
2674     case CenterGravity:
2675     case SouthGravity:
2676         *x -= (neww - oldw) / 2;
2677         break;
2678     case NorthEastGravity:
2679     case EastGravity:
2680     case SouthEastGravity:
2681         *x -= neww - oldw;
2682         break;
2683     }
2684 }
2685
2686 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2687 {
2688     /* these should be the current values. this is for when you're not moving,
2689        just resizing */
2690     g_assert(*y == self->area.y);
2691     g_assert(oldh == self->area.height);
2692
2693     /* vertical */
2694     switch (self->gravity) {
2695     default:
2696     case NorthWestGravity:
2697     case NorthGravity:
2698     case NorthEastGravity:
2699     case StaticGravity:
2700     case ForgetGravity:
2701         break;
2702     case WestGravity:
2703     case CenterGravity:
2704     case EastGravity:
2705         *y -= (newh - oldh) / 2;
2706         break;
2707     case SouthWestGravity:
2708     case SouthGravity:
2709     case SouthEastGravity:
2710         *y -= newh - oldh;
2711         break;
2712     }
2713 }
2714
2715 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2716                           gint *logicalw, gint *logicalh,
2717                           gboolean user)
2718 {
2719     Rect desired_area = {*x, *y, *w, *h};
2720
2721     /* make the frame recalculate its dimentions n shit without changing
2722        anything visible for real, this way the constraints below can work with
2723        the updated frame dimensions. */
2724     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
2725
2726     /* work within the prefered sizes given by the window */
2727     if (!(*w == self->area.width && *h == self->area.height)) {
2728         gint basew, baseh, minw, minh;
2729
2730         /* base size is substituted with min size if not specified */
2731         if (self->base_size.width || self->base_size.height) {
2732             basew = self->base_size.width;
2733             baseh = self->base_size.height;
2734         } else {
2735             basew = self->min_size.width;
2736             baseh = self->min_size.height;
2737         }
2738         /* min size is substituted with base size if not specified */
2739         if (self->min_size.width || self->min_size.height) {
2740             minw = self->min_size.width;
2741             minh = self->min_size.height;
2742         } else {
2743             minw = self->base_size.width;
2744             minh = self->base_size.height;
2745         }
2746
2747         /* if this is a user-requested resize, then check against min/max
2748            sizes */
2749
2750         /* smaller than min size or bigger than max size? */
2751         if (*w > self->max_size.width) *w = self->max_size.width;
2752         if (*w < minw) *w = minw;
2753         if (*h > self->max_size.height) *h = self->max_size.height;
2754         if (*h < minh) *h = minh;
2755
2756         *w -= basew;
2757         *h -= baseh;
2758
2759         /* keep to the increments */
2760         *w /= self->size_inc.width;
2761         *h /= self->size_inc.height;
2762
2763         /* you cannot resize to nothing */
2764         if (basew + *w < 1) *w = 1 - basew;
2765         if (baseh + *h < 1) *h = 1 - baseh;
2766   
2767         /* save the logical size */
2768         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2769         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2770
2771         *w *= self->size_inc.width;
2772         *h *= self->size_inc.height;
2773
2774         *w += basew;
2775         *h += baseh;
2776
2777         /* adjust the height to match the width for the aspect ratios.
2778            for this, min size is not substituted for base size ever. */
2779         *w -= self->base_size.width;
2780         *h -= self->base_size.height;
2781
2782         if (!self->fullscreen) {
2783             if (self->min_ratio)
2784                 if (*h * self->min_ratio > *w) {
2785                     *h = (gint)(*w / self->min_ratio);
2786
2787                     /* you cannot resize to nothing */
2788                     if (*h < 1) {
2789                         *h = 1;
2790                         *w = (gint)(*h * self->min_ratio);
2791                     }
2792                 }
2793             if (self->max_ratio)
2794                 if (*h * self->max_ratio < *w) {
2795                     *h = (gint)(*w / self->max_ratio);
2796
2797                     /* you cannot resize to nothing */
2798                     if (*h < 1) {
2799                         *h = 1;
2800                         *w = (gint)(*h * self->min_ratio);
2801                     }
2802                 }
2803         }
2804
2805         *w += self->base_size.width;
2806         *h += self->base_size.height;
2807     }
2808
2809     /* gets the frame's position */
2810     frame_client_gravity(self->frame, x, y, *w, *h);
2811
2812     /* these positions are frame positions, not client positions */
2813
2814     /* set the size and position if fullscreen */
2815     if (self->fullscreen) {
2816         Rect *a;
2817         guint i;
2818
2819         i = screen_find_monitor(&desired_area);
2820         a = screen_physical_area_monitor(i);
2821
2822         *x = a->x;
2823         *y = a->y;
2824         *w = a->width;
2825         *h = a->height;
2826
2827         user = FALSE; /* ignore if the client can't be moved/resized when it
2828                          is fullscreening */
2829     } else if (self->max_horz || self->max_vert) {
2830         Rect *a;
2831         guint i;
2832
2833         i = screen_find_monitor(&desired_area);
2834         a = screen_area_monitor(self->desktop, i);
2835
2836         /* set the size and position if maximized */
2837         if (self->max_horz) {
2838             *x = a->x;
2839             *w = a->width - self->frame->size.left - self->frame->size.right;
2840         }
2841         if (self->max_vert) {
2842             *y = a->y;
2843             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2844         }
2845
2846         user = FALSE; /* ignore if the client can't be moved/resized when it
2847                          is maximizing */
2848     }
2849
2850     /* gets the client's position */
2851     frame_frame_gravity(self->frame, x, y, *w, *h);
2852
2853     /* these override the above states! if you cant move you can't move! */
2854     if (user) {
2855         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2856             *x = self->area.x;
2857             *y = self->area.y;
2858         }
2859         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2860             *w = self->area.width;
2861             *h = self->area.height;
2862         }
2863     }
2864
2865     g_assert(*w > 0);
2866     g_assert(*h > 0);
2867 }
2868
2869
2870 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
2871                       gboolean user, gboolean final)
2872 {
2873     gint oldw, oldh;
2874     gboolean send_resize_client;
2875     gboolean moved = FALSE, resized = FALSE;
2876     gboolean fmoved, fresized;
2877     guint fdecor = self->frame->decorations;
2878     gboolean fhorz = self->frame->max_horz;
2879     gboolean fvert = self->frame->max_vert;
2880     gint logicalw, logicalh;
2881
2882     /* find the new x, y, width, and height (and logical size) */
2883     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2884
2885     /* set the logical size if things changed */
2886     if (!(w == self->area.width && h == self->area.height))
2887         SIZE_SET(self->logical_size, logicalw, logicalh);
2888
2889     /* figure out if we moved or resized or what */
2890     moved = (x != self->area.x || y != self->area.y);
2891     resized = (w != self->area.width || h != self->area.height);
2892
2893     oldw = self->area.width;
2894     oldh = self->area.height;
2895     RECT_SET(self->area, x, y, w, h);
2896
2897     /* for app-requested resizes, always resize if 'resized' is true.
2898        for user-requested ones, only resize if final is true, or when
2899        resizing in redraw mode */
2900     send_resize_client = ((!user && resized) ||
2901                           (user && (final ||
2902                                     (resized && config_resize_redraw))));
2903
2904     /* if the client is enlarging, then resize the client before the frame */
2905     if (send_resize_client && (w > oldw || h > oldh)) {
2906         XMoveResizeWindow(ob_display, self->window,
2907                           self->frame->size.left, self->frame->size.top,
2908                           MAX(w, oldw), MAX(h, oldh));
2909         frame_adjust_client_area(self->frame);
2910     }
2911
2912     /* find the frame's dimensions and move/resize it */
2913     fmoved = moved;
2914     fresized = resized;
2915
2916     /* if decorations changed, then readjust everything for the frame */
2917     if (self->decorations != fdecor ||
2918         self->max_horz != fhorz || self->max_vert != fvert)
2919     {
2920         fmoved = fresized = TRUE;
2921     }
2922
2923     /* adjust the frame */
2924     if (fmoved || fresized)
2925         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
2926
2927     /* This is kinda tricky and should not be changed.. let me explain!
2928
2929        When user = FALSE, then the request is coming from the application
2930        itself, and we are more strict about when to send a synthetic
2931        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
2932        in this case.
2933
2934        When user = TRUE, then the request is coming from "us", like when we
2935        maximize a window or sometihng.  In this case we are more lenient.  We
2936        used to follow the same rules as above, but _Java_ Swing can't handle
2937        this. So just to appease Swing, when user = TRUE, we always send
2938        a synthetic ConfigureNotify to give the window its root coordinates.
2939     */
2940     if ((!user && !resized) || (user && final))
2941     {
2942         XEvent event;
2943
2944         /* we have reset the client to 0 border width, so don't include
2945            it in these coords */
2946         POINT_SET(self->root_pos,
2947                   self->frame->area.x + self->frame->size.left -
2948                   self->border_width,
2949                   self->frame->area.y + self->frame->size.top -
2950                   self->border_width);
2951
2952         event.type = ConfigureNotify;
2953         event.xconfigure.display = ob_display;
2954         event.xconfigure.event = self->window;
2955         event.xconfigure.window = self->window;
2956
2957         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d\n",
2958                  self->title, self->root_pos.x, self->root_pos.y, w, h);
2959
2960         /* root window real coords */
2961         event.xconfigure.x = self->root_pos.x;
2962         event.xconfigure.y = self->root_pos.y;
2963         event.xconfigure.width = w;
2964         event.xconfigure.height = h;
2965         event.xconfigure.border_width = self->border_width;
2966         event.xconfigure.above = None;
2967         event.xconfigure.override_redirect = FALSE;
2968         XSendEvent(event.xconfigure.display, event.xconfigure.window,
2969                    FALSE, StructureNotifyMask, &event);
2970     }
2971
2972     /* if the client is shrinking, then resize the frame before the client.
2973
2974        both of these resize sections may run, because the top one only resizes
2975        in the direction that is growing
2976      */
2977     if (send_resize_client && (w <= oldw || h <= oldh)) {
2978         frame_adjust_client_area(self->frame);
2979         XMoveResizeWindow(ob_display, self->window,
2980                           self->frame->size.left, self->frame->size.top, w, h);
2981     }
2982
2983     XFlush(ob_display);
2984 }
2985
2986 void client_fullscreen(ObClient *self, gboolean fs)
2987 {
2988     gint x, y, w, h;
2989
2990     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2991         self->fullscreen == fs) return;                   /* already done */
2992
2993     self->fullscreen = fs;
2994     client_change_state(self); /* change the state hints on the client */
2995
2996     if (fs) {
2997         self->pre_fullscreen_area = self->area;
2998         /* if the window is maximized, its area isn't all that meaningful.
2999            save it's premax area instead. */
3000         if (self->max_horz) {
3001             self->pre_fullscreen_area.x = self->pre_max_area.x;
3002             self->pre_fullscreen_area.width = self->pre_max_area.width;
3003         }
3004         if (self->max_vert) {
3005             self->pre_fullscreen_area.y = self->pre_max_area.y;
3006             self->pre_fullscreen_area.height = self->pre_max_area.height;
3007         }
3008
3009         /* these will help configure_full figure out where to fullscreen
3010            the window */
3011         x = self->area.x;
3012         y = self->area.y;
3013         w = self->area.width;
3014         h = self->area.height;
3015     } else {
3016         g_assert(self->pre_fullscreen_area.width > 0 &&
3017                  self->pre_fullscreen_area.height > 0);
3018
3019         x = self->pre_fullscreen_area.x;
3020         y = self->pre_fullscreen_area.y;
3021         w = self->pre_fullscreen_area.width;
3022         h = self->pre_fullscreen_area.height;
3023         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3024     }
3025
3026     client_setup_decor_and_functions(self, FALSE);
3027     client_move_resize(self, x, y, w, h);
3028
3029     /* and adjust our layer/stacking. do this after resizing the window,
3030        and applying decorations, because windows which fill the screen are
3031        considered "fullscreen" and it affects their layer */
3032     client_calc_layer(self);
3033
3034     if (fs) {
3035         /* try focus us when we go into fullscreen mode */
3036         client_focus(self);
3037     }
3038 }
3039
3040 static void client_iconify_recursive(ObClient *self,
3041                                      gboolean iconic, gboolean curdesk,
3042                                      gboolean hide_animation)
3043 {
3044     GSList *it;
3045     gboolean changed = FALSE;
3046
3047
3048     if (self->iconic != iconic) {
3049         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
3050                  self->window);
3051
3052         if (iconic) {
3053             /* don't let non-normal windows iconify along with their parents
3054                or whatever */
3055             if (client_normal(self)) {
3056                 self->iconic = iconic;
3057
3058                 /* update the focus lists.. iconic windows go to the bottom of
3059                    the list */
3060                 focus_order_to_bottom(self);
3061
3062                 changed = TRUE;
3063             }
3064         } else {
3065             self->iconic = iconic;
3066
3067             if (curdesk && self->desktop != screen_desktop &&
3068                 self->desktop != DESKTOP_ALL)
3069                 client_set_desktop(self, screen_desktop, FALSE);
3070
3071             /* this puts it after the current focused window */
3072             focus_order_remove(self);
3073             focus_order_add_new(self);
3074
3075             changed = TRUE;
3076         }
3077     }
3078
3079     if (changed) {
3080         client_change_state(self);
3081         if (config_animate_iconify && !hide_animation)
3082             frame_begin_iconify_animation(self->frame, iconic);
3083         /* do this after starting the animation so it doesn't flash */
3084         client_showhide(self);
3085     }
3086
3087     /* iconify all direct transients, and deiconify all transients
3088        (non-direct too) */
3089     for (it = self->transients; it; it = g_slist_next(it))
3090         if (it->data != self)
3091             if (client_is_direct_child(self, it->data) || !iconic)
3092                 client_iconify_recursive(it->data, iconic, curdesk,
3093                                          hide_animation);
3094 }
3095
3096 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3097                     gboolean hide_animation)
3098 {
3099     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3100         /* move up the transient chain as far as possible first */
3101         self = client_search_top_normal_parent(self);
3102         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3103     }
3104 }
3105
3106 void client_maximize(ObClient *self, gboolean max, gint dir)
3107 {
3108     gint x, y, w, h;
3109      
3110     g_assert(dir == 0 || dir == 1 || dir == 2);
3111     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
3112
3113     /* check if already done */
3114     if (max) {
3115         if (dir == 0 && self->max_horz && self->max_vert) return;
3116         if (dir == 1 && self->max_horz) return;
3117         if (dir == 2 && self->max_vert) return;
3118     } else {
3119         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3120         if (dir == 1 && !self->max_horz) return;
3121         if (dir == 2 && !self->max_vert) return;
3122     }
3123
3124     /* these will help configure_full figure out which screen to fill with
3125        the window */
3126     x = self->area.x;
3127     y = self->area.y;
3128     w = self->area.width;
3129     h = self->area.height;
3130
3131     if (max) {
3132         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3133             RECT_SET(self->pre_max_area,
3134                      self->area.x, self->pre_max_area.y,
3135                      self->area.width, self->pre_max_area.height);
3136         }
3137         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3138             RECT_SET(self->pre_max_area,
3139                      self->pre_max_area.x, self->area.y,
3140                      self->pre_max_area.width, self->area.height);
3141         }
3142     } else {
3143         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3144             g_assert(self->pre_max_area.width > 0);
3145
3146             x = self->pre_max_area.x;
3147             w = self->pre_max_area.width;
3148
3149             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3150                      0, self->pre_max_area.height);
3151         }
3152         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3153             g_assert(self->pre_max_area.height > 0);
3154
3155             y = self->pre_max_area.y;
3156             h = self->pre_max_area.height;
3157
3158             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3159                      self->pre_max_area.width, 0);
3160         }
3161     }
3162
3163     if (dir == 0 || dir == 1) /* horz */
3164         self->max_horz = max;
3165     if (dir == 0 || dir == 2) /* vert */
3166         self->max_vert = max;
3167
3168     client_change_state(self); /* change the state hints on the client */
3169
3170     client_setup_decor_and_functions(self, FALSE);
3171     client_move_resize(self, x, y, w, h);
3172 }
3173
3174 void client_shade(ObClient *self, gboolean shade)
3175 {
3176     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3177          shade) ||                         /* can't shade */
3178         self->shaded == shade) return;     /* already done */
3179
3180     self->shaded = shade;
3181     client_change_state(self);
3182     client_change_wm_state(self); /* the window is being hidden/shown */
3183     /* resize the frame to just the titlebar */
3184     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
3185 }
3186
3187 void client_close(ObClient *self)
3188 {
3189     XEvent ce;
3190
3191     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3192
3193     /* in the case that the client provides no means to requesting that it
3194        close, we just kill it */
3195     if (!self->delete_window)
3196         client_kill(self);
3197     
3198     /*
3199       XXX: itd be cool to do timeouts and shit here for killing the client's
3200       process off
3201       like... if the window is around after 5 seconds, then the close button
3202       turns a nice red, and if this function is called again, the client is
3203       explicitly killed.
3204     */
3205
3206     ce.xclient.type = ClientMessage;
3207     ce.xclient.message_type =  prop_atoms.wm_protocols;
3208     ce.xclient.display = ob_display;
3209     ce.xclient.window = self->window;
3210     ce.xclient.format = 32;
3211     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
3212     ce.xclient.data.l[1] = event_curtime;
3213     ce.xclient.data.l[2] = 0l;
3214     ce.xclient.data.l[3] = 0l;
3215     ce.xclient.data.l[4] = 0l;
3216     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3217 }
3218
3219 void client_kill(ObClient *self)
3220 {
3221     XKillClient(ob_display, self->window);
3222 }
3223
3224 void client_hilite(ObClient *self, gboolean hilite)
3225 {
3226     if (self->demands_attention == hilite)
3227         return; /* no change */
3228
3229     /* don't allow focused windows to hilite */
3230     self->demands_attention = hilite && !client_focused(self);
3231     if (self->frame != NULL) { /* if we're mapping, just set the state */
3232         if (self->demands_attention)
3233             frame_flash_start(self->frame);
3234         else
3235             frame_flash_stop(self->frame);
3236         client_change_state(self);
3237     }
3238 }
3239
3240 void client_set_desktop_recursive(ObClient *self,
3241                                   guint target,
3242                                   gboolean donthide)
3243 {
3244     guint old;
3245     GSList *it;
3246
3247     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3248
3249         ob_debug("Setting desktop %u\n", target+1);
3250
3251         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3252
3253         old = self->desktop;
3254         self->desktop = target;
3255         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3256         /* the frame can display the current desktop state */
3257         frame_adjust_state(self->frame);
3258         /* 'move' the window to the new desktop */
3259         if (!donthide)
3260             client_showhide(self);
3261         /* raise if it was not already on the desktop */
3262         if (old != DESKTOP_ALL)
3263             stacking_raise(CLIENT_AS_WINDOW(self));
3264         if (STRUT_EXISTS(self->strut))
3265             screen_update_areas();
3266     }
3267
3268     /* move all transients */
3269     for (it = self->transients; it; it = g_slist_next(it))
3270         if (it->data != self)
3271             if (client_is_direct_child(self, it->data))
3272                 client_set_desktop_recursive(it->data, target, donthide);
3273 }
3274
3275 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
3276 {
3277     self = client_search_top_normal_parent(self);
3278     client_set_desktop_recursive(self, target, donthide);
3279 }
3280
3281 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3282 {
3283     while (child != parent &&
3284            child->transient_for && child->transient_for != OB_TRAN_GROUP)
3285         child = child->transient_for;
3286     return child == parent;
3287 }
3288
3289 ObClient *client_search_modal_child(ObClient *self)
3290 {
3291     GSList *it;
3292     ObClient *ret;
3293   
3294     for (it = self->transients; it; it = g_slist_next(it)) {
3295         ObClient *c = it->data;
3296         if ((ret = client_search_modal_child(c))) return ret;
3297         if (c->modal) return c;
3298     }
3299     return NULL;
3300 }
3301
3302 gboolean client_validate(ObClient *self)
3303 {
3304     XEvent e; 
3305
3306     XSync(ob_display, FALSE); /* get all events on the server */
3307
3308     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3309         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3310         XPutBackEvent(ob_display, &e);
3311         return FALSE;
3312     }
3313
3314     return TRUE;
3315 }
3316
3317 void client_set_wm_state(ObClient *self, glong state)
3318 {
3319     if (state == self->wmstate) return; /* no change */
3320   
3321     switch (state) {
3322     case IconicState:
3323         client_iconify(self, TRUE, TRUE, FALSE);
3324         break;
3325     case NormalState:
3326         client_iconify(self, FALSE, TRUE, FALSE);
3327         break;
3328     }
3329 }
3330
3331 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3332 {
3333     gboolean shaded = self->shaded;
3334     gboolean fullscreen = self->fullscreen;
3335     gboolean undecorated = self->undecorated;
3336     gboolean max_horz = self->max_horz;
3337     gboolean max_vert = self->max_vert;
3338     gboolean modal = self->modal;
3339     gboolean iconic = self->iconic;
3340     gboolean demands_attention = self->demands_attention;
3341     gboolean above = self->above;
3342     gboolean below = self->below;
3343     gint i;
3344
3345     if (!(action == prop_atoms.net_wm_state_add ||
3346           action == prop_atoms.net_wm_state_remove ||
3347           action == prop_atoms.net_wm_state_toggle))
3348         /* an invalid action was passed to the client message, ignore it */
3349         return; 
3350
3351     for (i = 0; i < 2; ++i) {
3352         Atom state = i == 0 ? data1 : data2;
3353     
3354         if (!state) continue;
3355
3356         /* if toggling, then pick whether we're adding or removing */
3357         if (action == prop_atoms.net_wm_state_toggle) {
3358             if (state == prop_atoms.net_wm_state_modal)
3359                 action = modal ? prop_atoms.net_wm_state_remove :
3360                     prop_atoms.net_wm_state_add;
3361             else if (state == prop_atoms.net_wm_state_maximized_vert)
3362                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3363                     prop_atoms.net_wm_state_add;
3364             else if (state == prop_atoms.net_wm_state_maximized_horz)
3365                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3366                     prop_atoms.net_wm_state_add;
3367             else if (state == prop_atoms.net_wm_state_shaded)
3368                 action = shaded ? prop_atoms.net_wm_state_remove :
3369                     prop_atoms.net_wm_state_add;
3370             else if (state == prop_atoms.net_wm_state_skip_taskbar)
3371                 action = self->skip_taskbar ?
3372                     prop_atoms.net_wm_state_remove :
3373                     prop_atoms.net_wm_state_add;
3374             else if (state == prop_atoms.net_wm_state_skip_pager)
3375                 action = self->skip_pager ?
3376                     prop_atoms.net_wm_state_remove :
3377                     prop_atoms.net_wm_state_add;
3378             else if (state == prop_atoms.net_wm_state_hidden)
3379                 action = self->iconic ?
3380                     prop_atoms.net_wm_state_remove :
3381                     prop_atoms.net_wm_state_add;
3382             else if (state == prop_atoms.net_wm_state_fullscreen)
3383                 action = fullscreen ?
3384                     prop_atoms.net_wm_state_remove :
3385                     prop_atoms.net_wm_state_add;
3386             else if (state == prop_atoms.net_wm_state_above)
3387                 action = self->above ? prop_atoms.net_wm_state_remove :
3388                     prop_atoms.net_wm_state_add;
3389             else if (state == prop_atoms.net_wm_state_below)
3390                 action = self->below ? prop_atoms.net_wm_state_remove :
3391                     prop_atoms.net_wm_state_add;
3392             else if (state == prop_atoms.net_wm_state_demands_attention)
3393                 action = self->demands_attention ?
3394                     prop_atoms.net_wm_state_remove :
3395                     prop_atoms.net_wm_state_add;
3396             else if (state == prop_atoms.ob_wm_state_undecorated)
3397                 action = undecorated ? prop_atoms.net_wm_state_remove :
3398                     prop_atoms.net_wm_state_add;
3399         }
3400     
3401         if (action == prop_atoms.net_wm_state_add) {
3402             if (state == prop_atoms.net_wm_state_modal) {
3403                 modal = TRUE;
3404             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3405                 max_vert = TRUE;
3406             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3407                 max_horz = TRUE;
3408             } else if (state == prop_atoms.net_wm_state_shaded) {
3409                 shaded = TRUE;
3410             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3411                 self->skip_taskbar = TRUE;
3412             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3413                 self->skip_pager = TRUE;
3414             } else if (state == prop_atoms.net_wm_state_hidden) {
3415                 iconic = TRUE;
3416             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3417                 fullscreen = TRUE;
3418             } else if (state == prop_atoms.net_wm_state_above) {
3419                 above = TRUE;
3420                 below = FALSE;
3421             } else if (state == prop_atoms.net_wm_state_below) {
3422                 above = FALSE;
3423                 below = TRUE;
3424             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3425                 demands_attention = TRUE;
3426             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3427                 undecorated = TRUE;
3428             }
3429
3430         } else { /* action == prop_atoms.net_wm_state_remove */
3431             if (state == prop_atoms.net_wm_state_modal) {
3432                 modal = FALSE;
3433             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3434                 max_vert = FALSE;
3435             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3436                 max_horz = FALSE;
3437             } else if (state == prop_atoms.net_wm_state_shaded) {
3438                 shaded = FALSE;
3439             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3440                 self->skip_taskbar = FALSE;
3441             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3442                 self->skip_pager = FALSE;
3443             } else if (state == prop_atoms.net_wm_state_hidden) {
3444                 iconic = FALSE;
3445             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3446                 fullscreen = FALSE;
3447             } else if (state == prop_atoms.net_wm_state_above) {
3448                 above = FALSE;
3449             } else if (state == prop_atoms.net_wm_state_below) {
3450                 below = FALSE;
3451             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3452                 demands_attention = FALSE;
3453             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3454                 undecorated = FALSE;
3455             }
3456         }
3457     }
3458
3459     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3460         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3461             /* toggling both */
3462             if (max_horz == max_vert) { /* both going the same way */
3463                 client_maximize(self, max_horz, 0);
3464             } else {
3465                 client_maximize(self, max_horz, 1);
3466                 client_maximize(self, max_vert, 2);
3467             }
3468         } else {
3469             /* toggling one */
3470             if (max_horz != self->max_horz)
3471                 client_maximize(self, max_horz, 1);
3472             else
3473                 client_maximize(self, max_vert, 2);
3474         }
3475     }
3476     /* change fullscreen state before shading, as it will affect if the window
3477        can shade or not */
3478     if (fullscreen != self->fullscreen)
3479         client_fullscreen(self, fullscreen);
3480     if (shaded != self->shaded)
3481         client_shade(self, shaded);
3482     if (undecorated != self->undecorated)
3483         client_set_undecorated(self, undecorated);
3484     if (above != self->above || below != self->below) {
3485         self->above = above;
3486         self->below = below;
3487         client_calc_layer(self);
3488     }
3489
3490     if (modal != self->modal) {
3491         self->modal = modal;
3492         /* when a window changes modality, then its stacking order with its
3493            transients needs to change */
3494         stacking_raise(CLIENT_AS_WINDOW(self));
3495
3496         /* it also may get focused. if something is focused that shouldn't
3497            be focused anymore, then move the focus */
3498         if (focus_client && client_focus_target(focus_client) != focus_client)
3499             client_focus(focus_client);
3500     }
3501
3502     if (iconic != self->iconic)
3503         client_iconify(self, iconic, FALSE, FALSE);
3504
3505     if (demands_attention != self->demands_attention)
3506         client_hilite(self, demands_attention);
3507
3508     client_change_state(self); /* change the hint to reflect these changes */
3509 }
3510
3511 ObClient *client_focus_target(ObClient *self)
3512 {
3513     ObClient *child = NULL;
3514
3515     child = client_search_modal_child(self);
3516     if (child) return child;
3517     return self;
3518 }
3519
3520 gboolean client_can_focus(ObClient *self)
3521 {
3522     /* choose the correct target */
3523     self = client_focus_target(self);
3524
3525     if (!self->frame->visible)
3526         return FALSE;
3527
3528     if (!(self->can_focus || self->focus_notify))
3529         return FALSE;
3530
3531     return TRUE;
3532 }
3533
3534 gboolean client_focus(ObClient *self)
3535 {
3536     /* we might not focus this window, so if we have modal children which would
3537        be focused instead, bring them to this desktop */
3538     client_bring_modal_windows(self);
3539
3540     /* choose the correct target */
3541     self = client_focus_target(self);
3542
3543     if (!client_can_focus(self)) {
3544         ob_debug_type(OB_DEBUG_FOCUS,
3545                       "Client %s can't be focused\n", self->title);
3546         return FALSE;
3547     }
3548
3549     ob_debug_type(OB_DEBUG_FOCUS,
3550                   "Focusing client \"%s\" (0x%x) at time %u\n",
3551                   self->title, self->window, event_curtime);
3552
3553     /* if there is a grab going on, then we need to cancel it. if we move
3554        focus during the grab, applications will get NotifyWhileGrabbed events
3555        and ignore them !
3556
3557        actions should not rely on being able to move focus during an
3558        interactive grab.
3559     */
3560     event_cancel_all_key_grabs();
3561
3562     xerror_set_ignore(TRUE);
3563     xerror_occured = FALSE;
3564
3565     if (self->can_focus) {
3566         /* This can cause a BadMatch error with CurrentTime, or if an app
3567            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3568         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3569                        event_curtime);
3570     }
3571
3572     if (self->focus_notify) {
3573         XEvent ce;
3574         ce.xclient.type = ClientMessage;
3575         ce.xclient.message_type = prop_atoms.wm_protocols;
3576         ce.xclient.display = ob_display;
3577         ce.xclient.window = self->window;
3578         ce.xclient.format = 32;
3579         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3580         ce.xclient.data.l[1] = event_curtime;
3581         ce.xclient.data.l[2] = 0l;
3582         ce.xclient.data.l[3] = 0l;
3583         ce.xclient.data.l[4] = 0l;
3584         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3585     }
3586
3587     xerror_set_ignore(FALSE);
3588
3589     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d\n", xerror_occured);
3590     return !xerror_occured;
3591 }
3592
3593 /*! Present the client to the user.
3594   @param raise If the client should be raised or not. You should only set
3595                raise to false if you don't care if the window is completely
3596                hidden.
3597 */
3598 static void client_present(ObClient *self, gboolean here, gboolean raise)
3599 {
3600     /* if using focus_delay, stop the timer now so that focus doesn't
3601        go moving on us */
3602     event_halt_focus_delay();
3603
3604     if (client_normal(self) && screen_showing_desktop)
3605         screen_show_desktop(FALSE, self);
3606     if (self->iconic)
3607         client_iconify(self, FALSE, here, FALSE);
3608     if (self->desktop != DESKTOP_ALL &&
3609         self->desktop != screen_desktop)
3610     {
3611         if (here)
3612             client_set_desktop(self, screen_desktop, FALSE);
3613         else
3614             screen_set_desktop(self->desktop, FALSE);
3615     } else if (!self->frame->visible)
3616         /* if its not visible for other reasons, then don't mess
3617            with it */
3618         return;
3619     if (self->shaded)
3620         client_shade(self, FALSE);
3621     if (raise)
3622         stacking_raise(CLIENT_AS_WINDOW(self));
3623
3624     client_focus(self);
3625 }
3626
3627 void client_activate(ObClient *self, gboolean here, gboolean user)
3628 {
3629     guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3630     gboolean allow = FALSE;
3631
3632     /* if the request came from the user, or if nothing is focused, then grant
3633        the request.
3634        if the currently focused app doesn't set a user_time, then it can't
3635        benefit from any focus stealing prevention.
3636     */
3637     if (user || !focus_client || !last_time)
3638         allow = TRUE;
3639     /* otherwise, if they didn't give a time stamp or if it is too old, they
3640        don't get focus */
3641     else
3642         allow = event_curtime && event_time_after(event_curtime, last_time);
3643
3644     ob_debug_type(OB_DEBUG_FOCUS,
3645                   "Want to activate window 0x%x with time %u (last time %u), "
3646                   "source=%s allowing? %d\n",
3647                   self->window, event_curtime, last_time,
3648                   (user ? "user" : "application"), allow);
3649
3650     if (allow)
3651         client_present(self, here, TRUE);
3652     else
3653         /* don't focus it but tell the user it wants attention */
3654         client_hilite(self, TRUE);
3655 }
3656
3657 static void client_bring_windows_recursive(ObClient *self,
3658                                            guint desktop,
3659                                            gboolean helpers,
3660                                            gboolean modals,
3661                                            gboolean iconic)
3662 {
3663     GSList *it;
3664
3665     for (it = self->transients; it; it = g_slist_next(it))
3666         client_bring_windows_recursive(it->data, desktop,
3667                                        helpers, modals, iconic);
3668
3669     if (((helpers && client_helper(self)) ||
3670          (modals && self->modal)) &&
3671         ((self->desktop != desktop && self->desktop != DESKTOP_ALL) ||
3672          (iconic && self->iconic)))
3673     {
3674         if (iconic && self->iconic)
3675             client_iconify(self, FALSE, TRUE, FALSE);
3676         else
3677             client_set_desktop(self, desktop, FALSE);
3678     }
3679 }
3680
3681 void client_bring_helper_windows(ObClient *self)
3682 {
3683     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
3684 }
3685
3686 void client_bring_modal_windows(ObClient *self)
3687 {
3688     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
3689 }
3690
3691 gboolean client_focused(ObClient *self)
3692 {
3693     return self == focus_client;
3694 }
3695
3696 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3697 {
3698     guint i;
3699     gulong min_diff, min_i;
3700
3701     if (!self->nicons) {
3702         ObClientIcon *parent = NULL;
3703
3704         if (self->transient_for) {
3705             if (self->transient_for != OB_TRAN_GROUP)
3706                 parent = client_icon_recursive(self->transient_for, w, h);
3707             else {
3708                 GSList *it;
3709                 for (it = self->group->members; it; it = g_slist_next(it)) {
3710                     ObClient *c = it->data;
3711                     if (c != self && !c->transient_for) {
3712                         if ((parent = client_icon_recursive(c, w, h)))
3713                             break;
3714                     }
3715                 }
3716             }
3717         }
3718         
3719         return parent;
3720     }
3721
3722     /* some kind of crappy approximation to find the icon closest in size to
3723        what we requested, but icons are generally all the same ratio as
3724        eachother so it's good enough. */
3725
3726     min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
3727     min_i = 0;
3728
3729     for (i = 1; i < self->nicons; ++i) {
3730         gulong diff;
3731
3732         diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
3733         if (diff < min_diff) {
3734             min_diff = diff;
3735             min_i = i;
3736         }
3737     }
3738     return &self->icons[min_i];
3739 }
3740
3741 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3742 {
3743     ObClientIcon *ret;
3744     static ObClientIcon deficon;
3745
3746     if (!(ret = client_icon_recursive(self, w, h))) {
3747         deficon.width = deficon.height = 48;
3748         deficon.data = ob_rr_theme->def_win_icon;
3749         ret = &deficon;
3750     }
3751     return ret;
3752 }
3753
3754 void client_set_layer(ObClient *self, gint layer)
3755 {
3756     if (layer < 0) {
3757         self->below = TRUE;
3758         self->above = FALSE;
3759     } else if (layer == 0) {
3760         self->below = self->above = FALSE;
3761     } else {
3762         self->below = FALSE;
3763         self->above = TRUE;
3764     }
3765     client_calc_layer(self);
3766     client_change_state(self); /* reflect this in the state hints */
3767 }
3768
3769 void client_set_undecorated(ObClient *self, gboolean undecorated)
3770 {
3771     if (self->undecorated != undecorated &&
3772         /* don't let it undecorate if the function is missing, but let 
3773            it redecorate */
3774         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3775     {
3776         self->undecorated = undecorated;
3777         client_setup_decor_and_functions(self, TRUE);
3778         client_change_state(self); /* reflect this in the state hints */
3779     }
3780 }
3781
3782 guint client_monitor(ObClient *self)
3783 {
3784     return screen_find_monitor(&self->frame->area);
3785 }
3786
3787 ObClient *client_search_top_normal_parent(ObClient *self)
3788 {
3789     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3790            client_normal(self->transient_for))
3791         self = self->transient_for;
3792     return self;
3793 }
3794
3795 static GSList *client_search_all_top_parents_internal(ObClient *self,
3796                                                       gboolean bylayer,
3797                                                       ObStackingLayer layer)
3798 {
3799     GSList *ret = NULL;
3800     
3801     /* move up the direct transient chain as far as possible */
3802     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3803            (!bylayer || self->transient_for->layer == layer) &&
3804            client_normal(self->transient_for))
3805         self = self->transient_for;
3806
3807     if (!self->transient_for)
3808         ret = g_slist_prepend(ret, self);
3809     else {
3810             GSList *it;
3811
3812             g_assert(self->group);
3813
3814             for (it = self->group->members; it; it = g_slist_next(it)) {
3815                 ObClient *c = it->data;
3816
3817                 if (!c->transient_for && client_normal(c) &&
3818                     (!bylayer || c->layer == layer))
3819                 {
3820                     ret = g_slist_prepend(ret, c);
3821                 }
3822             }
3823
3824             if (ret == NULL) /* no group parents */
3825                 ret = g_slist_prepend(ret, self);
3826     }
3827
3828     return ret;
3829 }
3830
3831 GSList *client_search_all_top_parents(ObClient *self)
3832 {
3833     return client_search_all_top_parents_internal(self, FALSE, 0);
3834 }
3835
3836 GSList *client_search_all_top_parents_layer(ObClient *self)
3837 {
3838     return client_search_all_top_parents_internal(self, TRUE, self->layer);
3839 }
3840
3841 ObClient *client_search_focus_parent(ObClient *self)
3842 {
3843     if (self->transient_for) {
3844         if (self->transient_for != OB_TRAN_GROUP) {
3845             if (client_focused(self->transient_for))
3846                 return self->transient_for;
3847         } else {
3848             GSList *it;
3849
3850             for (it = self->group->members; it; it = g_slist_next(it)) {
3851                 ObClient *c = it->data;
3852
3853                 /* checking transient_for prevents infinate loops! */
3854                 if (c != self && !c->transient_for)
3855                     if (client_focused(c))
3856                         return c;
3857             }
3858         }
3859     }
3860
3861     return NULL;
3862 }
3863
3864 ObClient *client_search_parent(ObClient *self, ObClient *search)
3865 {
3866     if (self->transient_for) {
3867         if (self->transient_for != OB_TRAN_GROUP) {
3868             if (self->transient_for == search)
3869                 return search;
3870         } else {
3871             GSList *it;
3872
3873             for (it = self->group->members; it; it = g_slist_next(it)) {
3874                 ObClient *c = it->data;
3875
3876                 /* checking transient_for prevents infinate loops! */
3877                 if (c != self && !c->transient_for)
3878                     if (c == search)
3879                         return search;
3880             }
3881         }
3882     }
3883
3884     return NULL;
3885 }
3886
3887 ObClient *client_search_transient(ObClient *self, ObClient *search)
3888 {
3889     GSList *sit;
3890
3891     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3892         if (sit->data == search)
3893             return search;
3894         if (client_search_transient(sit->data, search))
3895             return search;
3896     }
3897     return NULL;
3898 }
3899
3900 #define WANT_EDGE(cur, c) \
3901             if (cur == c)                                                     \
3902                 continue;                                                     \
3903             if (c->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&  \
3904                 cur->desktop != screen_desktop)                               \
3905                 continue;                                                     \
3906             if (cur->iconic)                                                  \
3907                 continue;
3908
3909 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3910             if ((his_edge_start >= my_edge_start && \
3911                  his_edge_start <= my_edge_end) ||  \
3912                 (my_edge_start >= his_edge_start && \
3913                  my_edge_start <= his_edge_end))    \
3914                 dest = his_offset;
3915
3916 /* finds the nearest edge in the given direction from the current client
3917  * note to self: the edge is the -frame- edge (the actual one), not the
3918  * client edge.
3919  */
3920 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3921 {
3922     gint dest, monitor_dest;
3923     gint my_edge_start, my_edge_end, my_offset;
3924     GList *it;
3925     Rect *a, *monitor;
3926     
3927     if(!client_list)
3928         return -1;
3929
3930     a = screen_area(c->desktop);
3931     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3932
3933     switch(dir) {
3934     case OB_DIRECTION_NORTH:
3935         my_edge_start = c->frame->area.x;
3936         my_edge_end = c->frame->area.x + c->frame->area.width;
3937         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3938         
3939         /* default: top of screen */
3940         dest = a->y + (hang ? c->frame->area.height : 0);
3941         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3942         /* if the monitor edge comes before the screen edge, */
3943         /* use that as the destination instead. (For xinerama) */
3944         if (monitor_dest != dest && my_offset > monitor_dest)
3945             dest = monitor_dest; 
3946
3947         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3948             gint his_edge_start, his_edge_end, his_offset;
3949             ObClient *cur = it->data;
3950
3951             WANT_EDGE(cur, c)
3952
3953             his_edge_start = cur->frame->area.x;
3954             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3955             his_offset = cur->frame->area.y + 
3956                          (hang ? 0 : cur->frame->area.height);
3957
3958             if(his_offset + 1 > my_offset)
3959                 continue;
3960
3961             if(his_offset < dest)
3962                 continue;
3963
3964             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3965         }
3966         break;
3967     case OB_DIRECTION_SOUTH:
3968         my_edge_start = c->frame->area.x;
3969         my_edge_end = c->frame->area.x + c->frame->area.width;
3970         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3971
3972         /* default: bottom of screen */
3973         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3974         monitor_dest = monitor->y + monitor->height -
3975                        (hang ? c->frame->area.height : 0);
3976         /* if the monitor edge comes before the screen edge, */
3977         /* use that as the destination instead. (For xinerama) */
3978         if (monitor_dest != dest && my_offset < monitor_dest)
3979             dest = monitor_dest; 
3980
3981         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3982             gint his_edge_start, his_edge_end, his_offset;
3983             ObClient *cur = it->data;
3984
3985             WANT_EDGE(cur, c)
3986
3987             his_edge_start = cur->frame->area.x;
3988             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3989             his_offset = cur->frame->area.y +
3990                          (hang ? cur->frame->area.height : 0);
3991
3992
3993             if(his_offset - 1 < my_offset)
3994                 continue;
3995             
3996             if(his_offset > dest)
3997                 continue;
3998
3999             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
4000         }
4001         break;
4002     case OB_DIRECTION_WEST:
4003         my_edge_start = c->frame->area.y;
4004         my_edge_end = c->frame->area.y + c->frame->area.height;
4005         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
4006
4007         /* default: leftmost egde of screen */
4008         dest = a->x + (hang ? c->frame->area.width : 0);
4009         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
4010         /* if the monitor edge comes before the screen edge, */
4011         /* use that as the destination instead. (For xinerama) */
4012         if (monitor_dest != dest && my_offset > monitor_dest)
4013             dest = monitor_dest;            
4014
4015         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
4016             gint his_edge_start, his_edge_end, his_offset;
4017             ObClient *cur = it->data;
4018
4019             WANT_EDGE(cur, c)
4020
4021             his_edge_start = cur->frame->area.y;
4022             his_edge_end = cur->frame->area.y + cur->frame->area.height;
4023             his_offset = cur->frame->area.x +
4024                          (hang ? 0 : cur->frame->area.width);
4025
4026             if(his_offset + 1 > my_offset)
4027                 continue;
4028
4029             if(his_offset < dest)
4030                 continue;
4031
4032             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
4033         }
4034        break;
4035     case OB_DIRECTION_EAST:
4036         my_edge_start = c->frame->area.y;
4037         my_edge_end = c->frame->area.y + c->frame->area.height;
4038         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
4039         
4040         /* default: rightmost edge of screen */
4041         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
4042         monitor_dest = monitor->x + monitor->width -
4043                        (hang ? c->frame->area.width : 0);
4044         /* if the monitor edge comes before the screen edge, */
4045         /* use that as the destination instead. (For xinerama) */
4046         if (monitor_dest != dest && my_offset < monitor_dest)
4047             dest = monitor_dest;            
4048
4049         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
4050             gint his_edge_start, his_edge_end, his_offset;
4051             ObClient *cur = it->data;
4052
4053             WANT_EDGE(cur, c)
4054
4055             his_edge_start = cur->frame->area.y;
4056             his_edge_end = cur->frame->area.y + cur->frame->area.height;
4057             his_offset = cur->frame->area.x +
4058                          (hang ? cur->frame->area.width : 0);
4059
4060             if(his_offset - 1 < my_offset)
4061                 continue;
4062             
4063             if(his_offset > dest)
4064                 continue;
4065
4066             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
4067         }
4068         break;
4069     case OB_DIRECTION_NORTHEAST:
4070     case OB_DIRECTION_SOUTHEAST:
4071     case OB_DIRECTION_NORTHWEST:
4072     case OB_DIRECTION_SOUTHWEST:
4073         /* not implemented */
4074     default:
4075         g_assert_not_reached();
4076         dest = 0; /* suppress warning */
4077     }
4078     return dest;
4079 }
4080
4081 ObClient* client_under_pointer()
4082 {
4083     gint x, y;
4084     GList *it;
4085     ObClient *ret = NULL;
4086
4087     if (screen_pointer_pos(&x, &y)) {
4088         for (it = stacking_list; it; it = g_list_next(it)) {
4089             if (WINDOW_IS_CLIENT(it->data)) {
4090                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4091                 if (c->frame->visible &&
4092                     /* ignore all animating windows */
4093                     !frame_iconify_animating(c->frame) &&
4094                     RECT_CONTAINS(c->frame->area, x, y))
4095                 {
4096                     ret = c;
4097                     break;
4098                 }
4099             }
4100         }
4101     }
4102     return ret;
4103 }
4104
4105 gboolean client_has_group_siblings(ObClient *self)
4106 {
4107     return self->group && self->group->members->next;
4108 }