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