]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
kill window title numbering...
[dana/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43
44 #include <glib.h>
45 #include <X11/Xutil.h>
46
47 /*! The event mask to grab on client windows */
48 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask)
49
50 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
51                                 ButtonMotionMask)
52
53 typedef struct
54 {
55     ObClientDestructor func;
56     gpointer data;
57 } Destructor;
58
59 GList         *client_list           = NULL;
60
61 static GSList *client_destructors    = NULL;
62 static Time    client_last_user_time = CurrentTime;
63
64 static void client_get_all(ObClient *self);
65 static void client_toggle_border(ObClient *self, gboolean show);
66 static void client_get_startup_id(ObClient *self);
67 static void client_get_area(ObClient *self);
68 static void client_get_desktop(ObClient *self);
69 static void client_get_state(ObClient *self);
70 static void client_get_layer(ObClient *self);
71 static void client_get_shaped(ObClient *self);
72 static void client_get_mwm_hints(ObClient *self);
73 static void client_get_gravity(ObClient *self);
74 static void client_change_allowed_actions(ObClient *self);
75 static void client_change_state(ObClient *self);
76 static void client_change_wm_state(ObClient *self);
77 static void client_apply_startup_state(ObClient *self, gint x, gint y);
78 static void client_restore_session_state(ObClient *self);
79 static void client_restore_session_stacking(ObClient *self);
80 static ObAppSettings *client_get_settings_state(ObClient *self);
81 static void client_unfocus(ObClient *self);
82
83 void client_startup(gboolean reconfig)
84 {
85     if (reconfig) return;
86
87     client_set_list();
88 }
89
90 void client_shutdown(gboolean reconfig)
91 {
92 }
93
94 void client_add_destructor(ObClientDestructor func, gpointer data)
95 {
96     Destructor *d = g_new(Destructor, 1);
97     d->func = func;
98     d->data = data;
99     client_destructors = g_slist_prepend(client_destructors, d);
100 }
101
102 void client_remove_destructor(ObClientDestructor func)
103 {
104     GSList *it;
105
106     for (it = client_destructors; it; it = g_slist_next(it)) {
107         Destructor *d = it->data;
108         if (d->func == func) {
109             g_free(d);
110             client_destructors = g_slist_delete_link(client_destructors, it);
111             break;
112         }
113     }
114 }
115
116 void client_set_list()
117 {
118     Window *windows, *win_it;
119     GList *it;
120     guint size = g_list_length(client_list);
121
122     /* create an array of the window ids */
123     if (size > 0) {
124         windows = g_new(Window, size);
125         win_it = windows;
126         for (it = client_list; it; it = g_list_next(it), ++win_it)
127             *win_it = ((ObClient*)it->data)->window;
128     } else
129         windows = NULL;
130
131     PROP_SETA32(RootWindow(ob_display, ob_screen),
132                 net_client_list, window, (gulong*)windows, size);
133
134     if (windows)
135         g_free(windows);
136
137     stacking_set_list();
138 }
139
140 /*
141   void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
142   {
143   GSList *it;
144
145   for (it = self->transients; it; it = g_slist_next(it)) {
146   if (!func(it->data, data)) return;
147   client_foreach_transient(it->data, func, data);
148   }
149   }
150
151   void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
152   {
153   if (self->transient_for) {
154   if (self->transient_for != OB_TRAN_GROUP) {
155   if (!func(self->transient_for, data)) return;
156   client_foreach_ancestor(self->transient_for, func, data);
157   } else {
158   GSList *it;
159
160   for (it = self->group->members; it; it = g_slist_next(it))
161   if (it->data != self &&
162   !((ObClient*)it->data)->transient_for) {
163   if (!func(it->data, data)) return;
164   client_foreach_ancestor(it->data, func, data);
165   }
166   }
167   }
168   }
169 */
170
171 void client_manage_all()
172 {
173     guint i, j, nchild;
174     Window w, *children;
175     XWMHints *wmhints;
176     XWindowAttributes attrib;
177
178     XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
179                &w, &w, &children, &nchild);
180
181     /* remove all icon windows from the list */
182     for (i = 0; i < nchild; i++) {
183         if (children[i] == None) continue;
184         wmhints = XGetWMHints(ob_display, children[i]);
185         if (wmhints) {
186             if ((wmhints->flags & IconWindowHint) &&
187                 (wmhints->icon_window != children[i]))
188                 for (j = 0; j < nchild; j++)
189                     if (children[j] == wmhints->icon_window) {
190                         children[j] = None;
191                         break;
192                     }
193             XFree(wmhints);
194         }
195     }
196
197     for (i = 0; i < nchild; ++i) {
198         if (children[i] == None)
199             continue;
200         if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
201             if (attrib.override_redirect) continue;
202
203             if (attrib.map_state != IsUnmapped)
204                 client_manage(children[i]);
205         }
206     }
207     XFree(children);
208 }
209
210 void client_manage(Window window)
211 {
212     ObClient *self;
213     XEvent e;
214     XWindowAttributes attrib;
215     XSetWindowAttributes attrib_set;
216     XWMHints *wmhint;
217     gboolean activate = FALSE;
218     ObAppSettings *settings;
219     gint newx, newy;
220
221     grab_server(TRUE);
222
223     /* check if it has already been unmapped by the time we started mapping
224        the grab does a sync so we don't have to here */
225     if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
226         XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e))
227     {
228         XPutBackEvent(ob_display, &e);
229
230         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 = ~0; /* maximum value, always newer than the real time */
275
276     client_get_all(self);
277     client_restore_session_state(self);
278     /* per-app settings override stuff, and return the settings for other
279        uses too */
280     settings = client_get_settings_state(self);
281
282     client_calc_layer(self);
283
284     {
285         Time t = sn_app_started(self->startup_id, self->class);
286         if (t) self->user_time = t;
287     }
288
289     /* update the focus lists, do this before the call to change_state or
290        it can end up in the list twice! */
291     focus_order_add_new(self);
292
293     /* remove the client's border (and adjust re gravity) */
294     client_toggle_border(self, FALSE);
295      
296     /* specify that if we exit, the window should not be destroyed and should
297        be reparented back to root automatically */
298     XChangeSaveSet(ob_display, window, SetModeInsert);
299
300     /* create the decoration frame for the client window */
301     self->frame = frame_new(self);
302
303     frame_grab_client(self->frame, self);
304
305     /* do this after we have a frame.. it uses the frame to help determine the
306        WM_STATE to apply. */
307     client_change_state(self);
308
309     grab_server(FALSE);
310
311     stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
312     client_restore_session_stacking(self);
313
314     /* focus the new window? */
315     if (ob_state() != OB_STATE_STARTING &&
316         /* this means focus=true for window is same as config_focus_new=true */
317         ((config_focus_new || (settings && settings->focus == 1)) ||
318          client_search_focus_parent(self)) &&
319         /* this checks for focus=false for the window */
320         (!settings || settings->focus != 0) &&
321         /* note the check against Type_Normal/Dialog, not client_normal(self),
322            which would also include other types. in this case we want more
323            strict rules for focus */
324         (self->type == OB_CLIENT_TYPE_NORMAL ||
325          self->type == OB_CLIENT_TYPE_DIALOG))
326     {
327         activate = TRUE;
328 #if 0
329         if (self->desktop != screen_desktop) {
330             /* activate the window */
331             activate = TRUE;
332         } else {
333             gboolean group_foc = FALSE;
334
335             if (self->group) {
336                 GSList *it;
337
338                 for (it = self->group->members; it; it = g_slist_next(it))
339                 {
340                     if (client_focused(it->data))
341                     {
342                         group_foc = TRUE;
343                         break;
344                     }
345                 }
346             }
347             if ((group_foc ||
348                  (!self->transient_for && (!self->group ||
349                                            !self->group->members->next))) ||
350                 client_search_focus_tree_full(self) ||
351                 !focus_client ||
352                 !client_normal(focus_client))
353             {
354                 /* activate the window */
355                 activate = TRUE;
356             }
357         }
358 #endif
359     }
360
361     /* get the current position */
362     newx = self->area.x;
363     newy = self->area.y;
364
365     /* figure out placement for the window */
366     if (ob_state() == OB_STATE_RUNNING) {
367         gboolean transient;
368
369         transient = place_client(self, &newx, &newy, settings);
370
371         /* make sure the window is visible. */
372         client_find_onscreen(self, &newx, &newy,
373                              self->frame->area.width,
374                              self->frame->area.height,
375                              /* non-normal clients has less rules, and
376                                 windows that are being restored from a
377                                 session do also. we can assume you want
378                                 it back where you saved it. Clients saying
379                                 they placed themselves are subjected to
380                                 harder rules, ones that are placed by
381                                 place.c or by the user are allowed partially
382                                 off-screen and on xinerama divides (ie,
383                                 it is up to the placement routines to avoid
384                                 the xinerama divides) */
385                              transient ||
386                              (((self->positioned & PPosition) &&
387                                !(self->positioned & USPosition)) &&
388                               client_normal(self) &&
389                               !self->session));
390     }
391
392     /* do this after the window is placed, so the premax/prefullscreen numbers
393        won't be all wacko!!
394        also, this moves the window to the position where it has been placed
395     */
396     client_apply_startup_state(self, newx, newy);
397
398     keyboard_grab_for_client(self, TRUE);
399     mouse_grab_for_client(self, TRUE);
400
401     if (activate) {
402         /* This is focus stealing prevention */
403         ob_debug("Want to focus new window 0x%x with time %u (last time %u)\n",
404                  self->window, self->user_time, client_last_user_time);
405
406         /* If a nothing at all, or a parent was focused, then focus this
407            always
408         */
409         if (!focus_client || client_search_focus_parent(self) != NULL)
410             activate = TRUE;
411         else
412         {
413             /* If time stamp is old, don't steal focus */
414             if (self->user_time && self->user_time < client_last_user_time)
415                 activate = FALSE;
416             /* Don't steal focus from globally active clients.
417                I stole this idea from KWin. It seems nice.
418              */
419             if (!(focus_client->can_focus || focus_client->focus_notify))
420                 activate = FALSE;
421         }
422
423         if (activate)
424         {
425             /* since focus can change the stacking orders, if we focus the
426                window then the standard raise it gets is not enough, we need
427                to queue one for after the focus change takes place */
428             client_raise(self);
429         } else {
430             ob_debug("Focus stealing prevention activated for %s with time %u "
431                      "(last time %u)\n",
432                      self->title, self->user_time, client_last_user_time);
433             /* if the client isn't focused, then hilite it so the user
434                knows it is there */
435             client_hilite(self, TRUE);
436         }
437     }
438     else {
439         /* This may look rather odd. Well it's because new windows are added
440            to the stacking order non-intrusively. If we're not going to focus
441            the new window or hilite it, then we raise it to the top. This will
442            take affect for things that don't get focused like splash screens.
443            Also if you don't have focus_new enabled, then it's going to get
444            raised to the top. Legacy begets legacy I guess?
445         */
446         client_raise(self);
447     }
448
449     /* this has to happen before we try focus the window, but we want it to
450        happen after the client's stacking has been determined or it looks bad
451     */
452     client_showhide(self);
453
454     /* use client_focus instead of client_activate cuz client_activate does
455        stuff like switch desktops etc and I'm not interested in all that when
456        a window maps since its not based on an action from the user like
457        clicking a window to activate it. so keep the new window out of the way
458        but do focus it. */
459     if (activate) {
460         /* if using focus_delay, stop the timer now so that focus doesn't
461            go moving on us */
462         event_halt_focus_delay();
463         client_focus(self);
464     }
465
466     /* client_activate does this but we aret using it so we have to do it
467        here as well */
468     if (screen_showing_desktop)
469         screen_show_desktop(FALSE);
470
471     /* add to client list/map */
472     client_list = g_list_append(client_list, self);
473     g_hash_table_insert(window_map, &self->window, self);
474
475     /* this has to happen after we're in the client_list */
476     if (STRUT_EXISTS(self->strut))
477         screen_update_areas();
478
479     /* update the list hints */
480     client_set_list();
481
482     ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
483 }
484
485 void client_unmanage_all()
486 {
487     while (client_list != NULL)
488         client_unmanage(client_list->data);
489 }
490
491 void client_unmanage(ObClient *self)
492 {
493     guint j;
494     GSList *it;
495
496     ob_debug("Unmanaging window: %lx (%s) (%s)\n", self->window, self->class,
497              self->title ? self->title : "");
498
499     g_assert(self != NULL);
500
501     /* update the focus lists */
502     focus_order_remove(self);
503
504     if (focus_client == self) {
505         XEvent e;
506
507         /* focus the last focused window on the desktop, and ignore enter
508            events from the unmap so it doesnt mess with the focus */
509         while (XCheckTypedEvent(ob_display, EnterNotify, &e));
510         /* remove these flags so we don't end up getting focused in the
511            fallback! */
512         self->can_focus = FALSE;
513         self->focus_notify = FALSE;
514         self->modal = FALSE;
515         client_unfocus(self);
516     }
517
518     /* potentially fix focusLast */
519     if (config_focus_last)
520         grab_pointer(TRUE, OB_CURSOR_NONE);
521
522     frame_hide(self->frame);
523     XFlush(ob_display);
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     /* we dont want events no more */
532     XSelectInput(ob_display, self->window, NoEventMask);
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     /* give the client its border back */
574     client_toggle_border(self, TRUE);
575
576     /* reparent the window out of the frame, and free the frame */
577     frame_release_client(self->frame, self);
578     self->frame = NULL;
579
580     /* restore the window's original geometry so it is not lost */
581     if (self->fullscreen)
582         XMoveResizeWindow(ob_display, self->window,
583                           self->pre_fullscreen_area.x,
584                           self->pre_fullscreen_area.y,
585                           self->pre_fullscreen_area.width,
586                           self->pre_fullscreen_area.height);
587     else if (self->max_horz || self->max_vert) {
588         Rect a = self->area;
589         if (self->max_horz) {
590             a.x = self->pre_max_area.x;
591             a.width = self->pre_max_area.width;
592         }
593         if (self->max_vert) {
594             a.y = self->pre_max_area.y;
595             a.height = self->pre_max_area.height;
596         }
597         XMoveResizeWindow(ob_display, self->window,
598                           a.x, a.y, a.width, a.height);
599     }
600      
601     if (ob_state() != OB_STATE_EXITING) {
602         /* these values should not be persisted across a window
603            unmapping/mapping */
604         PROP_ERASE(self->window, net_wm_desktop);
605         PROP_ERASE(self->window, net_wm_state);
606         PROP_ERASE(self->window, wm_state);
607     } else {
608         /* if we're left in an unmapped state, the client wont be mapped. this
609            is bad, since we will no longer be managing the window on restart */
610         XMapWindow(ob_display, self->window);
611     }
612
613
614     ob_debug("Unmanaged window 0x%lx\n", self->window);
615
616     /* free all data allocated in the client struct */
617     g_slist_free(self->transients);
618     for (j = 0; j < self->nicons; ++j)
619         g_free(self->icons[j].data);
620     if (self->nicons > 0)
621         g_free(self->icons);
622     g_free(self->title);
623     g_free(self->icon_title);
624     g_free(self->name);
625     g_free(self->class);
626     g_free(self->role);
627     g_free(self->sm_client_id);
628     g_free(self);
629      
630     /* update the list hints */
631     client_set_list();
632
633     if (config_focus_last)
634         grab_pointer(FALSE, OB_CURSOR_NONE);
635 }
636
637 static ObAppSettings *client_get_settings_state(ObClient *self)
638 {
639     ObAppSettings *settings = NULL;
640     GSList *it;
641
642     for (it = config_per_app_settings; it; it = g_slist_next(it)) {
643         ObAppSettings *app = it->data;
644         
645         if ((app->name && !app->class && !strcmp(app->name, self->name))
646             || (app->class && !app->name && !strcmp(app->class, self->class))
647             || (app->class && app->name && !strcmp(app->class, self->class)
648                 && !strcmp(app->name, self->name)))
649         {
650             ob_debug("Window matching: %s\n", app->name);
651             /* Match if no role was specified in the per app setting, or if the
652              * string matches the beginning of the role, since apps like to set
653              * the role to things like browser-window-23c4b2f */
654             if (!app->role
655                 || !strncmp(app->role, self->role, strlen(app->role)))
656             {
657                 /* use this one */
658                 settings = app;
659                 break;
660             }
661         }
662     }
663
664     if (settings) {
665         if (settings->shade != -1)
666             self->shaded = !!settings->shade;
667         if (settings->decor != -1)
668             self->undecorated = !settings->decor;
669         if (settings->iconic != -1)
670             self->iconic = !!settings->iconic;
671         if (settings->skip_pager != -1)
672             self->skip_pager = !!settings->skip_pager;
673         if (settings->skip_taskbar != -1)
674             self->skip_taskbar = !!settings->skip_taskbar;
675
676         if (settings->max_vert != -1)
677             self->max_vert = !!settings->max_vert;
678         if (settings->max_horz != -1)
679             self->max_vert = !!settings->max_horz;
680
681         if (settings->fullscreen != -1)
682             self->fullscreen = !!settings->fullscreen;
683
684         if (settings->desktop < screen_num_desktops
685             || settings->desktop == DESKTOP_ALL)
686             client_set_desktop(self, settings->desktop, TRUE);
687
688         if (settings->layer == -1) {
689             self->below = TRUE;
690             self->above = FALSE;
691         }
692         else if (settings->layer == 0) {
693             self->below = FALSE;
694             self->above = FALSE;
695         }
696         else if (settings->layer == 1) {
697             self->below = FALSE;
698             self->above = TRUE;
699         }
700     }
701     return settings;
702 }
703
704 static void client_restore_session_state(ObClient *self)
705 {
706     GList *it;
707
708     if (!(it = session_state_find(self)))
709         return;
710
711     self->session = it->data;
712
713     RECT_SET_POINT(self->area, self->session->x, self->session->y);
714     self->positioned = PPosition;
715     if (self->session->w > 0)
716         self->area.width = self->session->w;
717     if (self->session->h > 0)
718         self->area.height = self->session->h;
719     XResizeWindow(ob_display, self->window,
720                   self->area.width, self->area.height);
721
722     self->desktop = (self->session->desktop == DESKTOP_ALL ?
723                      self->session->desktop :
724                      MIN(screen_num_desktops - 1, self->session->desktop));
725     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
726
727     self->shaded = self->session->shaded;
728     self->iconic = self->session->iconic;
729     self->skip_pager = self->session->skip_pager;
730     self->skip_taskbar = self->session->skip_taskbar;
731     self->fullscreen = self->session->fullscreen;
732     self->above = self->session->above;
733     self->below = self->session->below;
734     self->max_horz = self->session->max_horz;
735     self->max_vert = self->session->max_vert;
736 }
737
738 static void client_restore_session_stacking(ObClient *self)
739 {
740     GList *it;
741
742     if (!self->session) return;
743
744     it = g_list_find(session_saved_state, self->session);
745     for (it = g_list_previous(it); it; it = g_list_previous(it)) {
746         GList *cit;
747
748         for (cit = client_list; cit; cit = g_list_next(cit))
749             if (session_state_cmp(it->data, cit->data))
750                 break;
751         if (cit) {
752             client_calc_layer(self);
753             stacking_below(CLIENT_AS_WINDOW(self),
754                            CLIENT_AS_WINDOW(cit->data));
755             break;
756         }
757     }
758 }
759
760 void client_move_onscreen(ObClient *self, gboolean rude)
761 {
762     gint x = self->area.x;
763     gint y = self->area.y;
764     if (client_find_onscreen(self, &x, &y,
765                              self->frame->area.width,
766                              self->frame->area.height, rude)) {
767         client_move(self, x, y);
768     }
769 }
770
771 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
772                               gboolean rude)
773 {
774     Rect *a;
775     gint ox = *x, oy = *y;
776
777     frame_client_gravity(self->frame, x, y); /* get where the frame
778                                                 would be */
779
780     /* XXX watch for xinerama dead areas */
781     /* This makes sure windows aren't entirely outside of the screen so you
782        can't see them at all.
783        It makes sure 10% of the window is on the screen at least. At don't let
784        it move itself off the top of the screen, which would hide the titlebar
785        on you. (The user can still do this if they want too, it's only limiting
786        the application.
787     */
788     if (client_normal(self)) {
789         a = screen_area(self->desktop);
790         if (!self->strut.right &&
791             *x + self->frame->area.width/10 >= a->x + a->width - 1)
792             *x = a->x + a->width - self->frame->area.width/10;
793         if (!self->strut.bottom &&
794             *y + self->frame->area.height/10 >= a->y + a->height - 1)
795             *y = a->y + a->height - self->frame->area.height/10;
796         if (!self->strut.left && *x + self->frame->area.width*9/10 - 1 < a->x)
797             *x = a->x - self->frame->area.width*9/10;
798         if (!self->strut.top && *y + self->frame->area.height*9/10 - 1 < a->y)
799             *y = a->y - self->frame->area.width*9/10;
800     }
801
802     /* This here doesn't let windows even a pixel outside the screen,
803      * when called from client_manage, programs placing themselves are
804      * forced completely onscreen, while things like
805      * xterm -geometry resolution-width/2 will work fine. Trying to
806      * place it completely offscreen will be handled in the above code.
807      * Sorry for this confused comment, i am tired. */
808     if (rude) {
809         /* avoid the xinerama monitor divide while we're at it,
810          * remember to fix the placement stuff to avoid it also and
811          * then remove this XXX */
812         a = screen_area_monitor(self->desktop, client_monitor(self));
813         /* dont let windows map into the strut unless they
814            are bigger than the available area */
815         if (w <= a->width) {
816             if (!self->strut.left && *x < a->x) *x = a->x;
817             if (!self->strut.right && *x + w > a->x + a->width)
818                 *x = a->x + a->width - w;
819         }
820         if (h <= a->height) {
821             if (!self->strut.top && *y < a->y) *y = a->y;
822             if (!self->strut.bottom && *y + h > a->y + a->height)
823                 *y = a->y + a->height - h;
824         }
825     }
826
827     frame_frame_gravity(self->frame, x, y); /* get where the client
828                                                should be */
829
830     return ox != *x || oy != *y;
831 }
832
833 static void client_toggle_border(ObClient *self, gboolean show)
834 {
835     /* adjust our idea of where the client is, based on its border. When the
836        border is removed, the client should now be considered to be in a
837        different position.
838        when re-adding the border to the client, the same operation needs to be
839        reversed. */
840     gint oldx = self->area.x, oldy = self->area.y;
841     gint x = oldx, y = oldy;
842     switch(self->gravity) {
843     default:
844     case NorthWestGravity:
845     case WestGravity:
846     case SouthWestGravity:
847         break;
848     case NorthEastGravity:
849     case EastGravity:
850     case SouthEastGravity:
851         if (show) x -= self->border_width * 2;
852         else      x += self->border_width * 2;
853         break;
854     case NorthGravity:
855     case SouthGravity:
856     case CenterGravity:
857     case ForgetGravity:
858     case StaticGravity:
859         if (show) x -= self->border_width;
860         else      x += self->border_width;
861         break;
862     }
863     switch(self->gravity) {
864     default:
865     case NorthWestGravity:
866     case NorthGravity:
867     case NorthEastGravity:
868         break;
869     case SouthWestGravity:
870     case SouthGravity:
871     case SouthEastGravity:
872         if (show) y -= self->border_width * 2;
873         else      y += self->border_width * 2;
874         break;
875     case WestGravity:
876     case EastGravity:
877     case CenterGravity:
878     case ForgetGravity:
879     case StaticGravity:
880         if (show) y -= self->border_width;
881         else      y += self->border_width;
882         break;
883     }
884     self->area.x = x;
885     self->area.y = y;
886
887     if (show) {
888         XSetWindowBorderWidth(ob_display, self->window, self->border_width);
889
890         /* move the client so it is back it the right spot _with_ its
891            border! */
892         if (x != oldx || y != oldy)
893             XMoveWindow(ob_display, self->window, x, y);
894     } else
895         XSetWindowBorderWidth(ob_display, self->window, 0);
896 }
897
898
899 static void client_get_all(ObClient *self)
900 {
901     client_get_area(self);
902     client_get_mwm_hints(self);
903
904     /* The transient hint is used to pick a type, but the type can also affect
905        transiency (dialogs are always made transients of their group if they
906        have one). This is Havoc's idea, but it is needed to make some apps
907        work right (eg tsclient). */
908     client_update_transient_for(self);
909     client_get_type(self);/* this can change the mwmhints for special cases */
910     client_get_state(self);
911     client_update_transient_for(self);
912
913     client_update_wmhints(self);
914     client_get_startup_id(self);
915     client_get_desktop(self);/* uses transient data/group/startup id if a
916                                 desktop is not specified */
917     client_get_shaped(self);
918
919     client_get_layer(self); /* if layer hasn't been specified, get it from
920                                other sources if possible */
921
922     {
923         /* a couple type-based defaults for new windows */
924
925         /* this makes sure that these windows appear on all desktops */
926         if (self->type == OB_CLIENT_TYPE_DESKTOP)
927             self->desktop = DESKTOP_ALL;
928     }
929
930     client_update_protocols(self);
931
932     client_get_gravity(self); /* get the attribute gravity */
933     client_update_normal_hints(self); /* this may override the attribute
934                                          gravity */
935
936     /* got the type, the mwmhints, the protocols, and the normal hints
937        (min/max sizes), so we're ready to set up the decorations/functions */
938     client_setup_decor_and_functions(self);
939   
940     client_update_title(self);
941     client_update_class(self);
942     client_update_sm_client_id(self);
943     client_update_strut(self);
944     client_update_icons(self);
945     client_update_user_time(self, FALSE);
946 }
947
948 static void client_get_startup_id(ObClient *self)
949 {
950     if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
951         if (self->group)
952             PROP_GETS(self->group->leader,
953                       net_startup_id, utf8, &self->startup_id);
954 }
955
956 static void client_get_area(ObClient *self)
957 {
958     XWindowAttributes wattrib;
959     Status ret;
960   
961     ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
962     g_assert(ret != BadWindow);
963
964     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
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     GList *it;
1647     guint32 nums;
1648     guint i;
1649     gchar *data = NULL;
1650     gchar *old_title;
1651
1652     old_title = self->title;
1653      
1654     /* try netwm */
1655     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1656         /* try old x stuff */
1657         if (!(PROP_GETS(self->window, wm_name, locale, &data)
1658               || PROP_GETS(self->window, wm_name, utf8, &data))) {
1659             if (self->transient != NULL) {
1660                 /*
1661                   GNOME alert windows are not given titles:
1662                   http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1663                 */
1664                 data = g_strdup("");
1665             } else
1666                 data = g_strdup("Unnamed Window");
1667         }
1668     }
1669
1670     PROP_SETS(self->window, net_wm_visible_name, data);
1671     self->title = data;
1672
1673     if (self->frame)
1674         frame_adjust_title(self->frame);
1675
1676     g_free(old_title);
1677
1678     /* update the icon title */
1679     data = NULL;
1680     g_free(self->icon_title);
1681
1682     /* try netwm */
1683     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1684         /* try old x stuff */
1685         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data) ||
1686               PROP_GETS(self->window, wm_icon_name, utf8, &data)))
1687             data = g_strdup(self->title);
1688
1689     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1690     self->icon_title = data;
1691 }
1692
1693 void client_update_class(ObClient *self)
1694 {
1695     gchar **data;
1696     gchar *s;
1697
1698     if (self->name) g_free(self->name);
1699     if (self->class) g_free(self->class);
1700     if (self->role) g_free(self->role);
1701
1702     self->name = self->class = self->role = NULL;
1703
1704     if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1705         if (data[0]) {
1706             self->name = g_strdup(data[0]);
1707             if (data[1])
1708                 self->class = g_strdup(data[1]);
1709         }
1710         g_strfreev(data);     
1711     }
1712
1713     if (PROP_GETS(self->window, wm_window_role, locale, &s))
1714         self->role = s;
1715
1716     if (self->name == NULL) self->name = g_strdup("");
1717     if (self->class == NULL) self->class = g_strdup("");
1718     if (self->role == NULL) self->role = g_strdup("");
1719 }
1720
1721 void client_update_strut(ObClient *self)
1722 {
1723     guint num;
1724     guint32 *data;
1725     gboolean got = FALSE;
1726     StrutPartial strut;
1727
1728     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1729                     &data, &num)) {
1730         if (num == 12) {
1731             got = TRUE;
1732             STRUT_PARTIAL_SET(strut,
1733                               data[0], data[2], data[1], data[3],
1734                               data[4], data[5], data[8], data[9],
1735                               data[6], data[7], data[10], data[11]);
1736         }
1737         g_free(data);
1738     }
1739
1740     if (!got &&
1741         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1742         if (num == 4) {
1743             const Rect *a;
1744
1745             got = TRUE;
1746
1747             /* use the screen's width/height */
1748             a = screen_physical_area();
1749
1750             STRUT_PARTIAL_SET(strut,
1751                               data[0], data[2], data[1], data[3],
1752                               a->y, a->y + a->height - 1,
1753                               a->x, a->x + a->width - 1,
1754                               a->y, a->y + a->height - 1,
1755                               a->x, a->x + a->width - 1);
1756         }
1757         g_free(data);
1758     }
1759
1760     if (!got)
1761         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1762                           0, 0, 0, 0, 0, 0, 0, 0);
1763
1764     if (!STRUT_EQUAL(strut, self->strut)) {
1765         self->strut = strut;
1766
1767         /* updating here is pointless while we're being mapped cuz we're not in
1768            the client list yet */
1769         if (self->frame)
1770             screen_update_areas();
1771     }
1772 }
1773
1774 void client_update_icons(ObClient *self)
1775 {
1776     guint num;
1777     guint32 *data;
1778     guint w, h, i, j;
1779
1780     for (i = 0; i < self->nicons; ++i)
1781         g_free(self->icons[i].data);
1782     if (self->nicons > 0)
1783         g_free(self->icons);
1784     self->nicons = 0;
1785
1786     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1787         /* figure out how many valid icons are in here */
1788         i = 0;
1789         while (num - i > 2) {
1790             w = data[i++];
1791             h = data[i++];
1792             i += w * h;
1793             if (i > num || w*h == 0) break;
1794             ++self->nicons;
1795         }
1796
1797         self->icons = g_new(ObClientIcon, self->nicons);
1798     
1799         /* store the icons */
1800         i = 0;
1801         for (j = 0; j < self->nicons; ++j) {
1802             guint x, y, t;
1803
1804             w = self->icons[j].width = data[i++];
1805             h = self->icons[j].height = data[i++];
1806
1807             if (w*h == 0) continue;
1808
1809             self->icons[j].data = g_new(RrPixel32, w * h);
1810             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1811                 if (x >= w) {
1812                     x = 0;
1813                     ++y;
1814                 }
1815                 self->icons[j].data[t] =
1816                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1817                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1818                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1819                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1820             }
1821             g_assert(i <= num);
1822         }
1823
1824         g_free(data);
1825     } else {
1826         XWMHints *hints;
1827
1828         if ((hints = XGetWMHints(ob_display, self->window))) {
1829             if (hints->flags & IconPixmapHint) {
1830                 self->nicons++;
1831                 self->icons = g_new(ObClientIcon, self->nicons);
1832                 xerror_set_ignore(TRUE);
1833                 if (!RrPixmapToRGBA(ob_rr_inst,
1834                                     hints->icon_pixmap,
1835                                     (hints->flags & IconMaskHint ?
1836                                      hints->icon_mask : None),
1837                                     &self->icons[self->nicons-1].width,
1838                                     &self->icons[self->nicons-1].height,
1839                                     &self->icons[self->nicons-1].data)){
1840                     g_free(&self->icons[self->nicons-1]);
1841                     self->nicons--;
1842                 }
1843                 xerror_set_ignore(FALSE);
1844             }
1845             XFree(hints);
1846         }
1847     }
1848
1849     if (self->frame)
1850         frame_adjust_icon(self->frame);
1851 }
1852
1853 void client_update_user_time(ObClient *self, gboolean new_event)
1854 {
1855     guint32 time;
1856
1857     if (PROP_GET32(self->window, net_wm_user_time, cardinal, &time)) {
1858         self->user_time = 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            However! This is called when a window is mapped to get its user time
1865            but it's an old number, it's not changing it from new user
1866            interaction, so in that case, don't change the last user time.
1867         */
1868         if (new_event)
1869             client_last_user_time = time;
1870
1871         /*ob_debug("window 0x%x user time %u\n", self->window, time);*/
1872     }
1873 }
1874
1875 static void client_change_wm_state(ObClient *self)
1876 {
1877     gulong state[2];
1878     glong old;
1879
1880     old = self->wmstate;
1881
1882     if (self->shaded || self->iconic || !self->frame->visible)
1883         self->wmstate = IconicState;
1884     else
1885         self->wmstate = NormalState;
1886
1887     if (old != self->wmstate) {
1888         PROP_MSG(self->window, kde_wm_change_state,
1889                  self->wmstate, 1, 0, 0);
1890
1891         state[0] = self->wmstate;
1892         state[1] = None;
1893         PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1894     }
1895 }
1896
1897 static void client_change_state(ObClient *self)
1898 {
1899     gulong netstate[11];
1900     guint num;
1901
1902     num = 0;
1903     if (self->modal)
1904         netstate[num++] = prop_atoms.net_wm_state_modal;
1905     if (self->shaded)
1906         netstate[num++] = prop_atoms.net_wm_state_shaded;
1907     if (self->iconic)
1908         netstate[num++] = prop_atoms.net_wm_state_hidden;
1909     if (self->skip_taskbar)
1910         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1911     if (self->skip_pager)
1912         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1913     if (self->fullscreen)
1914         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1915     if (self->max_vert)
1916         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1917     if (self->max_horz)
1918         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1919     if (self->above)
1920         netstate[num++] = prop_atoms.net_wm_state_above;
1921     if (self->below)
1922         netstate[num++] = prop_atoms.net_wm_state_below;
1923     if (self->demands_attention)
1924         netstate[num++] = prop_atoms.net_wm_state_demands_attention;
1925     if (self->undecorated)
1926         netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1927     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1928
1929     if (self->frame)
1930         frame_adjust_state(self->frame);
1931 }
1932
1933 ObClient *client_search_focus_tree(ObClient *self)
1934 {
1935     GSList *it;
1936     ObClient *ret;
1937
1938     for (it = self->transients; it; it = g_slist_next(it)) {
1939         if (client_focused(it->data)) return it->data;
1940         if ((ret = client_search_focus_tree(it->data))) return ret;
1941     }
1942     return NULL;
1943 }
1944
1945 ObClient *client_search_focus_tree_full(ObClient *self)
1946 {
1947     if (self->transient_for) {
1948         if (self->transient_for != OB_TRAN_GROUP) {
1949             return client_search_focus_tree_full(self->transient_for);
1950         } else {
1951             GSList *it;
1952             gboolean recursed = FALSE;
1953         
1954             for (it = self->group->members; it; it = g_slist_next(it))
1955                 if (!((ObClient*)it->data)->transient_for) {
1956                     ObClient *c;
1957                     if ((c = client_search_focus_tree_full(it->data)))
1958                         return c;
1959                     recursed = TRUE;
1960                 }
1961             if (recursed)
1962                 return NULL;
1963         }
1964     }
1965
1966     /* this function checks the whole tree, the client_search_focus_tree~
1967        does not, so we need to check this window */
1968     if (client_focused(self))
1969         return self;
1970     return client_search_focus_tree(self);
1971 }
1972
1973 static ObStackingLayer calc_layer(ObClient *self)
1974 {
1975     ObStackingLayer l;
1976
1977     if (self->fullscreen &&
1978         (client_focused(self) || client_search_focus_tree(self)))
1979         l = OB_STACKING_LAYER_FULLSCREEN;
1980     else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1981         l = OB_STACKING_LAYER_DESKTOP;
1982     else if (self->type == OB_CLIENT_TYPE_DOCK) {
1983         if (self->below) l = OB_STACKING_LAYER_NORMAL;
1984         else l = OB_STACKING_LAYER_ABOVE;
1985     }
1986     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1987     else if (self->below) l = OB_STACKING_LAYER_BELOW;
1988     else l = OB_STACKING_LAYER_NORMAL;
1989
1990     return l;
1991 }
1992
1993 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1994                                         ObStackingLayer min, gboolean raised)
1995 {
1996     ObStackingLayer old, own;
1997     GSList *it;
1998
1999     old = self->layer;
2000     own = calc_layer(self);
2001     self->layer = MAX(own, min);
2002
2003     for (it = self->transients; it; it = g_slist_next(it))
2004         client_calc_layer_recursive(it->data, orig,
2005                                     self->layer,
2006                                     raised ? raised : self->layer != old);
2007
2008     if (!raised && self->layer != old)
2009         if (orig->frame) { /* only restack if the original window is managed */
2010             stacking_remove(CLIENT_AS_WINDOW(self));
2011             stacking_add(CLIENT_AS_WINDOW(self));
2012         }
2013 }
2014
2015 void client_calc_layer(ObClient *self)
2016 {
2017     ObClient *orig;
2018     GSList *it;
2019
2020     orig = self;
2021
2022     /* transients take on the layer of their parents */
2023     it = client_search_all_top_parents(self);
2024
2025     for (; it; it = g_slist_next(it))
2026         client_calc_layer_recursive(it->data, orig, 0, FALSE);
2027 }
2028
2029 gboolean client_should_show(ObClient *self)
2030 {
2031     if (self->iconic)
2032         return FALSE;
2033     if (client_normal(self) && screen_showing_desktop)
2034         return FALSE;
2035     /*
2036     if (self->transient_for) {
2037         if (self->transient_for != OB_TRAN_GROUP)
2038             return client_should_show(self->transient_for);
2039         else {
2040             GSList *it;
2041
2042             for (it = self->group->members; it; it = g_slist_next(it)) {
2043                 ObClient *c = it->data;
2044                 if (c != self && !c->transient_for) {
2045                     if (client_should_show(c))
2046                         return TRUE;
2047                 }
2048             }
2049         }
2050     }
2051     */
2052     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2053         return TRUE;
2054     
2055     return FALSE;
2056 }
2057
2058 void client_showhide(ObClient *self)
2059 {
2060
2061     if (client_should_show(self)) {
2062         frame_show(self->frame);
2063     }
2064     else {
2065         frame_hide(self->frame);
2066
2067         /* Fall back focus since we're disappearing */
2068         if (focus_client == self)
2069             client_unfocus(self);
2070     }
2071
2072     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2073        needs to be in IconicState. This includes when it is on another
2074        desktop!
2075     */
2076     client_change_wm_state(self);
2077 }
2078
2079 gboolean client_normal(ObClient *self) {
2080     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2081               self->type == OB_CLIENT_TYPE_DOCK ||
2082               self->type == OB_CLIENT_TYPE_SPLASH);
2083 }
2084
2085 static void client_apply_startup_state(ObClient *self, gint x, gint y)
2086 {
2087     gboolean pos = FALSE; /* has the window's position been configured? */
2088     gint ox, oy;
2089
2090     /* save the position, and set self->area for these to use */
2091     ox = self->area.x;
2092     oy = self->area.y;
2093     self->area.x = x;
2094     self->area.y = y;
2095
2096     /* these are in a carefully crafted order.. */
2097
2098     if (self->iconic) {
2099         self->iconic = FALSE;
2100         client_iconify(self, TRUE, FALSE);
2101     }
2102     if (self->fullscreen) {
2103         self->fullscreen = FALSE;
2104         client_fullscreen(self, TRUE);
2105         pos = TRUE;
2106     }
2107     if (self->undecorated) {
2108         self->undecorated = FALSE;
2109         client_set_undecorated(self, TRUE);
2110     }
2111     if (self->shaded) {
2112         self->shaded = FALSE;
2113         client_shade(self, TRUE);
2114     }
2115     if (self->demands_attention) {
2116         self->demands_attention = FALSE;
2117         client_hilite(self, TRUE);
2118     }
2119   
2120     if (self->max_vert && self->max_horz) {
2121         self->max_vert = self->max_horz = FALSE;
2122         client_maximize(self, TRUE, 0);
2123         pos = TRUE;
2124     } else if (self->max_vert) {
2125         self->max_vert = FALSE;
2126         client_maximize(self, TRUE, 2);
2127         pos = TRUE;
2128     } else if (self->max_horz) {
2129         self->max_horz = FALSE;
2130         client_maximize(self, TRUE, 1);
2131         pos = TRUE;
2132     }
2133
2134     /* if the client didn't get positioned yet, then do so now */
2135     if (!pos && (ox != x || oy != y)) {
2136         /* use the saved position */
2137         self->area.x = ox;
2138         self->area.y = oy;
2139         client_move(self, x, y);
2140     }
2141
2142     /* nothing to do for the other states:
2143        skip_taskbar
2144        skip_pager
2145        modal
2146        above
2147        below
2148     */
2149 }
2150
2151 void client_try_configure(ObClient *self, ObCorner anchor,
2152                           gint *x, gint *y, gint *w, gint *h,
2153                           gint *logicalw, gint *logicalh,
2154                           gboolean user)
2155 {
2156     Rect desired_area = {*x, *y, *w, *h};
2157
2158     /* make the frame recalculate its dimentions n shit without changing
2159        anything visible for real, this way the constraints below can work with
2160        the updated frame dimensions. */
2161     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2162
2163     /* gets the frame's position */
2164     frame_client_gravity(self->frame, x, y);
2165
2166     /* these positions are frame positions, not client positions */
2167
2168     /* set the size and position if fullscreen */
2169     if (self->fullscreen) {
2170         Rect *a;
2171         guint i;
2172
2173         i = screen_find_monitor(&desired_area);
2174         a = screen_physical_area_monitor(i);
2175
2176         *x = a->x;
2177         *y = a->y;
2178         *w = a->width;
2179         *h = a->height;
2180
2181         user = FALSE; /* ignore that increment etc shit when in fullscreen */
2182     } else {
2183         Rect *a;
2184         guint i;
2185
2186         i = screen_find_monitor(&desired_area);
2187         a = screen_area_monitor(self->desktop, i);
2188
2189         /* set the size and position if maximized */
2190         if (self->max_horz) {
2191             *x = a->x;
2192             *w = a->width - self->frame->size.left - self->frame->size.right;
2193         }
2194         if (self->max_vert) {
2195             *y = a->y;
2196             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2197         }
2198     }
2199
2200     /* gets the client's position */
2201     frame_frame_gravity(self->frame, x, y);
2202
2203     /* these override the above states! if you cant move you can't move! */
2204     if (user) {
2205         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2206             *x = self->area.x;
2207             *y = self->area.y;
2208         }
2209         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2210             *w = self->area.width;
2211             *h = self->area.height;
2212         }
2213     }
2214
2215     if (!(*w == self->area.width && *h == self->area.height)) {
2216         gint basew, baseh, minw, minh;
2217
2218         /* base size is substituted with min size if not specified */
2219         if (self->base_size.width || self->base_size.height) {
2220             basew = self->base_size.width;
2221             baseh = self->base_size.height;
2222         } else {
2223             basew = self->min_size.width;
2224             baseh = self->min_size.height;
2225         }
2226         /* min size is substituted with base size if not specified */
2227         if (self->min_size.width || self->min_size.height) {
2228             minw = self->min_size.width;
2229             minh = self->min_size.height;
2230         } else {
2231             minw = self->base_size.width;
2232             minh = self->base_size.height;
2233         }
2234
2235         /* if this is a user-requested resize, then check against min/max
2236            sizes */
2237
2238         /* smaller than min size or bigger than max size? */
2239         if (*w > self->max_size.width) *w = self->max_size.width;
2240         if (*w < minw) *w = minw;
2241         if (*h > self->max_size.height) *h = self->max_size.height;
2242         if (*h < minh) *h = minh;
2243
2244         *w -= basew;
2245         *h -= baseh;
2246
2247         /* keep to the increments */
2248         *w /= self->size_inc.width;
2249         *h /= self->size_inc.height;
2250
2251         /* you cannot resize to nothing */
2252         if (basew + *w < 1) *w = 1 - basew;
2253         if (baseh + *h < 1) *h = 1 - baseh;
2254   
2255         /* save the logical size */
2256         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2257         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2258
2259         *w *= self->size_inc.width;
2260         *h *= self->size_inc.height;
2261
2262         *w += basew;
2263         *h += baseh;
2264
2265         /* adjust the height to match the width for the aspect ratios.
2266            for this, min size is not substituted for base size ever. */
2267         *w -= self->base_size.width;
2268         *h -= self->base_size.height;
2269
2270         if (!self->fullscreen) {
2271             if (self->min_ratio)
2272                 if (*h * self->min_ratio > *w) {
2273                     *h = (gint)(*w / self->min_ratio);
2274
2275                     /* you cannot resize to nothing */
2276                     if (*h < 1) {
2277                         *h = 1;
2278                         *w = (gint)(*h * self->min_ratio);
2279                     }
2280                 }
2281             if (self->max_ratio)
2282                 if (*h * self->max_ratio < *w) {
2283                     *h = (gint)(*w / self->max_ratio);
2284
2285                     /* you cannot resize to nothing */
2286                     if (*h < 1) {
2287                         *h = 1;
2288                         *w = (gint)(*h * self->min_ratio);
2289                     }
2290                 }
2291         }
2292
2293         *w += self->base_size.width;
2294         *h += self->base_size.height;
2295     }
2296
2297     g_assert(*w > 0);
2298     g_assert(*h > 0);
2299
2300     switch (anchor) {
2301     case OB_CORNER_TOPLEFT:
2302         break;
2303     case OB_CORNER_TOPRIGHT:
2304         *x -= *w - self->area.width;
2305         break;
2306     case OB_CORNER_BOTTOMLEFT:
2307         *y -= *h - self->area.height;
2308         break;
2309     case OB_CORNER_BOTTOMRIGHT:
2310         *x -= *w - self->area.width;
2311         *y -= *h - self->area.height;
2312         break;
2313     }
2314 }
2315
2316
2317 void client_configure_full(ObClient *self, ObCorner anchor,
2318                            gint x, gint y, gint w, gint h,
2319                            gboolean user, gboolean final,
2320                            gboolean force_reply)
2321 {
2322     gint oldw, oldh;
2323     gboolean send_resize_client;
2324     gboolean moved = FALSE, resized = FALSE;
2325     guint fdecor = self->frame->decorations;
2326     gboolean fhorz = self->frame->max_horz;
2327     gint logicalw, logicalh;
2328
2329     /* find the new x, y, width, and height (and logical size) */
2330     client_try_configure(self, anchor, &x, &y, &w, &h,
2331                          &logicalw, &logicalh, user);
2332
2333     /* set the logical size if things changed */
2334     if (!(w == self->area.width && h == self->area.height))
2335         SIZE_SET(self->logical_size, logicalw, logicalh);
2336
2337     /* figure out if we moved or resized or what */
2338     moved = x != self->area.x || y != self->area.y;
2339     resized = w != self->area.width || h != self->area.height;
2340
2341     oldw = self->area.width;
2342     oldh = self->area.height;
2343     RECT_SET(self->area, x, y, w, h);
2344
2345     /* for app-requested resizes, always resize if 'resized' is true.
2346        for user-requested ones, only resize if final is true, or when
2347        resizing in redraw mode */
2348     send_resize_client = ((!user && resized) ||
2349                           (user && (final ||
2350                                     (resized && config_resize_redraw))));
2351
2352     /* if the client is enlarging, then resize the client before the frame */
2353     if (send_resize_client && user && (w > oldw || h > oldh))
2354         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2355
2356     /* move/resize the frame to match the request */
2357     if (self->frame) {
2358         if (self->decorations != fdecor || self->max_horz != fhorz)
2359             moved = resized = TRUE;
2360
2361         if (moved || resized)
2362             frame_adjust_area(self->frame, moved, resized, FALSE);
2363
2364         if (!resized && (force_reply || ((!user && moved) || (user && final))))
2365         {
2366             XEvent event;
2367             event.type = ConfigureNotify;
2368             event.xconfigure.display = ob_display;
2369             event.xconfigure.event = self->window;
2370             event.xconfigure.window = self->window;
2371
2372             /* root window real coords */
2373             event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2374                 self->border_width;
2375             event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2376                 self->border_width;
2377             event.xconfigure.width = w;
2378             event.xconfigure.height = h;
2379             event.xconfigure.border_width = 0;
2380             event.xconfigure.above = self->frame->plate;
2381             event.xconfigure.override_redirect = FALSE;
2382             XSendEvent(event.xconfigure.display, event.xconfigure.window,
2383                        FALSE, StructureNotifyMask, &event);
2384         }
2385     }
2386
2387     /* if the client is shrinking, then resize the frame before the client */
2388     if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2389         XResizeWindow(ob_display, self->window, w, h);
2390
2391     XFlush(ob_display);
2392 }
2393
2394 void client_fullscreen(ObClient *self, gboolean fs)
2395 {
2396     gint x, y, w, h;
2397
2398     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2399         self->fullscreen == fs) return;                   /* already done */
2400
2401     self->fullscreen = fs;
2402     client_change_state(self); /* change the state hints on the client */
2403     client_calc_layer(self);   /* and adjust out layer/stacking */
2404
2405     if (fs) {
2406         self->pre_fullscreen_area = self->area;
2407         /* if the window is maximized, its area isn't all that meaningful.
2408            save it's premax area instead. */
2409         if (self->max_horz) {
2410             self->pre_fullscreen_area.x = self->pre_max_area.x;
2411             self->pre_fullscreen_area.width = self->pre_max_area.width;
2412         }
2413         if (self->max_vert) {
2414             self->pre_fullscreen_area.y = self->pre_max_area.y;
2415             self->pre_fullscreen_area.height = self->pre_max_area.height;
2416         }
2417
2418         /* these are not actually used cuz client_configure will set them
2419            as appropriate when the window is fullscreened */
2420         x = y = w = h = 0;
2421     } else {
2422         Rect *a;
2423
2424         if (self->pre_fullscreen_area.width > 0 &&
2425             self->pre_fullscreen_area.height > 0)
2426         {
2427             x = self->pre_fullscreen_area.x;
2428             y = self->pre_fullscreen_area.y;
2429             w = self->pre_fullscreen_area.width;
2430             h = self->pre_fullscreen_area.height;
2431             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2432         } else {
2433             /* pick some fallbacks... */
2434             a = screen_area_monitor(self->desktop, 0);
2435             x = a->x + a->width / 4;
2436             y = a->y + a->height / 4;
2437             w = a->width / 2;
2438             h = a->height / 2;
2439         }
2440     }
2441
2442     client_setup_decor_and_functions(self);
2443
2444     client_move_resize(self, x, y, w, h);
2445
2446     /* try focus us when we go into fullscreen mode */
2447     client_focus(self);
2448 }
2449
2450 static void client_iconify_recursive(ObClient *self,
2451                                      gboolean iconic, gboolean curdesk)
2452 {
2453     GSList *it;
2454     gboolean changed = FALSE;
2455
2456
2457     if (self->iconic != iconic) {
2458         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2459                  self->window);
2460
2461         if (iconic) {
2462             if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2463                 self->iconic = iconic;
2464
2465                 /* update the focus lists.. iconic windows go to the bottom of
2466                    the list, put the new iconic window at the 'top of the
2467                    bottom'. */
2468                 focus_order_to_top(self);
2469
2470                 changed = TRUE;
2471             }
2472         } else {
2473             self->iconic = iconic;
2474
2475             if (curdesk)
2476                 client_set_desktop(self, screen_desktop, FALSE);
2477
2478             /* this puts it after the current focused window */
2479             focus_order_remove(self);
2480             focus_order_add_new(self);
2481
2482             changed = TRUE;
2483         }
2484     }
2485
2486     if (changed) {
2487         client_change_state(self);
2488         client_showhide(self);
2489         if (STRUT_EXISTS(self->strut))
2490             screen_update_areas();
2491     }
2492
2493     /* iconify all direct transients */
2494     for (it = self->transients; it; it = g_slist_next(it))
2495         if (it->data != self)
2496             if (client_is_direct_child(self, it->data))
2497                 client_iconify_recursive(it->data, iconic, curdesk);
2498 }
2499
2500 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2501 {
2502     /* move up the transient chain as far as possible first */
2503     self = client_search_top_parent(self);
2504     client_iconify_recursive(self, iconic, curdesk);
2505 }
2506
2507 void client_maximize(ObClient *self, gboolean max, gint dir)
2508 {
2509     gint x, y, w, h;
2510      
2511     g_assert(dir == 0 || dir == 1 || dir == 2);
2512     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2513
2514     /* check if already done */
2515     if (max) {
2516         if (dir == 0 && self->max_horz && self->max_vert) return;
2517         if (dir == 1 && self->max_horz) return;
2518         if (dir == 2 && self->max_vert) return;
2519     } else {
2520         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2521         if (dir == 1 && !self->max_horz) return;
2522         if (dir == 2 && !self->max_vert) return;
2523     }
2524
2525     /* we just tell it to configure in the same place and client_configure
2526        worries about filling the screen with the window */
2527     x = self->area.x;
2528     y = self->area.y;
2529     w = self->area.width;
2530     h = self->area.height;
2531
2532     if (max) {
2533         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2534             RECT_SET(self->pre_max_area,
2535                      self->area.x, self->pre_max_area.y,
2536                      self->area.width, self->pre_max_area.height);
2537         }
2538         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2539             RECT_SET(self->pre_max_area,
2540                      self->pre_max_area.x, self->area.y,
2541                      self->pre_max_area.width, self->area.height);
2542         }
2543     } else {
2544         Rect *a;
2545
2546         a = screen_area_monitor(self->desktop, 0);
2547         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2548             if (self->pre_max_area.width > 0) {
2549                 x = self->pre_max_area.x;
2550                 w = self->pre_max_area.width;
2551
2552                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2553                          0, self->pre_max_area.height);
2554             } else {
2555                 /* pick some fallbacks... */
2556                 x = a->x + a->width / 4;
2557                 w = a->width / 2;
2558             }
2559         }
2560         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2561             if (self->pre_max_area.height > 0) {
2562                 y = self->pre_max_area.y;
2563                 h = self->pre_max_area.height;
2564
2565                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2566                          self->pre_max_area.width, 0);
2567             } else {
2568                 /* pick some fallbacks... */
2569                 y = a->y + a->height / 4;
2570                 h = a->height / 2;
2571             }
2572         }
2573     }
2574
2575     if (dir == 0 || dir == 1) /* horz */
2576         self->max_horz = max;
2577     if (dir == 0 || dir == 2) /* vert */
2578         self->max_vert = max;
2579
2580     client_change_state(self); /* change the state hints on the client */
2581
2582     client_setup_decor_and_functions(self);
2583
2584     client_move_resize(self, x, y, w, h);
2585 }
2586
2587 void client_shade(ObClient *self, gboolean shade)
2588 {
2589     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2590          shade) ||                         /* can't shade */
2591         self->shaded == shade) return;     /* already done */
2592
2593     self->shaded = shade;
2594     client_change_state(self);
2595     client_change_wm_state(self); /* the window is being hidden/shown */
2596     /* resize the frame to just the titlebar */
2597     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2598 }
2599
2600 void client_close(ObClient *self)
2601 {
2602     XEvent ce;
2603
2604     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2605
2606     /* in the case that the client provides no means to requesting that it
2607        close, we just kill it */
2608     if (!self->delete_window)
2609         client_kill(self);
2610     
2611     /*
2612       XXX: itd be cool to do timeouts and shit here for killing the client's
2613       process off
2614       like... if the window is around after 5 seconds, then the close button
2615       turns a nice red, and if this function is called again, the client is
2616       explicitly killed.
2617     */
2618
2619     ce.xclient.type = ClientMessage;
2620     ce.xclient.message_type =  prop_atoms.wm_protocols;
2621     ce.xclient.display = ob_display;
2622     ce.xclient.window = self->window;
2623     ce.xclient.format = 32;
2624     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2625     ce.xclient.data.l[1] = event_curtime;
2626     ce.xclient.data.l[2] = 0l;
2627     ce.xclient.data.l[3] = 0l;
2628     ce.xclient.data.l[4] = 0l;
2629     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2630 }
2631
2632 void client_kill(ObClient *self)
2633 {
2634     XKillClient(ob_display, self->window);
2635 }
2636
2637 void client_hilite(ObClient *self, gboolean hilite)
2638 {
2639     if (self->demands_attention == hilite)
2640         return; /* no change */
2641
2642     /* don't allow focused windows to hilite */
2643     self->demands_attention = hilite && !client_focused(self);
2644     if (self->demands_attention)
2645         frame_flash_start(self->frame);
2646     else
2647         frame_flash_stop(self->frame);
2648     client_change_state(self);
2649 }
2650
2651 void client_set_desktop_recursive(ObClient *self,
2652                                   guint target, gboolean donthide)
2653 {
2654     guint old;
2655     GSList *it;
2656
2657     if (target != self->desktop) {
2658
2659         ob_debug("Setting desktop %u\n", target+1);
2660
2661         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2662
2663         /* remove from the old desktop(s) */
2664         focus_order_remove(self);
2665
2666         old = self->desktop;
2667         self->desktop = target;
2668         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2669         /* the frame can display the current desktop state */
2670         frame_adjust_state(self->frame);
2671         /* 'move' the window to the new desktop */
2672         if (!donthide)
2673             client_showhide(self);
2674         /* raise if it was not already on the desktop */
2675         if (old != DESKTOP_ALL)
2676             client_raise(self);
2677         if (STRUT_EXISTS(self->strut))
2678             screen_update_areas();
2679
2680         /* add to the new desktop(s) */
2681         if (config_focus_new)
2682             focus_order_to_top(self);
2683         else
2684             focus_order_to_bottom(self);
2685     }
2686
2687     /* move all transients */
2688     for (it = self->transients; it; it = g_slist_next(it))
2689         if (it->data != self)
2690             if (client_is_direct_child(self, it->data))
2691                 client_set_desktop_recursive(it->data, target, donthide);
2692 }
2693
2694 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2695 {
2696     self = client_search_top_parent(self);
2697     client_set_desktop_recursive(self, target, donthide);
2698 }
2699
2700 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
2701 {
2702     while (child != parent &&
2703            child->transient_for && child->transient_for != OB_TRAN_GROUP)
2704         child = child->transient_for;
2705     return child == parent;
2706 }
2707
2708 ObClient *client_search_modal_child(ObClient *self)
2709 {
2710     GSList *it;
2711     ObClient *ret;
2712   
2713     for (it = self->transients; it; it = g_slist_next(it)) {
2714         ObClient *c = it->data;
2715         if ((ret = client_search_modal_child(c))) return ret;
2716         if (c->modal) return c;
2717     }
2718     return NULL;
2719 }
2720
2721 gboolean client_validate(ObClient *self)
2722 {
2723     XEvent e; 
2724
2725     XSync(ob_display, FALSE); /* get all events on the server */
2726
2727     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2728         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2729         XPutBackEvent(ob_display, &e);
2730         return FALSE;
2731     }
2732
2733     return TRUE;
2734 }
2735
2736 void client_set_wm_state(ObClient *self, glong state)
2737 {
2738     if (state == self->wmstate) return; /* no change */
2739   
2740     switch (state) {
2741     case IconicState:
2742         client_iconify(self, TRUE, TRUE);
2743         break;
2744     case NormalState:
2745         client_iconify(self, FALSE, TRUE);
2746         break;
2747     }
2748 }
2749
2750 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2751 {
2752     gboolean shaded = self->shaded;
2753     gboolean fullscreen = self->fullscreen;
2754     gboolean undecorated = self->undecorated;
2755     gboolean max_horz = self->max_horz;
2756     gboolean max_vert = self->max_vert;
2757     gboolean modal = self->modal;
2758     gboolean iconic = self->iconic;
2759     gboolean demands_attention = self->demands_attention;
2760     gint i;
2761
2762     if (!(action == prop_atoms.net_wm_state_add ||
2763           action == prop_atoms.net_wm_state_remove ||
2764           action == prop_atoms.net_wm_state_toggle))
2765         /* an invalid action was passed to the client message, ignore it */
2766         return; 
2767
2768     for (i = 0; i < 2; ++i) {
2769         Atom state = i == 0 ? data1 : data2;
2770     
2771         if (!state) continue;
2772
2773         /* if toggling, then pick whether we're adding or removing */
2774         if (action == prop_atoms.net_wm_state_toggle) {
2775             if (state == prop_atoms.net_wm_state_modal)
2776                 action = modal ? prop_atoms.net_wm_state_remove :
2777                     prop_atoms.net_wm_state_add;
2778             else if (state == prop_atoms.net_wm_state_maximized_vert)
2779                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2780                     prop_atoms.net_wm_state_add;
2781             else if (state == prop_atoms.net_wm_state_maximized_horz)
2782                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2783                     prop_atoms.net_wm_state_add;
2784             else if (state == prop_atoms.net_wm_state_shaded)
2785                 action = shaded ? prop_atoms.net_wm_state_remove :
2786                     prop_atoms.net_wm_state_add;
2787             else if (state == prop_atoms.net_wm_state_skip_taskbar)
2788                 action = self->skip_taskbar ?
2789                     prop_atoms.net_wm_state_remove :
2790                     prop_atoms.net_wm_state_add;
2791             else if (state == prop_atoms.net_wm_state_skip_pager)
2792                 action = self->skip_pager ?
2793                     prop_atoms.net_wm_state_remove :
2794                     prop_atoms.net_wm_state_add;
2795             else if (state == prop_atoms.net_wm_state_hidden)
2796                 action = self->iconic ?
2797                     prop_atoms.net_wm_state_remove :
2798                     prop_atoms.net_wm_state_add;
2799             else if (state == prop_atoms.net_wm_state_fullscreen)
2800                 action = fullscreen ?
2801                     prop_atoms.net_wm_state_remove :
2802                     prop_atoms.net_wm_state_add;
2803             else if (state == prop_atoms.net_wm_state_above)
2804                 action = self->above ? prop_atoms.net_wm_state_remove :
2805                     prop_atoms.net_wm_state_add;
2806             else if (state == prop_atoms.net_wm_state_below)
2807                 action = self->below ? prop_atoms.net_wm_state_remove :
2808                     prop_atoms.net_wm_state_add;
2809             else if (state == prop_atoms.net_wm_state_demands_attention)
2810                 action = self->demands_attention ?
2811                     prop_atoms.net_wm_state_remove :
2812                     prop_atoms.net_wm_state_add;
2813             else if (state == prop_atoms.ob_wm_state_undecorated)
2814                 action = undecorated ? prop_atoms.net_wm_state_remove :
2815                     prop_atoms.net_wm_state_add;
2816         }
2817     
2818         if (action == prop_atoms.net_wm_state_add) {
2819             if (state == prop_atoms.net_wm_state_modal) {
2820                 modal = TRUE;
2821             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2822                 max_vert = TRUE;
2823             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2824                 max_horz = TRUE;
2825             } else if (state == prop_atoms.net_wm_state_shaded) {
2826                 shaded = TRUE;
2827             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2828                 self->skip_taskbar = TRUE;
2829             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2830                 self->skip_pager = TRUE;
2831             } else if (state == prop_atoms.net_wm_state_hidden) {
2832                 iconic = TRUE;
2833             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2834                 fullscreen = TRUE;
2835             } else if (state == prop_atoms.net_wm_state_above) {
2836                 self->above = TRUE;
2837                 self->below = FALSE;
2838             } else if (state == prop_atoms.net_wm_state_below) {
2839                 self->above = FALSE;
2840                 self->below = TRUE;
2841             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2842                 demands_attention = TRUE;
2843             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2844                 undecorated = TRUE;
2845             }
2846
2847         } else { /* action == prop_atoms.net_wm_state_remove */
2848             if (state == prop_atoms.net_wm_state_modal) {
2849                 modal = FALSE;
2850             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2851                 max_vert = FALSE;
2852             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2853                 max_horz = FALSE;
2854             } else if (state == prop_atoms.net_wm_state_shaded) {
2855                 shaded = FALSE;
2856             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2857                 self->skip_taskbar = FALSE;
2858             } else if (state == prop_atoms.net_wm_state_skip_pager) {
2859                 self->skip_pager = FALSE;
2860             } else if (state == prop_atoms.net_wm_state_hidden) {
2861                 iconic = FALSE;
2862             } else if (state == prop_atoms.net_wm_state_fullscreen) {
2863                 fullscreen = FALSE;
2864             } else if (state == prop_atoms.net_wm_state_above) {
2865                 self->above = FALSE;
2866             } else if (state == prop_atoms.net_wm_state_below) {
2867                 self->below = FALSE;
2868             } else if (state == prop_atoms.net_wm_state_demands_attention) {
2869                 demands_attention = FALSE;
2870             } else if (state == prop_atoms.ob_wm_state_undecorated) {
2871                 undecorated = FALSE;
2872             }
2873         }
2874     }
2875     if (max_horz != self->max_horz || max_vert != self->max_vert) {
2876         if (max_horz != self->max_horz && max_vert != self->max_vert) {
2877             /* toggling both */
2878             if (max_horz == max_vert) { /* both going the same way */
2879                 client_maximize(self, max_horz, 0);
2880             } else {
2881                 client_maximize(self, max_horz, 1);
2882                 client_maximize(self, max_vert, 2);
2883             }
2884         } else {
2885             /* toggling one */
2886             if (max_horz != self->max_horz)
2887                 client_maximize(self, max_horz, 1);
2888             else
2889                 client_maximize(self, max_vert, 2);
2890         }
2891     }
2892     /* change fullscreen state before shading, as it will affect if the window
2893        can shade or not */
2894     if (fullscreen != self->fullscreen)
2895         client_fullscreen(self, fullscreen);
2896     if (shaded != self->shaded)
2897         client_shade(self, shaded);
2898     if (undecorated != self->undecorated)
2899         client_set_undecorated(self, undecorated);
2900     if (modal != self->modal) {
2901         self->modal = modal;
2902         /* when a window changes modality, then its stacking order with its
2903            transients needs to change */
2904         client_raise(self);
2905     }
2906     if (iconic != self->iconic)
2907         client_iconify(self, iconic, FALSE);
2908
2909     if (demands_attention != self->demands_attention)
2910         client_hilite(self, demands_attention);
2911
2912     client_change_state(self); /* change the hint to reflect these changes */
2913 }
2914
2915 ObClient *client_focus_target(ObClient *self)
2916 {
2917     ObClient *child = NULL;
2918
2919     child = client_search_modal_child(self);
2920     if (child) return child;
2921     return self;
2922 }
2923
2924 gboolean client_can_focus(ObClient *self)
2925 {
2926     XEvent ev;
2927
2928     /* choose the correct target */
2929     self = client_focus_target(self);
2930
2931     if (!self->frame->visible)
2932         return FALSE;
2933
2934     if (!(self->can_focus || self->focus_notify))
2935         return FALSE;
2936
2937     /* do a check to see if the window has already been unmapped or destroyed
2938        do this intelligently while watching out for unmaps we've generated
2939        (ignore_unmaps > 0) */
2940     if (XCheckTypedWindowEvent(ob_display, self->window,
2941                                DestroyNotify, &ev)) {
2942         XPutBackEvent(ob_display, &ev);
2943         return FALSE;
2944     }
2945     while (XCheckTypedWindowEvent(ob_display, self->window,
2946                                   UnmapNotify, &ev)) {
2947         if (self->ignore_unmaps) {
2948             self->ignore_unmaps--;
2949         } else {
2950             XPutBackEvent(ob_display, &ev);
2951             return FALSE;
2952         }
2953     }
2954
2955     return TRUE;
2956 }
2957
2958 gboolean client_focus(ObClient *self)
2959 {
2960     /* choose the correct target */
2961     self = client_focus_target(self);
2962
2963 #if 0
2964     if (!client_validate(self))
2965         return FALSE;
2966 #endif
2967
2968     if (!client_can_focus(self)) {
2969         if (!self->frame->visible) {
2970             /* update the focus lists */
2971             focus_order_to_top(self);
2972         }
2973         return FALSE;
2974     }
2975
2976     ob_debug("Focusing client \"%s\" at time %u\n", self->title, event_curtime);
2977
2978     if (self->can_focus) {
2979         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
2980                        event_curtime);
2981     }
2982
2983     if (self->focus_notify) {
2984         XEvent ce;
2985         ce.xclient.type = ClientMessage;
2986         ce.xclient.message_type = prop_atoms.wm_protocols;
2987         ce.xclient.display = ob_display;
2988         ce.xclient.window = self->window;
2989         ce.xclient.format = 32;
2990         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2991         ce.xclient.data.l[1] = event_curtime;
2992         ce.xclient.data.l[2] = 0l;
2993         ce.xclient.data.l[3] = 0l;
2994         ce.xclient.data.l[4] = 0l;
2995         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2996     }
2997
2998 #ifdef DEBUG_FOCUS
2999     ob_debug("%sively focusing %lx at %d\n",
3000              (self->can_focus ? "act" : "pass"),
3001              self->window, (gint) event_curtime);
3002 #endif
3003
3004     /* Cause the FocusIn to come back to us. Important for desktop switches,
3005        since otherwise we'll have no FocusIn on the queue and send it off to
3006        the focus_backup. */
3007     XSync(ob_display, FALSE);
3008     return TRUE;
3009 }
3010
3011 /* Used when the current client is closed or otherwise hidden, focus_last will
3012    then prevent focus from going to the mouse pointer
3013 */
3014 static void client_unfocus(ObClient *self)
3015 {
3016     if (focus_client == self) {
3017 #ifdef DEBUG_FOCUS
3018         ob_debug("client_unfocus for %lx\n", self->window);
3019 #endif
3020         focus_fallback(FALSE);
3021     }
3022 }
3023
3024 void client_activate(ObClient *self, gboolean here, gboolean user)
3025 {
3026     /* XXX do some stuff here if user is false to determine if we really want
3027        to activate it or not (a parent or group member is currently
3028        active)?
3029     */
3030     ob_debug("Want to activate window 0x%x with time %u (last time %u), "
3031              "source=%s\n",
3032              self->window, event_curtime, client_last_user_time,
3033              (user ? "user" : "application"));
3034     if (!user && event_curtime && event_curtime < client_last_user_time)
3035         client_hilite(self, TRUE);
3036     else {
3037         if (client_normal(self) && screen_showing_desktop)
3038             screen_show_desktop(FALSE);
3039         if (self->iconic)
3040             client_iconify(self, FALSE, here);
3041         if (self->desktop != DESKTOP_ALL &&
3042             self->desktop != screen_desktop) {
3043             if (here)
3044                 client_set_desktop(self, screen_desktop, FALSE);
3045             else
3046                 screen_set_desktop(self->desktop);
3047         } else if (!self->frame->visible)
3048             /* if its not visible for other reasons, then don't mess
3049                with it */
3050             return;
3051         if (self->shaded)
3052             client_shade(self, FALSE);
3053
3054         client_focus(self);
3055
3056         /* we do this an action here. this is rather important. this is because
3057            we want the results from the focus change to take place BEFORE we go
3058            about raising the window. when a fullscreen window loses focus, we
3059            need this or else the raise wont be able to raise above the
3060            to-lose-focus fullscreen window. */
3061         client_raise(self);
3062     }
3063 }
3064
3065 void client_raise(ObClient *self)
3066 {
3067     action_run_string("Raise", self, CurrentTime);
3068 }
3069
3070 void client_lower(ObClient *self)
3071 {
3072     action_run_string("Lower", self, CurrentTime);
3073 }
3074
3075 gboolean client_focused(ObClient *self)
3076 {
3077     return self == focus_client;
3078 }
3079
3080 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3081 {
3082     guint i;
3083     /* si is the smallest image >= req */
3084     /* li is the largest image < req */
3085     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3086
3087     if (!self->nicons) {
3088         ObClientIcon *parent = NULL;
3089
3090         if (self->transient_for) {
3091             if (self->transient_for != OB_TRAN_GROUP)
3092                 parent = client_icon_recursive(self->transient_for, w, h);
3093             else {
3094                 GSList *it;
3095                 for (it = self->group->members; it; it = g_slist_next(it)) {
3096                     ObClient *c = it->data;
3097                     if (c != self && !c->transient_for) {
3098                         if ((parent = client_icon_recursive(c, w, h)))
3099                             break;
3100                     }
3101                 }
3102             }
3103         }
3104         
3105         return parent;
3106     }
3107
3108     for (i = 0; i < self->nicons; ++i) {
3109         size = self->icons[i].width * self->icons[i].height;
3110         if (size < smallest && size >= (unsigned)(w * h)) {
3111             smallest = size;
3112             si = i;
3113         }
3114         if (size > largest && size <= (unsigned)(w * h)) {
3115             largest = size;
3116             li = i;
3117         }
3118     }
3119     if (largest == 0) /* didnt find one smaller than the requested size */
3120         return &self->icons[si];
3121     return &self->icons[li];
3122 }
3123
3124 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3125 {
3126     ObClientIcon *ret;
3127     static ObClientIcon deficon;
3128
3129     if (!(ret = client_icon_recursive(self, w, h))) {
3130         deficon.width = deficon.height = 48;
3131         deficon.data = ob_rr_theme->def_win_icon;
3132         ret = &deficon;
3133     }
3134     return ret;
3135 }
3136
3137 /* this be mostly ripped from fvwm */
3138 ObClient *client_find_directional(ObClient *c, ObDirection dir) 
3139 {
3140     gint my_cx, my_cy, his_cx, his_cy;
3141     gint offset = 0;
3142     gint distance = 0;
3143     gint score, best_score;
3144     ObClient *best_client, *cur;
3145     GList *it;
3146
3147     if(!client_list)
3148         return NULL;
3149
3150     /* first, find the centre coords of the currently focused window */
3151     my_cx = c->frame->area.x + c->frame->area.width / 2;
3152     my_cy = c->frame->area.y + c->frame->area.height / 2;
3153
3154     best_score = -1;
3155     best_client = NULL;
3156
3157     for(it = g_list_first(client_list); it; it = g_list_next(it)) {
3158         cur = it->data;
3159
3160         /* the currently selected window isn't interesting */
3161         if(cur == c)
3162             continue;
3163         if (!client_normal(cur))
3164             continue;
3165         /* using c->desktop instead of screen_desktop doesn't work if the
3166          * current window was omnipresent, hope this doesn't have any other
3167          * side effects */
3168         if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3169             continue;
3170         if(cur->iconic)
3171             continue;
3172         if(!(client_focus_target(cur) == cur &&
3173              client_can_focus(cur)))
3174             continue;
3175
3176         /* find the centre coords of this window, from the
3177          * currently focused window's point of view */
3178         his_cx = (cur->frame->area.x - my_cx)
3179             + cur->frame->area.width / 2;
3180         his_cy = (cur->frame->area.y - my_cy)
3181             + cur->frame->area.height / 2;
3182
3183         if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3184            dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3185             gint tx;
3186             /* Rotate the diagonals 45 degrees counterclockwise.
3187              * To do this, multiply the matrix /+h +h\ with the
3188              * vector (x y).                   \-h +h/
3189              * h = sqrt(0.5). We can set h := 1 since absolute
3190              * distance doesn't matter here. */
3191             tx = his_cx + his_cy;
3192             his_cy = -his_cx + his_cy;
3193             his_cx = tx;
3194         }
3195
3196         switch(dir) {
3197         case OB_DIRECTION_NORTH:
3198         case OB_DIRECTION_SOUTH:
3199         case OB_DIRECTION_NORTHEAST:
3200         case OB_DIRECTION_SOUTHWEST:
3201             offset = (his_cx < 0) ? -his_cx : his_cx;
3202             distance = ((dir == OB_DIRECTION_NORTH ||
3203                          dir == OB_DIRECTION_NORTHEAST) ?
3204                         -his_cy : his_cy);
3205             break;
3206         case OB_DIRECTION_EAST:
3207         case OB_DIRECTION_WEST:
3208         case OB_DIRECTION_SOUTHEAST:
3209         case OB_DIRECTION_NORTHWEST:
3210             offset = (his_cy < 0) ? -his_cy : his_cy;
3211             distance = ((dir == OB_DIRECTION_WEST ||
3212                          dir == OB_DIRECTION_NORTHWEST) ?
3213                         -his_cx : his_cx);
3214             break;
3215         }
3216
3217         /* the target must be in the requested direction */
3218         if(distance <= 0)
3219             continue;
3220
3221         /* Calculate score for this window.  The smaller the better. */
3222         score = distance + offset;
3223
3224         /* windows more than 45 degrees off the direction are
3225          * heavily penalized and will only be chosen if nothing
3226          * else within a million pixels */
3227         if(offset > distance)
3228             score += 1000000;
3229
3230         if(best_score == -1 || score < best_score)
3231             best_client = cur,
3232                 best_score = score;
3233     }
3234
3235     return best_client;
3236 }
3237
3238 void client_set_layer(ObClient *self, gint layer)
3239 {
3240     if (layer < 0) {
3241         self->below = TRUE;
3242         self->above = FALSE;
3243     } else if (layer == 0) {
3244         self->below = self->above = FALSE;
3245     } else {
3246         self->below = FALSE;
3247         self->above = TRUE;
3248     }
3249     client_calc_layer(self);
3250     client_change_state(self); /* reflect this in the state hints */
3251 }
3252
3253 void client_set_undecorated(ObClient *self, gboolean undecorated)
3254 {
3255     if (self->undecorated != undecorated) {
3256         self->undecorated = undecorated;
3257         client_setup_decor_and_functions(self);
3258         /* Make sure the client knows it might have moved. Maybe there is a
3259          * better way of doing this so only one client_configure is sent, but
3260          * since 125 of these are sent per second when moving the window (with
3261          * user = FALSE) i doubt it matters much.
3262          */
3263         client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3264                          self->area.width, self->area.height, TRUE, TRUE);
3265         client_change_state(self); /* reflect this in the state hints */
3266     }
3267 }
3268
3269 guint client_monitor(ObClient *self)
3270 {
3271     return screen_find_monitor(&self->frame->area);
3272 }
3273
3274 ObClient *client_search_top_parent(ObClient *self)
3275 {
3276     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3277         self = self->transient_for;
3278     return self;
3279 }
3280
3281 GSList *client_search_all_top_parents(ObClient *self)
3282 {
3283     GSList *ret = NULL;
3284
3285     /* move up the direct transient chain as far as possible */
3286     while (self->transient_for && self->transient_for != OB_TRAN_GROUP)
3287         self = self->transient_for;
3288
3289     if (!self->transient_for)
3290         ret = g_slist_prepend(ret, self);
3291     else {
3292             GSList *it;
3293
3294             g_assert(self->group);
3295
3296             for (it = self->group->members; it; it = g_slist_next(it)) {
3297                 ObClient *c = it->data;
3298
3299                 if (!c->transient_for)
3300                     ret = g_slist_prepend(ret, c);
3301             }
3302
3303             if (ret == NULL) /* no group parents */
3304                 ret = g_slist_prepend(ret, self);
3305     }
3306
3307     return ret;
3308 }
3309
3310 ObClient *client_search_focus_parent(ObClient *self)
3311 {
3312     if (self->transient_for) {
3313         if (self->transient_for != OB_TRAN_GROUP) {
3314             if (client_focused(self->transient_for))
3315                 return self->transient_for;
3316         } else {
3317             GSList *it;
3318
3319             for (it = self->group->members; it; it = g_slist_next(it)) {
3320                 ObClient *c = it->data;
3321
3322                 /* checking transient_for prevents infinate loops! */
3323                 if (c != self && !c->transient_for)
3324                     if (client_focused(c))
3325                         return c;
3326             }
3327         }
3328     }
3329
3330     return NULL;
3331 }
3332
3333 ObClient *client_search_parent(ObClient *self, ObClient *search)
3334 {
3335     if (self->transient_for) {
3336         if (self->transient_for != OB_TRAN_GROUP) {
3337             if (self->transient_for == search)
3338                 return search;
3339         } else {
3340             GSList *it;
3341
3342             for (it = self->group->members; it; it = g_slist_next(it)) {
3343                 ObClient *c = it->data;
3344
3345                 /* checking transient_for prevents infinate loops! */
3346                 if (c != self && !c->transient_for)
3347                     if (c == search)
3348                         return search;
3349             }
3350         }
3351     }
3352
3353     return NULL;
3354 }
3355
3356 ObClient *client_search_transient(ObClient *self, ObClient *search)
3357 {
3358     GSList *sit;
3359
3360     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3361         if (sit->data == search)
3362             return search;
3363         if (client_search_transient(sit->data, search))
3364             return search;
3365     }
3366     return NULL;
3367 }
3368
3369 void client_update_sm_client_id(ObClient *self)
3370 {
3371     g_free(self->sm_client_id);
3372     self->sm_client_id = NULL;
3373
3374     if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3375         self->group)
3376         PROP_GETS(self->group->leader, sm_client_id, locale,
3377                   &self->sm_client_id);
3378 }
3379
3380 #define WANT_EDGE(cur, c) \
3381             if(cur == c)                                                      \
3382                 continue;                                                     \
3383             if(!client_normal(cur))                                   \
3384                 continue;                                                     \
3385             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3386                 continue;                                                     \
3387             if(cur->iconic)                                                   \
3388                 continue;                                                     \
3389             if(cur->layer < c->layer && !config_resist_layers_below)          \
3390                 continue;
3391
3392 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3393             if ((his_edge_start >= my_edge_start && \
3394                  his_edge_start <= my_edge_end) ||  \
3395                 (my_edge_start >= his_edge_start && \
3396                  my_edge_start <= his_edge_end))    \
3397                 dest = his_offset;
3398
3399 /* finds the nearest edge in the given direction from the current client
3400  * note to self: the edge is the -frame- edge (the actual one), not the
3401  * client edge.
3402  */
3403 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3404 {
3405     gint dest, monitor_dest;
3406     gint my_edge_start, my_edge_end, my_offset;
3407     GList *it;
3408     Rect *a, *monitor;
3409     
3410     if(!client_list)
3411         return -1;
3412
3413     a = screen_area(c->desktop);
3414     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3415
3416     switch(dir) {
3417     case OB_DIRECTION_NORTH:
3418         my_edge_start = c->frame->area.x;
3419         my_edge_end = c->frame->area.x + c->frame->area.width;
3420         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3421         
3422         /* default: top of screen */
3423         dest = a->y + (hang ? c->frame->area.height : 0);
3424         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3425         /* if the monitor edge comes before the screen edge, */
3426         /* use that as the destination instead. (For xinerama) */
3427         if (monitor_dest != dest && my_offset > monitor_dest)
3428             dest = monitor_dest; 
3429
3430         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3431             gint his_edge_start, his_edge_end, his_offset;
3432             ObClient *cur = it->data;
3433
3434             WANT_EDGE(cur, c)
3435
3436             his_edge_start = cur->frame->area.x;
3437             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3438             his_offset = cur->frame->area.y + 
3439                          (hang ? 0 : cur->frame->area.height);
3440
3441             if(his_offset + 1 > my_offset)
3442                 continue;
3443
3444             if(his_offset < dest)
3445                 continue;
3446
3447             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3448         }
3449         break;
3450     case OB_DIRECTION_SOUTH:
3451         my_edge_start = c->frame->area.x;
3452         my_edge_end = c->frame->area.x + c->frame->area.width;
3453         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3454
3455         /* default: bottom of screen */
3456         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3457         monitor_dest = monitor->y + monitor->height -
3458                        (hang ? c->frame->area.height : 0);
3459         /* if the monitor edge comes before the screen edge, */
3460         /* use that as the destination instead. (For xinerama) */
3461         if (monitor_dest != dest && my_offset < monitor_dest)
3462             dest = monitor_dest; 
3463
3464         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3465             gint his_edge_start, his_edge_end, his_offset;
3466             ObClient *cur = it->data;
3467
3468             WANT_EDGE(cur, c)
3469
3470             his_edge_start = cur->frame->area.x;
3471             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3472             his_offset = cur->frame->area.y +
3473                          (hang ? cur->frame->area.height : 0);
3474
3475
3476             if(his_offset - 1 < my_offset)
3477                 continue;
3478             
3479             if(his_offset > dest)
3480                 continue;
3481
3482             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3483         }
3484         break;
3485     case OB_DIRECTION_WEST:
3486         my_edge_start = c->frame->area.y;
3487         my_edge_end = c->frame->area.y + c->frame->area.height;
3488         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3489
3490         /* default: leftmost egde of screen */
3491         dest = a->x + (hang ? c->frame->area.width : 0);
3492         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3493         /* if the monitor edge comes before the screen edge, */
3494         /* use that as the destination instead. (For xinerama) */
3495         if (monitor_dest != dest && my_offset > monitor_dest)
3496             dest = monitor_dest;            
3497
3498         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3499             gint his_edge_start, his_edge_end, his_offset;
3500             ObClient *cur = it->data;
3501
3502             WANT_EDGE(cur, c)
3503
3504             his_edge_start = cur->frame->area.y;
3505             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3506             his_offset = cur->frame->area.x +
3507                          (hang ? 0 : cur->frame->area.width);
3508
3509             if(his_offset + 1 > my_offset)
3510                 continue;
3511
3512             if(his_offset < dest)
3513                 continue;
3514
3515             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3516         }
3517        break;
3518     case OB_DIRECTION_EAST:
3519         my_edge_start = c->frame->area.y;
3520         my_edge_end = c->frame->area.y + c->frame->area.height;
3521         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3522         
3523         /* default: rightmost edge of screen */
3524         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3525         monitor_dest = monitor->x + monitor->width -
3526                        (hang ? c->frame->area.width : 0);
3527         /* if the monitor edge comes before the screen edge, */
3528         /* use that as the destination instead. (For xinerama) */
3529         if (monitor_dest != dest && my_offset < monitor_dest)
3530             dest = monitor_dest;            
3531
3532         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3533             gint his_edge_start, his_edge_end, his_offset;
3534             ObClient *cur = it->data;
3535
3536             WANT_EDGE(cur, c)
3537
3538             his_edge_start = cur->frame->area.y;
3539             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3540             his_offset = cur->frame->area.x +
3541                          (hang ? cur->frame->area.width : 0);
3542
3543             if(his_offset - 1 < my_offset)
3544                 continue;
3545             
3546             if(his_offset > dest)
3547                 continue;
3548
3549             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3550         }
3551         break;
3552     case OB_DIRECTION_NORTHEAST:
3553     case OB_DIRECTION_SOUTHEAST:
3554     case OB_DIRECTION_NORTHWEST:
3555     case OB_DIRECTION_SOUTHWEST:
3556         /* not implemented */
3557     default:
3558         g_assert_not_reached();
3559         dest = 0; /* suppress warning */
3560     }
3561     return dest;
3562 }
3563
3564 ObClient* client_under_pointer()
3565 {
3566     gint x, y;
3567     GList *it;
3568     ObClient *ret = NULL;
3569
3570     if (screen_pointer_pos(&x, &y)) {
3571         for (it = stacking_list; it; it = g_list_next(it)) {
3572             if (WINDOW_IS_CLIENT(it->data)) {
3573                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3574                 if (c->frame->visible &&
3575                     RECT_CONTAINS(c->frame->area, x, y)) {
3576                     ret = c;
3577                     break;
3578                 }
3579             }
3580         }
3581     }
3582     return ret;
3583 }
3584
3585 gboolean client_has_group_siblings(ObClient *self)
3586 {
3587     return self->group && self->group->members->next;
3588 }