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