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