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