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