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