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