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