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