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