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