]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
add support for _NET_REQUEST_FRAME_EXTENTS
[dana/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2    
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "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             client_raise(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;
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     }
2203 }
2204
2205 static void client_change_wm_state(ObClient *self)
2206 {
2207     gulong state[2];
2208     glong old;
2209
2210     old = self->wmstate;
2211
2212     if (self->shaded || self->iconic ||
2213         (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2214     {
2215         self->wmstate = IconicState;
2216     } else
2217         self->wmstate = NormalState;
2218
2219     if (old != self->wmstate) {
2220         PROP_MSG(self->window, kde_wm_change_state,
2221                  self->wmstate, 1, 0, 0);
2222
2223         state[0] = self->wmstate;
2224         state[1] = None;
2225         PROP_SETA32(self->window, wm_state, wm_state, state, 2);
2226     }
2227 }
2228
2229 static void client_change_state(ObClient *self)
2230 {
2231     gulong netstate[11];
2232     guint num;
2233
2234     num = 0;
2235     if (self->modal)
2236         netstate[num++] = prop_atoms.net_wm_state_modal;
2237     if (self->shaded)
2238         netstate[num++] = prop_atoms.net_wm_state_shaded;
2239     if (self->iconic)
2240         netstate[num++] = prop_atoms.net_wm_state_hidden;
2241     if (self->skip_taskbar)
2242         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
2243     if (self->skip_pager)
2244         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
2245     if (self->fullscreen)
2246         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
2247     if (self->max_vert)
2248         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
2249     if (self->max_horz)
2250         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
2251     if (self->above)
2252         netstate[num++] = prop_atoms.net_wm_state_above;
2253     if (self->below)
2254         netstate[num++] = prop_atoms.net_wm_state_below;
2255     if (self->demands_attention)
2256         netstate[num++] = prop_atoms.net_wm_state_demands_attention;
2257     if (self->undecorated)
2258         netstate[num++] = prop_atoms.openbox_wm_state_undecorated;
2259     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
2260
2261     if (self->frame)
2262         frame_adjust_state(self->frame);
2263 }
2264
2265 ObClient *client_search_focus_tree(ObClient *self)
2266 {
2267     GSList *it;
2268     ObClient *ret;
2269
2270     for (it = self->transients; it; it = g_slist_next(it)) {
2271         if (client_focused(it->data)) return it->data;
2272         if ((ret = client_search_focus_tree(it->data))) return ret;
2273     }
2274     return NULL;
2275 }
2276
2277 ObClient *client_search_focus_tree_full(ObClient *self)
2278 {
2279     if (self->transient_for) {
2280         if (self->transient_for != OB_TRAN_GROUP) {
2281             return client_search_focus_tree_full(self->transient_for);
2282         } else {
2283             GSList *it;
2284             gboolean recursed = FALSE;
2285         
2286             for (it = self->group->members; it; it = g_slist_next(it))
2287                 if (!((ObClient*)it->data)->transient_for) {
2288                     ObClient *c;
2289                     if ((c = client_search_focus_tree_full(it->data)))
2290                         return c;
2291                     recursed = TRUE;
2292                 }
2293             if (recursed)
2294                 return NULL;
2295         }
2296     }
2297
2298     /* this function checks the whole tree, the client_search_focus_tree~
2299        does not, so we need to check this window */
2300     if (client_focused(self))
2301         return self;
2302     return client_search_focus_tree(self);
2303 }
2304
2305 static ObStackingLayer calc_layer(ObClient *self)
2306 {
2307     ObStackingLayer l;
2308
2309     if (self->type == OB_CLIENT_TYPE_DESKTOP)
2310         l = OB_STACKING_LAYER_DESKTOP;
2311     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2312         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2313         else l = OB_STACKING_LAYER_ABOVE;
2314     }
2315     else if ((self->fullscreen ||
2316               /* no decorations and fills the monitor = oldskool fullscreen */
2317               (self->frame != NULL &&
2318                (self->frame->size.right == 0 && self->frame->size.left == 0 &&
2319                 self->frame->size.bottom == 0 && self->frame->size.top == 0 &&
2320                 RECT_EQUAL(self->area,
2321                            *screen_physical_area_monitor
2322                            (client_monitor(self)))))) &&
2323              (client_focused(self) || client_search_focus_tree(self)))
2324         l = OB_STACKING_LAYER_FULLSCREEN;
2325     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2326     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2327     else l = OB_STACKING_LAYER_NORMAL;
2328
2329     return l;
2330 }
2331
2332 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2333                                         ObStackingLayer min, gboolean raised)
2334 {
2335     ObStackingLayer old, own;
2336     GSList *it;
2337
2338     old = self->layer;
2339     own = calc_layer(self);
2340     self->layer = MAX(own, min);
2341
2342     for (it = self->transients; it; it = g_slist_next(it))
2343         client_calc_layer_recursive(it->data, orig,
2344                                     self->layer,
2345                                     raised ? raised : self->layer != old);
2346
2347     if (!raised && self->layer != old)
2348         if (orig->frame) { /* only restack if the original window is managed */
2349             stacking_remove(CLIENT_AS_WINDOW(self));
2350             stacking_add(CLIENT_AS_WINDOW(self));
2351         }
2352 }
2353
2354 void client_calc_layer(ObClient *self)
2355 {
2356     ObClient *orig;
2357     GSList *it;
2358
2359     orig = self;
2360
2361     /* transients take on the layer of their parents */
2362     it = client_search_all_top_parents(self);
2363
2364     for (; it; it = g_slist_next(it))
2365         client_calc_layer_recursive(it->data, orig, 0, FALSE);
2366 }
2367
2368 gboolean client_should_show(ObClient *self)
2369 {
2370     if (self->iconic)
2371         return FALSE;
2372     if (client_normal(self) && screen_showing_desktop)
2373         return FALSE;
2374     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2375         return TRUE;
2376     
2377     return FALSE;
2378 }
2379
2380 void client_show(ObClient *self)
2381 {
2382
2383     if (client_should_show(self)) {
2384         frame_show(self->frame);
2385     }
2386
2387     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2388        needs to be in IconicState. This includes when it is on another
2389        desktop!
2390     */
2391     client_change_wm_state(self);
2392 }
2393
2394 void client_hide(ObClient *self)
2395 {
2396     if (!client_should_show(self)) {
2397         frame_hide(self->frame);
2398     }
2399
2400     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2401        needs to be in IconicState. This includes when it is on another
2402        desktop!
2403     */
2404     client_change_wm_state(self);
2405 }
2406
2407 void client_showhide(ObClient *self)
2408 {
2409
2410     if (client_should_show(self)) {
2411         frame_show(self->frame);
2412     }
2413     else {
2414         frame_hide(self->frame);
2415     }
2416
2417     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2418        needs to be in IconicState. This includes when it is on another
2419        desktop!
2420     */
2421     client_change_wm_state(self);
2422 }
2423
2424 gboolean client_normal(ObClient *self) {
2425     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2426               self->type == OB_CLIENT_TYPE_DOCK ||
2427               self->type == OB_CLIENT_TYPE_SPLASH);
2428 }
2429
2430 gboolean client_helper(ObClient *self)
2431 {
2432     return (self->type == OB_CLIENT_TYPE_UTILITY ||
2433             self->type == OB_CLIENT_TYPE_MENU ||
2434             self->type == OB_CLIENT_TYPE_TOOLBAR);
2435 }
2436
2437 gboolean client_mouse_focusable(ObClient *self)
2438 {
2439     return !(self->type == OB_CLIENT_TYPE_MENU ||
2440              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2441              self->type == OB_CLIENT_TYPE_SPLASH ||
2442              self->type == OB_CLIENT_TYPE_DOCK);
2443 }
2444
2445 gboolean client_enter_focusable(ObClient *self)
2446 {
2447     /* you can focus desktops but it shouldn't on enter */
2448     return (client_mouse_focusable(self) &&
2449             self->type != OB_CLIENT_TYPE_DESKTOP);
2450 }
2451
2452
2453 static void client_apply_startup_state(ObClient *self, gint x, gint y)
2454 {
2455     gboolean pos = FALSE; /* has the window's position been configured? */
2456     gint ox, oy;
2457
2458     /* save the position, and set self->area for these to use */
2459     ox = self->area.x;
2460     oy = self->area.y;
2461     self->area.x = x;
2462     self->area.y = y;
2463
2464     /* set the desktop hint, to make sure that it always exists */
2465     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
2466
2467     /* these are in a carefully crafted order.. */
2468
2469     if (self->iconic) {
2470         self->iconic = FALSE;
2471         client_iconify(self, TRUE, FALSE);
2472     }
2473     if (self->fullscreen) {
2474         self->fullscreen = FALSE;
2475         client_fullscreen(self, TRUE);
2476         pos = TRUE;
2477     }
2478     if (self->undecorated) {
2479         self->undecorated = FALSE;
2480         client_set_undecorated(self, TRUE);
2481     }
2482     if (self->shaded) {
2483         self->shaded = FALSE;
2484         client_shade(self, TRUE);
2485     }
2486     if (self->demands_attention) {
2487         self->demands_attention = FALSE;
2488         client_hilite(self, TRUE);
2489     }
2490   
2491     if (self->max_vert && self->max_horz) {
2492         self->max_vert = self->max_horz = FALSE;
2493         client_maximize(self, TRUE, 0);
2494         pos = TRUE;
2495     } else if (self->max_vert) {
2496         self->max_vert = FALSE;
2497         client_maximize(self, TRUE, 2);
2498         pos = TRUE;
2499     } else if (self->max_horz) {
2500         self->max_horz = FALSE;
2501         client_maximize(self, TRUE, 1);
2502         pos = TRUE;
2503     }
2504
2505     /* if the client didn't get positioned yet, then do so now.
2506        call client_move even if the window is not being moved anywhere, because
2507        when we reparent it and decorate it, it is getting moved and we need to
2508        be telling it so with a ConfigureNotify event.
2509     */
2510     if (!pos) {
2511         /* use the saved position */
2512         self->area.x = ox;
2513         self->area.y = oy;
2514         client_move(self, x, y);
2515     }
2516
2517     /* nothing to do for the other states:
2518        skip_taskbar
2519        skip_pager
2520        modal
2521        above
2522        below
2523     */
2524 }
2525
2526 void client_convert_gravity(ObClient *self, gint gravity, gint *x, gint *y,
2527                             gint w, gint h)
2528 {
2529     gint oldg = self->gravity;
2530
2531     /* get the frame's position from the requested stuff */
2532     self->gravity = gravity;
2533     frame_client_gravity(self->frame, x, y, w, h);
2534     self->gravity = oldg;
2535
2536     /* get the client's position in its true gravity from that */
2537     frame_frame_gravity(self->frame, x, y, w, h);
2538 }
2539
2540 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2541                           gint *logicalw, gint *logicalh,
2542                           gboolean user)
2543 {
2544     Rect desired_area = {*x, *y, *w, *h};
2545
2546     /* make the frame recalculate its dimentions n shit without changing
2547        anything visible for real, this way the constraints below can work with
2548        the updated frame dimensions. */
2549     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2550
2551     /* work within the prefered sizes given by the window */
2552     if (!(*w == self->area.width && *h == self->area.height)) {
2553         gint basew, baseh, minw, minh;
2554
2555         /* base size is substituted with min size if not specified */
2556         if (self->base_size.width || self->base_size.height) {
2557             basew = self->base_size.width;
2558             baseh = self->base_size.height;
2559         } else {
2560             basew = self->min_size.width;
2561             baseh = self->min_size.height;
2562         }
2563         /* min size is substituted with base size if not specified */
2564         if (self->min_size.width || self->min_size.height) {
2565             minw = self->min_size.width;
2566             minh = self->min_size.height;
2567         } else {
2568             minw = self->base_size.width;
2569             minh = self->base_size.height;
2570         }
2571
2572         /* if this is a user-requested resize, then check against min/max
2573            sizes */
2574
2575         /* smaller than min size or bigger than max size? */
2576         if (*w > self->max_size.width) *w = self->max_size.width;
2577         if (*w < minw) *w = minw;
2578         if (*h > self->max_size.height) *h = self->max_size.height;
2579         if (*h < minh) *h = minh;
2580
2581         *w -= basew;
2582         *h -= baseh;
2583
2584         /* keep to the increments */
2585         *w /= self->size_inc.width;
2586         *h /= self->size_inc.height;
2587
2588         /* you cannot resize to nothing */
2589         if (basew + *w < 1) *w = 1 - basew;
2590         if (baseh + *h < 1) *h = 1 - baseh;
2591   
2592         /* save the logical size */
2593         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2594         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2595
2596         *w *= self->size_inc.width;
2597         *h *= self->size_inc.height;
2598
2599         *w += basew;
2600         *h += baseh;
2601
2602         /* adjust the height to match the width for the aspect ratios.
2603            for this, min size is not substituted for base size ever. */
2604         *w -= self->base_size.width;
2605         *h -= self->base_size.height;
2606
2607         if (!self->fullscreen) {
2608             if (self->min_ratio)
2609                 if (*h * self->min_ratio > *w) {
2610                     *h = (gint)(*w / self->min_ratio);
2611
2612                     /* you cannot resize to nothing */
2613                     if (*h < 1) {
2614                         *h = 1;
2615                         *w = (gint)(*h * self->min_ratio);
2616                     }
2617                 }
2618             if (self->max_ratio)
2619                 if (*h * self->max_ratio < *w) {
2620                     *h = (gint)(*w / self->max_ratio);
2621
2622                     /* you cannot resize to nothing */
2623                     if (*h < 1) {
2624                         *h = 1;
2625                         *w = (gint)(*h * self->min_ratio);
2626                     }
2627                 }
2628         }
2629
2630         *w += self->base_size.width;
2631         *h += self->base_size.height;
2632     }
2633
2634     /* gets the frame's position */
2635     frame_client_gravity(self->frame, x, y, *w, *h);
2636
2637     /* these positions are frame positions, not client positions */
2638
2639     /* set the size and position if fullscreen */
2640     if (self->fullscreen) {
2641         Rect *a;
2642         guint i;
2643
2644         i = screen_find_monitor(&desired_area);
2645         a = screen_physical_area_monitor(i);
2646
2647         *x = a->x;
2648         *y = a->y;
2649         *w = a->width;
2650         *h = a->height;
2651
2652         user = FALSE; /* ignore if the client can't be moved/resized when it
2653                          is entering fullscreen */
2654     } else if (self->max_horz || self->max_vert) {
2655         Rect *a;
2656         guint i;
2657
2658         i = screen_find_monitor(&desired_area);
2659         a = screen_area_monitor(self->desktop, i);
2660
2661         /* set the size and position if maximized */
2662         if (self->max_horz) {
2663             *x = a->x;
2664             *w = a->width - self->frame->size.left - self->frame->size.right;
2665         }
2666         if (self->max_vert) {
2667             *y = a->y;
2668             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2669         }
2670
2671         /* maximizing is not allowed if the user can't move+resize the window
2672          */
2673     }
2674
2675     /* gets the client's position */
2676     frame_frame_gravity(self->frame, x, y, *w, *h);
2677
2678     /* these override the above states! if you cant move you can't move! */
2679     if (user) {
2680         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2681             *x = self->area.x;
2682             *y = self->area.y;
2683         }
2684         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2685             *w = self->area.width;
2686             *h = self->area.height;
2687         }
2688     }
2689
2690     g_assert(*w > 0);
2691     g_assert(*h > 0);
2692 }
2693
2694
2695 void client_configure_full(ObClient *self, gint x, gint y, gint w, gint h,
2696                            gboolean user, gboolean final,
2697                            gboolean force_reply)
2698 {
2699     gint oldw, oldh, oldrx, oldry;
2700     gboolean send_resize_client;
2701     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
2702     guint fdecor = self->frame->decorations;
2703     gboolean fhorz = self->frame->max_horz;
2704     gint logicalw, logicalh;
2705
2706     /* find the new x, y, width, and height (and logical size) */
2707     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2708
2709     /* set the logical size if things changed */
2710     if (!(w == self->area.width && h == self->area.height))
2711         SIZE_SET(self->logical_size, logicalw, logicalh);
2712
2713     /* figure out if we moved or resized or what */
2714     moved = x != self->area.x || y != self->area.y;
2715     resized = w != self->area.width || h != self->area.height;
2716
2717     oldw = self->area.width;
2718     oldh = self->area.height;
2719     RECT_SET(self->area, x, y, w, h);
2720
2721     /* for app-requested resizes, always resize if 'resized' is true.
2722        for user-requested ones, only resize if final is true, or when
2723        resizing in redraw mode */
2724     send_resize_client = ((!user && resized) ||
2725                           (user && (final ||
2726                                     (resized && config_resize_redraw))));
2727
2728     /* if the client is enlarging, then resize the client before the frame */
2729     if (send_resize_client && user && (w > oldw || h > oldh)) {
2730         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2731         /* resize the plate to show the client padding color underneath */
2732         frame_adjust_client_area(self->frame);
2733     }
2734
2735     /* find the frame's dimensions and move/resize it */
2736     if (self->decorations != fdecor || self->max_horz != fhorz)
2737         moved = resized = TRUE;
2738     if (moved || resized)
2739         frame_adjust_area(self->frame, moved, resized, FALSE);
2740
2741     /* find the client's position relative to the root window */
2742     oldrx = self->root_pos.x;
2743     oldry = self->root_pos.y;
2744     rootmoved = (oldrx != (signed)(self->frame->area.x +
2745                                    self->frame->size.left -
2746                                    self->border_width) ||
2747                  oldry != (signed)(self->frame->area.y +
2748                                    self->frame->size.top -
2749                                    self->border_width));
2750
2751     if (force_reply || ((!user || (user && final)) && rootmoved))
2752     {
2753         XEvent event;
2754
2755         POINT_SET(self->root_pos,
2756                   self->frame->area.x + self->frame->size.left -
2757                   self->border_width,
2758                   self->frame->area.y + self->frame->size.top -
2759                   self->border_width);
2760
2761         event.type = ConfigureNotify;
2762         event.xconfigure.display = ob_display;
2763         event.xconfigure.event = self->window;
2764         event.xconfigure.window = self->window;
2765
2766         /* root window real coords */
2767         event.xconfigure.x = self->root_pos.x;
2768         event.xconfigure.y = self->root_pos.y;
2769         event.xconfigure.width = w;
2770         event.xconfigure.height = h;
2771         event.xconfigure.border_width = 0;
2772         event.xconfigure.above = self->frame->plate;
2773         event.xconfigure.override_redirect = FALSE;
2774         XSendEvent(event.xconfigure.display, event.xconfigure.window,
2775                    FALSE, StructureNotifyMask, &event);
2776     }
2777
2778     /* if the client is shrinking, then resize the frame before the client */
2779     if (send_resize_client && (!user || (w <= oldw || h <= oldh))) {
2780         /* resize the plate to show the client padding color underneath */
2781         frame_adjust_client_area(self->frame);
2782
2783         XResizeWindow(ob_display, self->window, w, h);
2784     }
2785
2786     XFlush(ob_display);
2787 }
2788
2789 void client_fullscreen(ObClient *self, gboolean fs)
2790 {
2791     gint x, y, w, h;
2792
2793     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2794         self->fullscreen == fs) return;                   /* already done */
2795
2796     self->fullscreen = fs;
2797     client_change_state(self); /* change the state hints on the client */
2798     client_calc_layer(self);   /* and adjust out layer/stacking */
2799
2800     if (fs) {
2801         self->pre_fullscreen_area = self->area;
2802         /* if the window is maximized, its area isn't all that meaningful.
2803            save it's premax area instead. */
2804         if (self->max_horz) {
2805             self->pre_fullscreen_area.x = self->pre_max_area.x;
2806             self->pre_fullscreen_area.width = self->pre_max_area.width;
2807         }
2808         if (self->max_vert) {
2809             self->pre_fullscreen_area.y = self->pre_max_area.y;
2810             self->pre_fullscreen_area.height = self->pre_max_area.height;
2811         }
2812
2813         /* these are not actually used cuz client_configure will set them
2814            as appropriate when the window is fullscreened */
2815         x = y = w = h = 0;
2816     } else {
2817         Rect *a;
2818
2819         if (self->pre_fullscreen_area.width > 0 &&
2820             self->pre_fullscreen_area.height > 0)
2821         {
2822             x = self->pre_fullscreen_area.x;
2823             y = self->pre_fullscreen_area.y;
2824             w = self->pre_fullscreen_area.width;
2825             h = self->pre_fullscreen_area.height;
2826             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2827         } else {
2828             /* pick some fallbacks... */
2829             a = screen_area_monitor(self->desktop, 0);
2830             x = a->x + a->width / 4;
2831             y = a->y + a->height / 4;
2832             w = a->width / 2;
2833             h = a->height / 2;
2834         }
2835     }
2836
2837     client_setup_decor_and_functions(self);
2838
2839     client_move_resize(self, x, y, w, h);
2840
2841     /* try focus us when we go into fullscreen mode */
2842     client_focus(self);
2843 }
2844
2845 static void client_iconify_recursive(ObClient *self,
2846                                      gboolean iconic, gboolean curdesk)
2847 {
2848     GSList *it;
2849     gboolean changed = FALSE;
2850
2851
2852     if (self->iconic != iconic) {
2853         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2854                  self->window);
2855
2856         if (iconic) {
2857             /* don't let non-normal windows iconify along with their parents
2858                or whatever */
2859             if (client_normal(self)) {
2860                 self->iconic = iconic;
2861
2862                 /* update the focus lists.. iconic windows go to the bottom of
2863                    the list, put the new iconic window at the 'top of the
2864                    bottom'. */
2865                 focus_order_to_top(self);
2866
2867                 changed = TRUE;
2868             }
2869         } else {
2870             self->iconic = iconic;
2871
2872             if (curdesk && self->desktop != screen_desktop &&
2873                 self->desktop != DESKTOP_ALL)
2874                 client_set_desktop(self, screen_desktop, FALSE);
2875
2876             /* this puts it after the current focused window */
2877             focus_order_remove(self);
2878             focus_order_add_new(self);
2879
2880             changed = TRUE;
2881         }
2882     }
2883
2884     if (changed) {
2885         client_change_state(self);
2886         if (ob_state() != OB_STATE_STARTING && config_animate_iconify)
2887             frame_begin_iconify_animation(self->frame, iconic);
2888         /* do this after starting the animation so it doesn't flash */
2889         client_showhide(self);
2890     }
2891
2892     /* iconify all direct transients, and deiconify all transients
2893        (non-direct too) */
2894     for (it = self->transients; it; it = g_slist_next(it))
2895         if (it->data != self)
2896             if (client_is_direct_child(self, it->data) || !iconic)
2897                 client_iconify_recursive(it->data, iconic, curdesk);
2898 }
2899
2900 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2901 {
2902     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
2903         /* move up the transient chain as far as possible first */
2904         self = client_search_top_normal_parent(self);
2905         client_iconify_recursive(self, iconic, curdesk);
2906     }
2907 }
2908
2909 void client_maximize(ObClient *self, gboolean max, gint dir)
2910 {
2911     gint x, y, w, h;
2912      
2913     g_assert(dir == 0 || dir == 1 || dir == 2);
2914     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2915
2916     /* check if already done */
2917     if (max) {
2918         if (dir == 0 && self->max_horz && self->max_vert) return;
2919         if (dir == 1 && self->max_horz) return;
2920         if (dir == 2 && self->max_vert) return;
2921     } else {
2922         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2923         if (dir == 1 && !self->max_horz) return;
2924         if (dir == 2 && !self->max_vert) return;
2925     }
2926
2927     /* we just tell it to configure in the same place and client_configure
2928        worries about filling the screen with the window */
2929     x = self->area.x;
2930     y = self->area.y;
2931     w = self->area.width;
2932     h = self->area.height;
2933
2934     if (max) {
2935         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2936             RECT_SET(self->pre_max_area,
2937                      self->area.x, self->pre_max_area.y,
2938                      self->area.width, self->pre_max_area.height);
2939         }
2940         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2941             RECT_SET(self->pre_max_area,
2942                      self->pre_max_area.x, self->area.y,
2943                      self->pre_max_area.width, self->area.height);
2944         }
2945     } else {
2946         Rect *a;
2947
2948         a = screen_area_monitor(self->desktop, 0);
2949         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2950             if (self->pre_max_area.width > 0) {
2951                 x = self->pre_max_area.x;
2952                 w = self->pre_max_area.width;
2953
2954                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2955                          0, self->pre_max_area.height);
2956             } else {
2957                 /* pick some fallbacks... */
2958                 x = a->x + a->width / 4;
2959                 w = a->width / 2;
2960             }
2961         }
2962         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2963             if (self->pre_max_area.height > 0) {
2964                 y = self->pre_max_area.y;
2965                 h = self->pre_max_area.height;
2966
2967                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2968                          self->pre_max_area.width, 0);
2969             } else {
2970                 /* pick some fallbacks... */
2971                 y = a->y + a->height / 4;
2972                 h = a->height / 2;
2973             }
2974         }
2975     }
2976
2977     if (dir == 0 || dir == 1) /* horz */
2978         self->max_horz = max;
2979     if (dir == 0 || dir == 2) /* vert */
2980         self->max_vert = max;
2981
2982     client_change_state(self); /* change the state hints on the client */
2983
2984     client_setup_decor_and_functions(self);
2985
2986     client_move_resize(self, x, y, w, h);
2987 }
2988
2989 void client_shade(ObClient *self, gboolean shade)
2990 {
2991     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2992          shade) ||                         /* can't shade */
2993         self->shaded == shade) return;     /* already done */
2994
2995     self->shaded = shade;
2996     client_change_state(self);
2997     client_change_wm_state(self); /* the window is being hidden/shown */
2998     /* resize the frame to just the titlebar */
2999     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
3000 }
3001
3002 void client_close(ObClient *self)
3003 {
3004     XEvent ce;
3005
3006     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3007
3008     /* in the case that the client provides no means to requesting that it
3009        close, we just kill it */
3010     if (!self->delete_window)
3011         client_kill(self);
3012     
3013     /*
3014       XXX: itd be cool to do timeouts and shit here for killing the client's
3015       process off
3016       like... if the window is around after 5 seconds, then the close button
3017       turns a nice red, and if this function is called again, the client is
3018       explicitly killed.
3019     */
3020
3021     ce.xclient.type = ClientMessage;
3022     ce.xclient.message_type =  prop_atoms.wm_protocols;
3023     ce.xclient.display = ob_display;
3024     ce.xclient.window = self->window;
3025     ce.xclient.format = 32;
3026     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
3027     ce.xclient.data.l[1] = event_curtime;
3028     ce.xclient.data.l[2] = 0l;
3029     ce.xclient.data.l[3] = 0l;
3030     ce.xclient.data.l[4] = 0l;
3031     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3032 }
3033
3034 void client_kill(ObClient *self)
3035 {
3036     XKillClient(ob_display, self->window);
3037 }
3038
3039 void client_hilite(ObClient *self, gboolean hilite)
3040 {
3041     if (self->demands_attention == hilite)
3042         return; /* no change */
3043
3044     /* don't allow focused windows to hilite */
3045     self->demands_attention = hilite && !client_focused(self);
3046     if (self->frame != NULL) { /* if we're mapping, just set the state */
3047         if (self->demands_attention)
3048             frame_flash_start(self->frame);
3049         else
3050             frame_flash_stop(self->frame);
3051         client_change_state(self);
3052     }
3053 }
3054
3055 void client_set_desktop_recursive(ObClient *self,
3056                                   guint target,
3057                                   gboolean donthide)
3058 {
3059     guint old;
3060     GSList *it;
3061
3062     if (target != self->desktop) {
3063
3064         ob_debug("Setting desktop %u\n", target+1);
3065
3066         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3067
3068         old = self->desktop;
3069         self->desktop = target;
3070         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3071         /* the frame can display the current desktop state */
3072         frame_adjust_state(self->frame);
3073         /* 'move' the window to the new desktop */
3074         if (!donthide)
3075             client_showhide(self);
3076         /* raise if it was not already on the desktop */
3077         if (old != DESKTOP_ALL)
3078             client_raise(self);
3079         if (STRUT_EXISTS(self->strut))
3080             screen_update_areas();
3081     }
3082
3083     /* move all transients */
3084     for (it = self->transients; it; it = g_slist_next(it))
3085         if (it->data != self)
3086             if (client_is_direct_child(self, it->data))
3087                 client_set_desktop_recursive(it->data, target, donthide);
3088 }
3089
3090 void client_set_desktop(ObClient *self, guint target,
3091                         gboolean donthide)
3092 {
3093     self = client_search_top_normal_parent(self);
3094     client_set_desktop_recursive(self, target, donthide);
3095 }
3096
3097 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3098 {
3099     while (child != parent &&
3100            child->transient_for && child->transient_for != OB_TRAN_GROUP)
3101         child = child->transient_for;
3102     return child == parent;
3103 }
3104
3105 ObClient *client_search_modal_child(ObClient *self)
3106 {
3107     GSList *it;
3108     ObClient *ret;
3109   
3110     for (it = self->transients; it; it = g_slist_next(it)) {
3111         ObClient *c = it->data;
3112         if ((ret = client_search_modal_child(c))) return ret;
3113         if (c->modal) return c;
3114     }
3115     return NULL;
3116 }
3117
3118 gboolean client_validate(ObClient *self)
3119 {
3120     XEvent e; 
3121
3122     XSync(ob_display, FALSE); /* get all events on the server */
3123
3124     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3125         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3126         XPutBackEvent(ob_display, &e);
3127         return FALSE;
3128     }
3129
3130     return TRUE;
3131 }
3132
3133 void client_set_wm_state(ObClient *self, glong state)
3134 {
3135     if (state == self->wmstate) return; /* no change */
3136   
3137     switch (state) {
3138     case IconicState:
3139         client_iconify(self, TRUE, TRUE);
3140         break;
3141     case NormalState:
3142         client_iconify(self, FALSE, TRUE);
3143         break;
3144     }
3145 }
3146
3147 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3148 {
3149     gboolean shaded = self->shaded;
3150     gboolean fullscreen = self->fullscreen;
3151     gboolean undecorated = self->undecorated;
3152     gboolean max_horz = self->max_horz;
3153     gboolean max_vert = self->max_vert;
3154     gboolean modal = self->modal;
3155     gboolean iconic = self->iconic;
3156     gboolean demands_attention = self->demands_attention;
3157     gboolean above = self->above;
3158     gboolean below = self->below;
3159     gint i;
3160
3161     if (!(action == prop_atoms.net_wm_state_add ||
3162           action == prop_atoms.net_wm_state_remove ||
3163           action == prop_atoms.net_wm_state_toggle))
3164         /* an invalid action was passed to the client message, ignore it */
3165         return; 
3166
3167     for (i = 0; i < 2; ++i) {
3168         Atom state = i == 0 ? data1 : data2;
3169     
3170         if (!state) continue;
3171
3172         /* if toggling, then pick whether we're adding or removing */
3173         if (action == prop_atoms.net_wm_state_toggle) {
3174             if (state == prop_atoms.net_wm_state_modal)
3175                 action = modal ? prop_atoms.net_wm_state_remove :
3176                     prop_atoms.net_wm_state_add;
3177             else if (state == prop_atoms.net_wm_state_maximized_vert)
3178                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3179                     prop_atoms.net_wm_state_add;
3180             else if (state == prop_atoms.net_wm_state_maximized_horz)
3181                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3182                     prop_atoms.net_wm_state_add;
3183             else if (state == prop_atoms.net_wm_state_shaded)
3184                 action = shaded ? prop_atoms.net_wm_state_remove :
3185                     prop_atoms.net_wm_state_add;
3186             else if (state == prop_atoms.net_wm_state_skip_taskbar)
3187                 action = self->skip_taskbar ?
3188                     prop_atoms.net_wm_state_remove :
3189                     prop_atoms.net_wm_state_add;
3190             else if (state == prop_atoms.net_wm_state_skip_pager)
3191                 action = self->skip_pager ?
3192                     prop_atoms.net_wm_state_remove :
3193                     prop_atoms.net_wm_state_add;
3194             else if (state == prop_atoms.net_wm_state_hidden)
3195                 action = self->iconic ?
3196                     prop_atoms.net_wm_state_remove :
3197                     prop_atoms.net_wm_state_add;
3198             else if (state == prop_atoms.net_wm_state_fullscreen)
3199                 action = fullscreen ?
3200                     prop_atoms.net_wm_state_remove :
3201                     prop_atoms.net_wm_state_add;
3202             else if (state == prop_atoms.net_wm_state_above)
3203                 action = self->above ? prop_atoms.net_wm_state_remove :
3204                     prop_atoms.net_wm_state_add;
3205             else if (state == prop_atoms.net_wm_state_below)
3206                 action = self->below ? prop_atoms.net_wm_state_remove :
3207                     prop_atoms.net_wm_state_add;
3208             else if (state == prop_atoms.net_wm_state_demands_attention)
3209                 action = self->demands_attention ?
3210                     prop_atoms.net_wm_state_remove :
3211                     prop_atoms.net_wm_state_add;
3212             else if (state == prop_atoms.openbox_wm_state_undecorated)
3213                 action = undecorated ? prop_atoms.net_wm_state_remove :
3214                     prop_atoms.net_wm_state_add;
3215         }
3216     
3217         if (action == prop_atoms.net_wm_state_add) {
3218             if (state == prop_atoms.net_wm_state_modal) {
3219                 modal = TRUE;
3220             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3221                 max_vert = TRUE;
3222             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3223                 max_horz = TRUE;
3224             } else if (state == prop_atoms.net_wm_state_shaded) {
3225                 shaded = TRUE;
3226             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3227                 self->skip_taskbar = TRUE;
3228             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3229                 self->skip_pager = TRUE;
3230             } else if (state == prop_atoms.net_wm_state_hidden) {
3231                 iconic = TRUE;
3232             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3233                 fullscreen = TRUE;
3234             } else if (state == prop_atoms.net_wm_state_above) {
3235                 above = TRUE;
3236                 below = FALSE;
3237             } else if (state == prop_atoms.net_wm_state_below) {
3238                 above = FALSE;
3239                 below = TRUE;
3240             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3241                 demands_attention = TRUE;
3242             } else if (state == prop_atoms.openbox_wm_state_undecorated) {
3243                 undecorated = TRUE;
3244             }
3245
3246         } else { /* action == prop_atoms.net_wm_state_remove */
3247             if (state == prop_atoms.net_wm_state_modal) {
3248                 modal = FALSE;
3249             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3250                 max_vert = FALSE;
3251             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3252                 max_horz = FALSE;
3253             } else if (state == prop_atoms.net_wm_state_shaded) {
3254                 shaded = FALSE;
3255             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3256                 self->skip_taskbar = FALSE;
3257             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3258                 self->skip_pager = FALSE;
3259             } else if (state == prop_atoms.net_wm_state_hidden) {
3260                 iconic = FALSE;
3261             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3262                 fullscreen = FALSE;
3263             } else if (state == prop_atoms.net_wm_state_above) {
3264                 above = FALSE;
3265             } else if (state == prop_atoms.net_wm_state_below) {
3266                 below = FALSE;
3267             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3268                 demands_attention = FALSE;
3269             } else if (state == prop_atoms.openbox_wm_state_undecorated) {
3270                 undecorated = FALSE;
3271             }
3272         }
3273     }
3274     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3275         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3276             /* toggling both */
3277             if (max_horz == max_vert) { /* both going the same way */
3278                 client_maximize(self, max_horz, 0);
3279             } else {
3280                 client_maximize(self, max_horz, 1);
3281                 client_maximize(self, max_vert, 2);
3282             }
3283         } else {
3284             /* toggling one */
3285             if (max_horz != self->max_horz)
3286                 client_maximize(self, max_horz, 1);
3287             else
3288                 client_maximize(self, max_vert, 2);
3289         }
3290     }
3291     /* change fullscreen state before shading, as it will affect if the window
3292        can shade or not */
3293     if (fullscreen != self->fullscreen)
3294         client_fullscreen(self, fullscreen);
3295     if (shaded != self->shaded)
3296         client_shade(self, shaded);
3297     if (undecorated != self->undecorated)
3298         client_set_undecorated(self, undecorated);
3299     if (modal != self->modal) {
3300         self->modal = modal;
3301         /* when a window changes modality, then its stacking order with its
3302            transients needs to change */
3303         client_raise(self);
3304     }
3305     if (iconic != self->iconic)
3306         client_iconify(self, iconic, FALSE);
3307
3308     if (demands_attention != self->demands_attention)
3309         client_hilite(self, demands_attention);
3310
3311     if (above != self->above || below != self->below) {
3312         self->above = above;
3313         self->below = below;
3314         client_calc_layer(self);
3315     }
3316
3317     client_change_state(self); /* change the hint to reflect these changes */
3318 }
3319
3320 ObClient *client_focus_target(ObClient *self)
3321 {
3322     ObClient *child = NULL;
3323
3324     child = client_search_modal_child(self);
3325     if (child) return child;
3326     return self;
3327 }
3328
3329 gboolean client_can_focus(ObClient *self)
3330 {
3331     XEvent ev;
3332
3333     /* choose the correct target */
3334     self = client_focus_target(self);
3335
3336     if (!self->frame->visible)
3337         return FALSE;
3338
3339     if (!(self->can_focus || self->focus_notify))
3340         return FALSE;
3341
3342     /* do a check to see if the window has already been unmapped or destroyed
3343        do this intelligently while watching out for unmaps we've generated
3344        (ignore_unmaps > 0) */
3345     if (XCheckTypedWindowEvent(ob_display, self->window,
3346                                DestroyNotify, &ev)) {
3347         XPutBackEvent(ob_display, &ev);
3348         return FALSE;
3349     }
3350     while (XCheckTypedWindowEvent(ob_display, self->window,
3351                                   UnmapNotify, &ev)) {
3352         if (self->ignore_unmaps) {
3353             self->ignore_unmaps--;
3354         } else {
3355             XPutBackEvent(ob_display, &ev);
3356             return FALSE;
3357         }
3358     }
3359
3360     return TRUE;
3361 }
3362
3363 gboolean client_focus(ObClient *self)
3364 {
3365     /* choose the correct target */
3366     self = client_focus_target(self);
3367
3368     if (!client_can_focus(self)) {
3369         if (!self->frame->visible) {
3370             /* update the focus lists */
3371             focus_order_to_top(self);
3372         }
3373         return FALSE;
3374     }
3375
3376     ob_debug_type(OB_DEBUG_FOCUS,
3377                   "Focusing client \"%s\" at time %u\n",
3378                   self->title, event_curtime);
3379
3380     if (self->can_focus) {
3381         /* This can cause a BadMatch error with CurrentTime, or if an app
3382            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3383         xerror_set_ignore(TRUE);
3384         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3385                        event_curtime);
3386         xerror_set_ignore(FALSE);
3387     }
3388
3389     if (self->focus_notify) {
3390         XEvent ce;
3391         ce.xclient.type = ClientMessage;
3392         ce.xclient.message_type = prop_atoms.wm_protocols;
3393         ce.xclient.display = ob_display;
3394         ce.xclient.window = self->window;
3395         ce.xclient.format = 32;
3396         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3397         ce.xclient.data.l[1] = event_curtime;
3398         ce.xclient.data.l[2] = 0l;
3399         ce.xclient.data.l[3] = 0l;
3400         ce.xclient.data.l[4] = 0l;
3401         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3402     }
3403
3404 #ifdef DEBUG_FOCUS
3405     ob_debug("%sively focusing %lx at %d\n",
3406              (self->can_focus ? "act" : "pass"),
3407              self->window, (gint) event_curtime);
3408 #endif
3409
3410     /* Cause the FocusIn to come back to us. Important for desktop switches,
3411        since otherwise we'll have no FocusIn on the queue and send it off to
3412        the focus_backup. */
3413     XSync(ob_display, FALSE);
3414     return TRUE;
3415 }
3416
3417 /*! Present the client to the user.
3418   @param raise If the client should be raised or not. You should only set
3419                raise to false if you don't care if the window is completely
3420                hidden.
3421 */
3422 static void client_present(ObClient *self, gboolean here, gboolean raise)
3423 {
3424     /* if using focus_delay, stop the timer now so that focus doesn't
3425        go moving on us */
3426     event_halt_focus_delay();
3427
3428     if (client_normal(self) && screen_showing_desktop)
3429         screen_show_desktop(FALSE, FALSE);
3430     if (self->iconic)
3431         client_iconify(self, FALSE, here);
3432     if (self->desktop != DESKTOP_ALL &&
3433         self->desktop != screen_desktop)
3434     {
3435         if (here)
3436             client_set_desktop(self, screen_desktop, FALSE);
3437         else
3438             screen_set_desktop(self->desktop, FALSE);
3439     } else if (!self->frame->visible)
3440         /* if its not visible for other reasons, then don't mess
3441            with it */
3442         return;
3443     if (self->shaded)
3444         client_shade(self, FALSE);
3445
3446     client_focus(self);
3447
3448     if (raise) {
3449         /* we do this as an action here. this is rather important. this is
3450            because we want the results from the focus change to take place 
3451            BEFORE we go about raising the window. when a fullscreen window 
3452            loses focus, we need this or else the raise wont be able to raise 
3453            above the to-lose-focus fullscreen window. */
3454         client_raise(self);
3455     }
3456 }
3457
3458 void client_activate(ObClient *self, gboolean here, gboolean user)
3459 {
3460     guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3461     gboolean allow = FALSE;
3462
3463     /* if the request came from the user, or if nothing is focused, then grant
3464        the request.
3465        if the currently focused app doesn't set a user_time, then it can't
3466        benefit from any focus stealing prevention.
3467     */
3468     if (user || !focus_client || !last_time)
3469         allow = TRUE;
3470     /* otherwise, if they didn't give a time stamp or if it is too old, they
3471        don't get focus */
3472     else
3473         allow = event_curtime && event_time_after(event_curtime, last_time);
3474
3475     ob_debug_type(OB_DEBUG_FOCUS,
3476                   "Want to activate window 0x%x with time %u (last time %u), "
3477                   "source=%s allowing? %d\n",
3478                   self->window, event_curtime, last_time,
3479                   (user ? "user" : "application"), allow);
3480
3481     if (allow) {
3482         if (event_curtime != CurrentTime)
3483             self->user_time = event_curtime;
3484
3485         client_present(self, here, TRUE);
3486     } else
3487         /* don't focus it but tell the user it wants attention */
3488         client_hilite(self, TRUE);
3489 }
3490
3491 static void client_bring_helper_windows_recursive(ObClient *self,
3492                                                   guint desktop)
3493 {
3494     GSList *it;
3495
3496     for (it = self->transients; it; it = g_slist_next(it))
3497         client_bring_helper_windows_recursive(it->data, desktop);
3498
3499     if (client_helper(self) &&
3500         self->desktop != desktop && self->desktop != DESKTOP_ALL)
3501     {
3502         client_set_desktop(self, desktop, FALSE);
3503     }
3504 }
3505
3506 void client_bring_helper_windows(ObClient *self)
3507 {
3508     client_bring_helper_windows_recursive(self, self->desktop);
3509 }
3510
3511 void client_raise(ObClient *self)
3512 {
3513     action_run_string("Raise", self, CurrentTime);
3514 }
3515
3516 void client_lower(ObClient *self)
3517 {
3518     action_run_string("Lower", self, CurrentTime);
3519 }
3520
3521 gboolean client_focused(ObClient *self)
3522 {
3523     return self == focus_client;
3524 }
3525
3526 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3527 {
3528     guint i;
3529     /* si is the smallest image >= req */
3530     /* li is the largest image < req */
3531     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3532
3533     if (!self->nicons) {
3534         ObClientIcon *parent = NULL;
3535
3536         if (self->transient_for) {
3537             if (self->transient_for != OB_TRAN_GROUP)
3538                 parent = client_icon_recursive(self->transient_for, w, h);
3539             else {
3540                 GSList *it;
3541                 for (it = self->group->members; it; it = g_slist_next(it)) {
3542                     ObClient *c = it->data;
3543                     if (c != self && !c->transient_for) {
3544                         if ((parent = client_icon_recursive(c, w, h)))
3545                             break;
3546                     }
3547                 }
3548             }
3549         }
3550         
3551         return parent;
3552     }
3553
3554     for (i = 0; i < self->nicons; ++i) {
3555         size = self->icons[i].width * self->icons[i].height;
3556         if (size < smallest && size >= (unsigned)(w * h)) {
3557             smallest = size;
3558             si = i;
3559         }
3560         if (size > largest && size <= (unsigned)(w * h)) {
3561             largest = size;
3562             li = i;
3563         }
3564     }
3565     if (largest == 0) /* didnt find one smaller than the requested size */
3566         return &self->icons[si];
3567     return &self->icons[li];
3568 }
3569
3570 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3571 {
3572     ObClientIcon *ret;
3573     static ObClientIcon deficon;
3574
3575     if (!(ret = client_icon_recursive(self, w, h))) {
3576         deficon.width = deficon.height = 48;
3577         deficon.data = ob_rr_theme->def_win_icon;
3578         ret = &deficon;
3579     }
3580     return ret;
3581 }
3582
3583 void client_set_layer(ObClient *self, gint layer)
3584 {
3585     if (layer < 0) {
3586         self->below = TRUE;
3587         self->above = FALSE;
3588     } else if (layer == 0) {
3589         self->below = self->above = FALSE;
3590     } else {
3591         self->below = FALSE;
3592         self->above = TRUE;
3593     }
3594     client_calc_layer(self);
3595     client_change_state(self); /* reflect this in the state hints */
3596 }
3597
3598 void client_set_undecorated(ObClient *self, gboolean undecorated)
3599 {
3600     if (self->undecorated != undecorated) {
3601         self->undecorated = undecorated;
3602         client_setup_decor_and_functions(self);
3603         /* Make sure the client knows it might have moved. Maybe there is a
3604          * better way of doing this so only one client_configure is sent, but
3605          * since 125 of these are sent per second when moving the window (with
3606          * user = FALSE) i doubt it matters much.
3607          */
3608         client_configure(self, self->area.x, self->area.y,
3609                          self->area.width, self->area.height, TRUE, TRUE);
3610         client_change_state(self); /* reflect this in the state hints */
3611     }
3612 }
3613
3614 guint client_monitor(ObClient *self)
3615 {
3616     return screen_find_monitor(&self->frame->area);
3617 }
3618
3619 ObClient *client_search_top_normal_parent(ObClient *self)
3620 {
3621     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3622            client_normal(self->transient_for))
3623         self = self->transient_for;
3624     return self;
3625 }
3626
3627 static GSList *client_search_all_top_parents_internal(ObClient *self,
3628                                                       gboolean bylayer,
3629                                                       ObStackingLayer layer)
3630 {
3631     GSList *ret = NULL;
3632     
3633     /* move up the direct transient chain as far as possible */
3634     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3635            (!bylayer || self->transient_for->layer == layer) &&
3636            client_normal(self->transient_for))
3637         self = self->transient_for;
3638
3639     if (!self->transient_for)
3640         ret = g_slist_prepend(ret, self);
3641     else {
3642             GSList *it;
3643
3644             g_assert(self->group);
3645
3646             for (it = self->group->members; it; it = g_slist_next(it)) {
3647                 ObClient *c = it->data;
3648
3649                 if (!c->transient_for && client_normal(c) &&
3650                     (!bylayer || c->layer == layer))
3651                 {
3652                     ret = g_slist_prepend(ret, c);
3653                 }
3654             }
3655
3656             if (ret == NULL) /* no group parents */
3657                 ret = g_slist_prepend(ret, self);
3658     }
3659
3660     return ret;
3661 }
3662
3663 GSList *client_search_all_top_parents(ObClient *self)
3664 {
3665     return client_search_all_top_parents_internal(self, FALSE, 0);
3666 }
3667
3668 GSList *client_search_all_top_parents_layer(ObClient *self)
3669 {
3670     return client_search_all_top_parents_internal(self, TRUE, self->layer);
3671 }
3672
3673 ObClient *client_search_focus_parent(ObClient *self)
3674 {
3675     if (self->transient_for) {
3676         if (self->transient_for != OB_TRAN_GROUP) {
3677             if (client_focused(self->transient_for))
3678                 return self->transient_for;
3679         } else {
3680             GSList *it;
3681
3682             for (it = self->group->members; it; it = g_slist_next(it)) {
3683                 ObClient *c = it->data;
3684
3685                 /* checking transient_for prevents infinate loops! */
3686                 if (c != self && !c->transient_for)
3687                     if (client_focused(c))
3688                         return c;
3689             }
3690         }
3691     }
3692
3693     return NULL;
3694 }
3695
3696 ObClient *client_search_parent(ObClient *self, ObClient *search)
3697 {
3698     if (self->transient_for) {
3699         if (self->transient_for != OB_TRAN_GROUP) {
3700             if (self->transient_for == search)
3701                 return search;
3702         } else {
3703             GSList *it;
3704
3705             for (it = self->group->members; it; it = g_slist_next(it)) {
3706                 ObClient *c = it->data;
3707
3708                 /* checking transient_for prevents infinate loops! */
3709                 if (c != self && !c->transient_for)
3710                     if (c == search)
3711                         return search;
3712             }
3713         }
3714     }
3715
3716     return NULL;
3717 }
3718
3719 ObClient *client_search_transient(ObClient *self, ObClient *search)
3720 {
3721     GSList *sit;
3722
3723     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3724         if (sit->data == search)
3725             return search;
3726         if (client_search_transient(sit->data, search))
3727             return search;
3728     }
3729     return NULL;
3730 }
3731
3732 #define WANT_EDGE(cur, c) \
3733             if(cur == c)                                                      \
3734                 continue;                                                     \
3735             if(!client_normal(cur))                                           \
3736                 continue;                                                     \
3737             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3738                 continue;                                                     \
3739             if(cur->iconic)                                                   \
3740                 continue;                                                     \
3741             if(cur->layer < c->layer && !config_resist_layers_below)          \
3742                 continue;
3743
3744 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3745             if ((his_edge_start >= my_edge_start && \
3746                  his_edge_start <= my_edge_end) ||  \
3747                 (my_edge_start >= his_edge_start && \
3748                  my_edge_start <= his_edge_end))    \
3749                 dest = his_offset;
3750
3751 /* finds the nearest edge in the given direction from the current client
3752  * note to self: the edge is the -frame- edge (the actual one), not the
3753  * client edge.
3754  */
3755 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3756 {
3757     gint dest, monitor_dest;
3758     gint my_edge_start, my_edge_end, my_offset;
3759     GList *it;
3760     Rect *a, *monitor;
3761     
3762     if(!client_list)
3763         return -1;
3764
3765     a = screen_area(c->desktop);
3766     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3767
3768     switch(dir) {
3769     case OB_DIRECTION_NORTH:
3770         my_edge_start = c->frame->area.x;
3771         my_edge_end = c->frame->area.x + c->frame->area.width;
3772         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3773         
3774         /* default: top of screen */
3775         dest = a->y + (hang ? c->frame->area.height : 0);
3776         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3777         /* if the monitor edge comes before the screen edge, */
3778         /* use that as the destination instead. (For xinerama) */
3779         if (monitor_dest != dest && my_offset > monitor_dest)
3780             dest = monitor_dest; 
3781
3782         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3783             gint his_edge_start, his_edge_end, his_offset;
3784             ObClient *cur = it->data;
3785
3786             WANT_EDGE(cur, c)
3787
3788             his_edge_start = cur->frame->area.x;
3789             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3790             his_offset = cur->frame->area.y + 
3791                          (hang ? 0 : cur->frame->area.height);
3792
3793             if(his_offset + 1 > my_offset)
3794                 continue;
3795
3796             if(his_offset < dest)
3797                 continue;
3798
3799             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3800         }
3801         break;
3802     case OB_DIRECTION_SOUTH:
3803         my_edge_start = c->frame->area.x;
3804         my_edge_end = c->frame->area.x + c->frame->area.width;
3805         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3806
3807         /* default: bottom of screen */
3808         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3809         monitor_dest = monitor->y + monitor->height -
3810                        (hang ? c->frame->area.height : 0);
3811         /* if the monitor edge comes before the screen edge, */
3812         /* use that as the destination instead. (For xinerama) */
3813         if (monitor_dest != dest && my_offset < monitor_dest)
3814             dest = monitor_dest; 
3815
3816         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3817             gint his_edge_start, his_edge_end, his_offset;
3818             ObClient *cur = it->data;
3819
3820             WANT_EDGE(cur, c)
3821
3822             his_edge_start = cur->frame->area.x;
3823             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3824             his_offset = cur->frame->area.y +
3825                          (hang ? cur->frame->area.height : 0);
3826
3827
3828             if(his_offset - 1 < my_offset)
3829                 continue;
3830             
3831             if(his_offset > dest)
3832                 continue;
3833
3834             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3835         }
3836         break;
3837     case OB_DIRECTION_WEST:
3838         my_edge_start = c->frame->area.y;
3839         my_edge_end = c->frame->area.y + c->frame->area.height;
3840         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3841
3842         /* default: leftmost egde of screen */
3843         dest = a->x + (hang ? c->frame->area.width : 0);
3844         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3845         /* if the monitor edge comes before the screen edge, */
3846         /* use that as the destination instead. (For xinerama) */
3847         if (monitor_dest != dest && my_offset > monitor_dest)
3848             dest = monitor_dest;            
3849
3850         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3851             gint his_edge_start, his_edge_end, his_offset;
3852             ObClient *cur = it->data;
3853
3854             WANT_EDGE(cur, c)
3855
3856             his_edge_start = cur->frame->area.y;
3857             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3858             his_offset = cur->frame->area.x +
3859                          (hang ? 0 : cur->frame->area.width);
3860
3861             if(his_offset + 1 > my_offset)
3862                 continue;
3863
3864             if(his_offset < dest)
3865                 continue;
3866
3867             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3868         }
3869        break;
3870     case OB_DIRECTION_EAST:
3871         my_edge_start = c->frame->area.y;
3872         my_edge_end = c->frame->area.y + c->frame->area.height;
3873         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3874         
3875         /* default: rightmost edge of screen */
3876         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3877         monitor_dest = monitor->x + monitor->width -
3878                        (hang ? c->frame->area.width : 0);
3879         /* if the monitor edge comes before the screen edge, */
3880         /* use that as the destination instead. (For xinerama) */
3881         if (monitor_dest != dest && my_offset < monitor_dest)
3882             dest = monitor_dest;            
3883
3884         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3885             gint his_edge_start, his_edge_end, his_offset;
3886             ObClient *cur = it->data;
3887
3888             WANT_EDGE(cur, c)
3889
3890             his_edge_start = cur->frame->area.y;
3891             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3892             his_offset = cur->frame->area.x +
3893                          (hang ? cur->frame->area.width : 0);
3894
3895             if(his_offset - 1 < my_offset)
3896                 continue;
3897             
3898             if(his_offset > dest)
3899                 continue;
3900
3901             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3902         }
3903         break;
3904     case OB_DIRECTION_NORTHEAST:
3905     case OB_DIRECTION_SOUTHEAST:
3906     case OB_DIRECTION_NORTHWEST:
3907     case OB_DIRECTION_SOUTHWEST:
3908         /* not implemented */
3909     default:
3910         g_assert_not_reached();
3911         dest = 0; /* suppress warning */
3912     }
3913     return dest;
3914 }
3915
3916 ObClient* client_under_pointer()
3917 {
3918     gint x, y;
3919     GList *it;
3920     ObClient *ret = NULL;
3921
3922     if (screen_pointer_pos(&x, &y)) {
3923         for (it = stacking_list; it; it = g_list_next(it)) {
3924             if (WINDOW_IS_CLIENT(it->data)) {
3925                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3926                 if (c->frame->visible &&
3927                     /* ignore all animating windows */
3928                     !frame_iconify_animating(c->frame) &&
3929                     RECT_CONTAINS(c->frame->area, x, y))
3930                 {
3931                     ret = c;
3932                     break;
3933                 }
3934             }
3935         }
3936     }
3937     return ret;
3938 }
3939
3940 gboolean client_has_group_siblings(ObClient *self)
3941 {
3942     return self->group && self->group->members->next;
3943 }