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