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