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