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