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