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