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