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