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