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