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