]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
when a window is fully maxed, make clicking on the titlebar past the edge buttons...
[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 | OB_FRAME_DECOR_GRIPS);
1668
1669     /* finally, the user can have requested no decorations, which overrides
1670        everything (but doesnt give it a border if it doesnt have one) */
1671     if (self->undecorated) {
1672         if (config_theme_keepborder)
1673             self->decorations &= OB_FRAME_DECOR_BORDER;
1674         else
1675             self->decorations = 0;
1676     }
1677
1678     /* if we don't have a titlebar, then we cannot shade! */
1679     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1680         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1681
1682     /* now we need to check against rules for the client's current state */
1683     if (self->fullscreen) {
1684         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1685                             OB_CLIENT_FUNC_FULLSCREEN |
1686                             OB_CLIENT_FUNC_ICONIFY);
1687         self->decorations = 0;
1688     }
1689
1690     client_change_allowed_actions(self);
1691
1692     if (self->frame) {
1693         /* adjust the client's decorations, etc. */
1694         client_reconfigure(self);
1695     }
1696 }
1697
1698 static void client_change_allowed_actions(ObClient *self)
1699 {
1700     gulong actions[9];
1701     gint num = 0;
1702
1703     /* desktop windows are kept on all desktops */
1704     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1705         actions[num++] = prop_atoms.net_wm_action_change_desktop;
1706
1707     if (self->functions & OB_CLIENT_FUNC_SHADE)
1708         actions[num++] = prop_atoms.net_wm_action_shade;
1709     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1710         actions[num++] = prop_atoms.net_wm_action_close;
1711     if (self->functions & OB_CLIENT_FUNC_MOVE)
1712         actions[num++] = prop_atoms.net_wm_action_move;
1713     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1714         actions[num++] = prop_atoms.net_wm_action_minimize;
1715     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1716         actions[num++] = prop_atoms.net_wm_action_resize;
1717     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1718         actions[num++] = prop_atoms.net_wm_action_fullscreen;
1719     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1720         actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1721         actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1722     }
1723
1724     PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1725
1726     /* make sure the window isn't breaking any rules now */
1727
1728     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1729         if (self->frame) client_shade(self, FALSE);
1730         else self->shaded = FALSE;
1731     }
1732     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1733         if (self->frame) client_iconify(self, FALSE, TRUE);
1734         else self->iconic = FALSE;
1735     }
1736     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1737         if (self->frame) client_fullscreen(self, FALSE);
1738         else self->fullscreen = FALSE;
1739     }
1740     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1741                                                          self->max_vert)) {
1742         if (self->frame) client_maximize(self, FALSE, 0);
1743         else self->max_vert = self->max_horz = FALSE;
1744     }
1745 }
1746
1747 void client_reconfigure(ObClient *self)
1748 {
1749     /* by making this pass FALSE for user, we avoid the emacs event storm where
1750        every configurenotify causes an update in its normal hints, i think this
1751        is generally what we want anyways... */
1752     client_configure(self, self->area.x, self->area.y,
1753                      self->area.width, self->area.height, FALSE, TRUE);
1754 }
1755
1756 void client_update_wmhints(ObClient *self)
1757 {
1758     XWMHints *hints;
1759
1760     /* assume a window takes input if it doesnt specify */
1761     self->can_focus = TRUE;
1762   
1763     if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1764         gboolean ur;
1765
1766         if (hints->flags & InputHint)
1767             self->can_focus = hints->input;
1768
1769         /* only do this when first managing the window *AND* when we aren't
1770            starting up! */
1771         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1772             if (hints->flags & StateHint)
1773                 self->iconic = hints->initial_state == IconicState;
1774
1775         ur = self->urgent;
1776         self->urgent = (hints->flags & XUrgencyHint);
1777         if (self->urgent && !ur)
1778             client_hilite(self, TRUE);
1779         else if (!self->urgent && ur && self->demands_attention)
1780             client_hilite(self, FALSE);
1781
1782         if (!(hints->flags & WindowGroupHint))
1783             hints->window_group = None;
1784
1785         /* did the group state change? */
1786         if (hints->window_group !=
1787             (self->group ? self->group->leader : None))
1788         {
1789             ObGroup *oldgroup = self->group;
1790
1791             /* remove from the old group if there was one */
1792             if (self->group != NULL) {
1793                 group_remove(self->group, self);
1794                 self->group = NULL;
1795             }
1796
1797             /* add ourself to the group if we have one */
1798             if (hints->window_group != None) {
1799                 self->group = group_add(hints->window_group, self);
1800             }
1801
1802             /* Put ourselves into the new group's transient tree, and remove
1803                ourselves from the old group's */
1804             client_update_transient_tree(self, oldgroup, self->group,
1805                                          self->transient_for,
1806                                          self->transient_for);
1807
1808             /* Lastly, being in a group, or not, can change if the window is
1809                transient for anything.
1810
1811                The logic for this is:
1812                self->transient = TRUE always if the window wants to be
1813                transient for something, even if transient_for was NULL because
1814                it wasn't in a group before.
1815
1816                If transient_for was NULL and oldgroup was NULL we can assume
1817                that when we add the new group, it will become transient for
1818                something.
1819
1820                If transient_for was OB_TRAN_GROUP, then it must have already
1821                had a group. If it is getting a new group, the above call to
1822                client_update_transient_tree has already taken care of
1823                everything ! If it is losing all group status then it will
1824                no longer be transient for anything and that needs to be
1825                updated.
1826             */
1827             if (self->transient &&
1828                 ((self->transient_for == NULL && oldgroup == NULL) ||
1829                  (self->transient_for == OB_TRAN_GROUP && !self->group)))
1830                 client_update_transient_for(self);
1831         }
1832
1833         /* the WM_HINTS can contain an icon */
1834         client_update_icons(self);
1835
1836         XFree(hints);
1837     }
1838 }
1839
1840 void client_update_title(ObClient *self)
1841 {
1842     gchar *data = NULL;
1843     gchar *visible = NULL;
1844
1845     g_free(self->title);
1846      
1847     /* try netwm */
1848     if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1849         /* try old x stuff */
1850         if (!(PROP_GETS(self->window, wm_name, locale, &data)
1851               || PROP_GETS(self->window, wm_name, utf8, &data))) {
1852             if (self->transient) {
1853                 /*
1854                   GNOME alert windows are not given titles:
1855                   http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1856                 */
1857                 data = g_strdup("");
1858             } else
1859                 data = g_strdup("Unnamed Window");
1860         }
1861     }
1862
1863     if (self->client_machine) {
1864         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1865         g_free(data);
1866     } else
1867         visible = data;
1868
1869     PROP_SETS(self->window, net_wm_visible_name, visible);
1870     self->title = visible;
1871
1872     if (self->frame)
1873         frame_adjust_title(self->frame);
1874
1875     /* update the icon title */
1876     data = NULL;
1877     g_free(self->icon_title);
1878
1879     /* try netwm */
1880     if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1881         /* try old x stuff */
1882         if (!(PROP_GETS(self->window, wm_icon_name, locale, &data) ||
1883               PROP_GETS(self->window, wm_icon_name, utf8, &data)))
1884             data = g_strdup(self->title);
1885
1886     PROP_SETS(self->window, net_wm_visible_icon_name, data);
1887     self->icon_title = data;
1888 }
1889
1890 void client_update_strut(ObClient *self)
1891 {
1892     guint num;
1893     guint32 *data;
1894     gboolean got = FALSE;
1895     StrutPartial strut;
1896
1897     if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1898                     &data, &num)) {
1899         if (num == 12) {
1900             got = TRUE;
1901             STRUT_PARTIAL_SET(strut,
1902                               data[0], data[2], data[1], data[3],
1903                               data[4], data[5], data[8], data[9],
1904                               data[6], data[7], data[10], data[11]);
1905         }
1906         g_free(data);
1907     }
1908
1909     if (!got &&
1910         PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1911         if (num == 4) {
1912             const Rect *a;
1913
1914             got = TRUE;
1915
1916             /* use the screen's width/height */
1917             a = screen_physical_area();
1918
1919             STRUT_PARTIAL_SET(strut,
1920                               data[0], data[2], data[1], data[3],
1921                               a->y, a->y + a->height - 1,
1922                               a->x, a->x + a->width - 1,
1923                               a->y, a->y + a->height - 1,
1924                               a->x, a->x + a->width - 1);
1925         }
1926         g_free(data);
1927     }
1928
1929     if (!got)
1930         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1931                           0, 0, 0, 0, 0, 0, 0, 0);
1932
1933     if (!STRUT_EQUAL(strut, self->strut)) {
1934         self->strut = strut;
1935
1936         /* updating here is pointless while we're being mapped cuz we're not in
1937            the client list yet */
1938         if (self->frame)
1939             screen_update_areas();
1940     }
1941 }
1942
1943 void client_update_icons(ObClient *self)
1944 {
1945     guint num;
1946     guint32 *data;
1947     guint w, h, i, j;
1948
1949     for (i = 0; i < self->nicons; ++i)
1950         g_free(self->icons[i].data);
1951     if (self->nicons > 0)
1952         g_free(self->icons);
1953     self->nicons = 0;
1954
1955     if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1956         /* figure out how many valid icons are in here */
1957         i = 0;
1958         while (num - i > 2) {
1959             w = data[i++];
1960             h = data[i++];
1961             i += w * h;
1962             if (i > num || w*h == 0) break;
1963             ++self->nicons;
1964         }
1965
1966         self->icons = g_new(ObClientIcon, self->nicons);
1967     
1968         /* store the icons */
1969         i = 0;
1970         for (j = 0; j < self->nicons; ++j) {
1971             guint x, y, t;
1972
1973             w = self->icons[j].width = data[i++];
1974             h = self->icons[j].height = data[i++];
1975
1976             if (w*h == 0) continue;
1977
1978             self->icons[j].data = g_new(RrPixel32, w * h);
1979             for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1980                 if (x >= w) {
1981                     x = 0;
1982                     ++y;
1983                 }
1984                 self->icons[j].data[t] =
1985                     (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1986                     (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1987                     (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1988                     (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1989             }
1990             g_assert(i <= num);
1991         }
1992
1993         g_free(data);
1994     } else {
1995         XWMHints *hints;
1996
1997         if ((hints = XGetWMHints(ob_display, self->window))) {
1998             if (hints->flags & IconPixmapHint) {
1999                 self->nicons++;
2000                 self->icons = g_new(ObClientIcon, self->nicons);
2001                 xerror_set_ignore(TRUE);
2002                 if (!RrPixmapToRGBA(ob_rr_inst,
2003                                     hints->icon_pixmap,
2004                                     (hints->flags & IconMaskHint ?
2005                                      hints->icon_mask : None),
2006                                     &self->icons[self->nicons-1].width,
2007                                     &self->icons[self->nicons-1].height,
2008                                     &self->icons[self->nicons-1].data)){
2009                     g_free(&self->icons[self->nicons-1]);
2010                     self->nicons--;
2011                 }
2012                 xerror_set_ignore(FALSE);
2013             }
2014             XFree(hints);
2015         }
2016     }
2017
2018     /* set the default icon onto the window
2019        in theory, this could be a race, but if a window doesn't set an icon
2020        or removes it entirely, it's not very likely it is going to set one
2021        right away afterwards */
2022     if (self->nicons == 0) {
2023         RrPixel32 *icon = ob_rr_theme->def_win_icon;
2024         gulong *data;
2025
2026         data = g_new(gulong, 48*48+2);
2027         data[0] = data[1] =  48;
2028         for (i = 0; i < 48*48; ++i)
2029             data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2030                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2031                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2032                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2033         PROP_SETA32(self->window, net_wm_icon, cardinal, data, 48*48+2);
2034         g_free(data);
2035     } else if (self->frame)
2036         /* don't draw the icon empty if we're just setting one now anyways,
2037            we'll get the property change any second */
2038         frame_adjust_icon(self->frame);
2039 }
2040
2041 void client_update_user_time(ObClient *self)
2042 {
2043     guint32 time;
2044     gboolean got = FALSE;
2045
2046     if (self->user_time_window)
2047         got = PROP_GET32(self->user_time_window,
2048                          net_wm_user_time, cardinal, &time);
2049     if (!got)
2050         got = PROP_GET32(self->window, net_wm_user_time, cardinal, &time);
2051
2052     if (got) {
2053         /* we set this every time, not just when it grows, because in practice
2054            sometimes time goes backwards! (ntpdate.. yay....) so.. if it goes
2055            backward we don't want all windows to stop focusing. we'll just
2056            assume noone is setting times older than the last one, cuz that
2057            would be pretty stupid anyways
2058         */
2059         self->user_time = time;
2060
2061         /*ob_debug("window %s user time %u\n", self->title, time);*/
2062     }
2063 }
2064
2065 void client_update_user_time_window(ObClient *self)
2066 {
2067     guint32 w;
2068
2069     if (!PROP_GET32(self->window, net_wm_user_time_window, window, &w))
2070         w = None;
2071
2072     if (w != self->user_time_window) {
2073         /* remove the old window */
2074         propwin_remove(self->user_time_window, OB_PROPWIN_USER_TIME, self);
2075         self->user_time_window = None;
2076
2077         if (self->group && self->group->leader == w) {
2078             ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2079                           "_NET_WM_USER_TYPE_WINDOW to its group leader\n");
2080             /* do it anyways..? */
2081         }
2082         else if (w == self->window) {
2083             ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2084                           "_NET_WM_USER_TIME_WINDOW to itself\n");
2085             w = None; /* don't do it */
2086         }
2087
2088         /* add the new window */
2089         propwin_add(w, OB_PROPWIN_USER_TIME, self);
2090         self->user_time_window = w;
2091
2092         /* and update from it */
2093         client_update_user_time(self);
2094     }
2095 }
2096
2097 void client_update_icon_geometry(ObClient *self)
2098 {
2099     guint num;
2100     guint32 *data;
2101
2102     RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2103
2104     if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num)
2105         && num == 4)
2106     {
2107         /* don't let them set it with an area < 0 */
2108         RECT_SET(self->icon_geometry, data[0], data[1],
2109                  MAX(data[2],0), MAX(data[3],0));
2110     }
2111 }
2112
2113 static void client_get_session_ids(ObClient *self)
2114 {
2115     guint32 leader;
2116     gboolean got;
2117     gchar *s;
2118     gchar **ss;
2119
2120     if (!PROP_GET32(self->window, wm_client_leader, window, &leader))
2121         leader = None;
2122
2123     /* get the SM_CLIENT_ID */
2124     got = FALSE;
2125     if (leader)
2126         got = PROP_GETS(leader, sm_client_id, locale, &self->sm_client_id);
2127     if (!got)
2128         PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id);
2129
2130     /* get the WM_CLASS (name and class). make them "" if they are not
2131        provided */
2132     got = FALSE;
2133     if (leader)
2134         got = PROP_GETSS(leader, wm_class, locale, &ss);
2135     if (!got)
2136         got = PROP_GETSS(self->window, wm_class, locale, &ss);
2137
2138     if (got) {
2139         if (ss[0]) {
2140             self->name = g_strdup(ss[0]);
2141             if (ss[1])
2142                 self->class = g_strdup(ss[1]);
2143         }
2144         g_strfreev(ss);
2145     }
2146
2147     if (self->name == NULL) self->name = g_strdup("");
2148     if (self->class == NULL) self->class = g_strdup("");
2149
2150     /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2151     got = FALSE;
2152     if (leader)
2153         got = PROP_GETS(leader, wm_window_role, locale, &s);
2154     if (!got)
2155         got = PROP_GETS(self->window, wm_window_role, locale, &s);
2156
2157     if (got)
2158         self->role = s;
2159     else
2160         self->role = g_strdup("");
2161
2162     /* get the WM_COMMAND */
2163     got = FALSE;
2164
2165     if (leader)
2166         got = PROP_GETSS(leader, wm_command, locale, &ss);
2167     if (!got)
2168         got = PROP_GETSS(self->window, wm_command, locale, &ss);
2169
2170     if (got) {
2171         /* merge/mash them all together */
2172         gchar *merge = NULL;
2173         gint i;
2174
2175         for (i = 0; ss[i]; ++i) {
2176             gchar *tmp = merge;
2177             if (merge)
2178                 merge = g_strconcat(merge, ss[i], NULL);
2179             else
2180                 merge = g_strconcat(ss[i], NULL);
2181             g_free(tmp);
2182         }
2183         g_strfreev(ss);
2184
2185         self->wm_command = merge;
2186     }
2187
2188     /* get the WM_CLIENT_MACHINE */
2189     got = FALSE;
2190     if (leader)
2191         got = PROP_GETS(leader, wm_client_machine, locale, &s);
2192     if (!got)
2193         got = PROP_GETS(self->window, wm_client_machine, locale, &s);
2194
2195     if (got) {
2196         gchar localhost[128];
2197
2198         gethostname(localhost, 127);
2199         localhost[127] = '\0';
2200         if (strcmp(localhost, s) != 0)
2201             self->client_machine = s;
2202         else
2203             g_free(s);
2204     }
2205 }
2206
2207 static void client_change_wm_state(ObClient *self)
2208 {
2209     gulong state[2];
2210     glong old;
2211
2212     old = self->wmstate;
2213
2214     if (self->shaded || self->iconic ||
2215         (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2216     {
2217         self->wmstate = IconicState;
2218     } else
2219         self->wmstate = NormalState;
2220
2221     if (old != self->wmstate) {
2222         PROP_MSG(self->window, kde_wm_change_state,
2223                  self->wmstate, 1, 0, 0);
2224
2225         state[0] = self->wmstate;
2226         state[1] = None;
2227         PROP_SETA32(self->window, wm_state, wm_state, state, 2);
2228     }
2229 }
2230
2231 static void client_change_state(ObClient *self)
2232 {
2233     gulong netstate[11];
2234     guint num;
2235
2236     num = 0;
2237     if (self->modal)
2238         netstate[num++] = prop_atoms.net_wm_state_modal;
2239     if (self->shaded)
2240         netstate[num++] = prop_atoms.net_wm_state_shaded;
2241     if (self->iconic)
2242         netstate[num++] = prop_atoms.net_wm_state_hidden;
2243     if (self->skip_taskbar)
2244         netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
2245     if (self->skip_pager)
2246         netstate[num++] = prop_atoms.net_wm_state_skip_pager;
2247     if (self->fullscreen)
2248         netstate[num++] = prop_atoms.net_wm_state_fullscreen;
2249     if (self->max_vert)
2250         netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
2251     if (self->max_horz)
2252         netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
2253     if (self->above)
2254         netstate[num++] = prop_atoms.net_wm_state_above;
2255     if (self->below)
2256         netstate[num++] = prop_atoms.net_wm_state_below;
2257     if (self->demands_attention)
2258         netstate[num++] = prop_atoms.net_wm_state_demands_attention;
2259     if (self->undecorated)
2260         netstate[num++] = prop_atoms.openbox_wm_state_undecorated;
2261     PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
2262
2263     if (self->frame)
2264         frame_adjust_state(self->frame);
2265 }
2266
2267 ObClient *client_search_focus_tree(ObClient *self)
2268 {
2269     GSList *it;
2270     ObClient *ret;
2271
2272     for (it = self->transients; it; it = g_slist_next(it)) {
2273         if (client_focused(it->data)) return it->data;
2274         if ((ret = client_search_focus_tree(it->data))) return ret;
2275     }
2276     return NULL;
2277 }
2278
2279 ObClient *client_search_focus_tree_full(ObClient *self)
2280 {
2281     if (self->transient_for) {
2282         if (self->transient_for != OB_TRAN_GROUP) {
2283             return client_search_focus_tree_full(self->transient_for);
2284         } else {
2285             GSList *it;
2286             gboolean recursed = FALSE;
2287         
2288             for (it = self->group->members; it; it = g_slist_next(it))
2289                 if (!((ObClient*)it->data)->transient_for) {
2290                     ObClient *c;
2291                     if ((c = client_search_focus_tree_full(it->data)))
2292                         return c;
2293                     recursed = TRUE;
2294                 }
2295             if (recursed)
2296                 return NULL;
2297         }
2298     }
2299
2300     /* this function checks the whole tree, the client_search_focus_tree~
2301        does not, so we need to check this window */
2302     if (client_focused(self))
2303         return self;
2304     return client_search_focus_tree(self);
2305 }
2306
2307 static ObStackingLayer calc_layer(ObClient *self)
2308 {
2309     ObStackingLayer l;
2310
2311     if (self->type == OB_CLIENT_TYPE_DESKTOP)
2312         l = OB_STACKING_LAYER_DESKTOP;
2313     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2314         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2315         else l = OB_STACKING_LAYER_ABOVE;
2316     }
2317     else if ((self->fullscreen ||
2318               /* no decorations and fills the monitor = oldskool fullscreen */
2319               (self->frame != NULL &&
2320                (self->frame->size.right == 0 && self->frame->size.left == 0 &&
2321                 self->frame->size.bottom == 0 && self->frame->size.top == 0 &&
2322                 RECT_EQUAL(self->area,
2323                            *screen_physical_area_monitor
2324                            (client_monitor(self)))))) &&
2325              (client_focused(self) || client_search_focus_tree(self)))
2326         l = OB_STACKING_LAYER_FULLSCREEN;
2327     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2328     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2329     else l = OB_STACKING_LAYER_NORMAL;
2330
2331     return l;
2332 }
2333
2334 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2335                                         ObStackingLayer min, gboolean raised)
2336 {
2337     ObStackingLayer old, own;
2338     GSList *it;
2339
2340     old = self->layer;
2341     own = calc_layer(self);
2342     self->layer = MAX(own, min);
2343
2344     for (it = self->transients; it; it = g_slist_next(it))
2345         client_calc_layer_recursive(it->data, orig,
2346                                     self->layer,
2347                                     raised ? raised : self->layer != old);
2348
2349     if (!raised && self->layer != old)
2350         if (orig->frame) { /* only restack if the original window is managed */
2351             stacking_remove(CLIENT_AS_WINDOW(self));
2352             stacking_add(CLIENT_AS_WINDOW(self));
2353         }
2354 }
2355
2356 void client_calc_layer(ObClient *self)
2357 {
2358     ObClient *orig;
2359     GSList *it;
2360
2361     orig = self;
2362
2363     /* transients take on the layer of their parents */
2364     it = client_search_all_top_parents(self);
2365
2366     for (; it; it = g_slist_next(it))
2367         client_calc_layer_recursive(it->data, orig, 0, FALSE);
2368 }
2369
2370 gboolean client_should_show(ObClient *self)
2371 {
2372     if (self->iconic)
2373         return FALSE;
2374     if (client_normal(self) && screen_showing_desktop)
2375         return FALSE;
2376     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2377         return TRUE;
2378     
2379     return FALSE;
2380 }
2381
2382 void client_show(ObClient *self)
2383 {
2384
2385     if (client_should_show(self)) {
2386         frame_show(self->frame);
2387     }
2388
2389     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2390        needs to be in IconicState. This includes when it is on another
2391        desktop!
2392     */
2393     client_change_wm_state(self);
2394 }
2395
2396 void client_hide(ObClient *self)
2397 {
2398     if (!client_should_show(self)) {
2399         frame_hide(self->frame);
2400     }
2401
2402     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2403        needs to be in IconicState. This includes when it is on another
2404        desktop!
2405     */
2406     client_change_wm_state(self);
2407 }
2408
2409 void client_showhide(ObClient *self)
2410 {
2411
2412     if (client_should_show(self)) {
2413         frame_show(self->frame);
2414     }
2415     else {
2416         frame_hide(self->frame);
2417     }
2418
2419     /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2420        needs to be in IconicState. This includes when it is on another
2421        desktop!
2422     */
2423     client_change_wm_state(self);
2424 }
2425
2426 gboolean client_normal(ObClient *self) {
2427     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2428               self->type == OB_CLIENT_TYPE_DOCK ||
2429               self->type == OB_CLIENT_TYPE_SPLASH);
2430 }
2431
2432 gboolean client_helper(ObClient *self)
2433 {
2434     return (self->type == OB_CLIENT_TYPE_UTILITY ||
2435             self->type == OB_CLIENT_TYPE_MENU ||
2436             self->type == OB_CLIENT_TYPE_TOOLBAR);
2437 }
2438
2439 gboolean client_mouse_focusable(ObClient *self)
2440 {
2441     return !(self->type == OB_CLIENT_TYPE_MENU ||
2442              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2443              self->type == OB_CLIENT_TYPE_SPLASH ||
2444              self->type == OB_CLIENT_TYPE_DOCK);
2445 }
2446
2447 gboolean client_enter_focusable(ObClient *self)
2448 {
2449     /* you can focus desktops but it shouldn't on enter */
2450     return (client_mouse_focusable(self) &&
2451             self->type != OB_CLIENT_TYPE_DESKTOP);
2452 }
2453
2454
2455 static void client_apply_startup_state(ObClient *self, gint x, gint y)
2456 {
2457     gboolean pos = FALSE; /* has the window's position been configured? */
2458     gint ox, oy;
2459
2460     /* save the position, and set self->area for these to use */
2461     ox = self->area.x;
2462     oy = self->area.y;
2463     self->area.x = x;
2464     self->area.y = y;
2465
2466     /* set the desktop hint, to make sure that it always exists */
2467     PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
2468
2469     /* these are in a carefully crafted order.. */
2470
2471     if (self->iconic) {
2472         self->iconic = FALSE;
2473         client_iconify(self, TRUE, FALSE);
2474     }
2475     if (self->fullscreen) {
2476         self->fullscreen = FALSE;
2477         client_fullscreen(self, TRUE);
2478         pos = TRUE;
2479     }
2480     if (self->undecorated) {
2481         self->undecorated = FALSE;
2482         client_set_undecorated(self, TRUE);
2483     }
2484     if (self->shaded) {
2485         self->shaded = FALSE;
2486         client_shade(self, TRUE);
2487     }
2488     if (self->demands_attention) {
2489         self->demands_attention = FALSE;
2490         client_hilite(self, TRUE);
2491     }
2492   
2493     if (self->max_vert && self->max_horz) {
2494         self->max_vert = self->max_horz = FALSE;
2495         client_maximize(self, TRUE, 0);
2496         pos = TRUE;
2497     } else if (self->max_vert) {
2498         self->max_vert = FALSE;
2499         client_maximize(self, TRUE, 2);
2500         pos = TRUE;
2501     } else if (self->max_horz) {
2502         self->max_horz = FALSE;
2503         client_maximize(self, TRUE, 1);
2504         pos = TRUE;
2505     }
2506
2507     /* if the client didn't get positioned yet, then do so now.
2508        call client_move even if the window is not being moved anywhere, because
2509        when we reparent it and decorate it, it is getting moved and we need to
2510        be telling it so with a ConfigureNotify event.
2511     */
2512     if (!pos) {
2513         /* use the saved position */
2514         self->area.x = ox;
2515         self->area.y = oy;
2516         client_move(self, x, y);
2517     }
2518
2519     /* nothing to do for the other states:
2520        skip_taskbar
2521        skip_pager
2522        modal
2523        above
2524        below
2525     */
2526 }
2527
2528 void client_convert_gravity(ObClient *self, gint gravity, gint *x, gint *y,
2529                             gint w, gint h)
2530 {
2531     gint oldg = self->gravity;
2532
2533     /* get the frame's position from the requested stuff */
2534     self->gravity = gravity;
2535     frame_client_gravity(self->frame, x, y, w, h);
2536     self->gravity = oldg;
2537
2538     /* get the client's position in its true gravity from that */
2539     frame_frame_gravity(self->frame, x, y, w, h);
2540 }
2541
2542 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2543                           gint *logicalw, gint *logicalh,
2544                           gboolean user)
2545 {
2546     Rect desired_area = {*x, *y, *w, *h};
2547
2548     /* make the frame recalculate its dimentions n shit without changing
2549        anything visible for real, this way the constraints below can work with
2550        the updated frame dimensions. */
2551     frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2552
2553     /* work within the prefered sizes given by the window */
2554     if (!(*w == self->area.width && *h == self->area.height)) {
2555         gint basew, baseh, minw, minh;
2556
2557         /* base size is substituted with min size if not specified */
2558         if (self->base_size.width || self->base_size.height) {
2559             basew = self->base_size.width;
2560             baseh = self->base_size.height;
2561         } else {
2562             basew = self->min_size.width;
2563             baseh = self->min_size.height;
2564         }
2565         /* min size is substituted with base size if not specified */
2566         if (self->min_size.width || self->min_size.height) {
2567             minw = self->min_size.width;
2568             minh = self->min_size.height;
2569         } else {
2570             minw = self->base_size.width;
2571             minh = self->base_size.height;
2572         }
2573
2574         /* if this is a user-requested resize, then check against min/max
2575            sizes */
2576
2577         /* smaller than min size or bigger than max size? */
2578         if (*w > self->max_size.width) *w = self->max_size.width;
2579         if (*w < minw) *w = minw;
2580         if (*h > self->max_size.height) *h = self->max_size.height;
2581         if (*h < minh) *h = minh;
2582
2583         *w -= basew;
2584         *h -= baseh;
2585
2586         /* keep to the increments */
2587         *w /= self->size_inc.width;
2588         *h /= self->size_inc.height;
2589
2590         /* you cannot resize to nothing */
2591         if (basew + *w < 1) *w = 1 - basew;
2592         if (baseh + *h < 1) *h = 1 - baseh;
2593   
2594         /* save the logical size */
2595         *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2596         *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2597
2598         *w *= self->size_inc.width;
2599         *h *= self->size_inc.height;
2600
2601         *w += basew;
2602         *h += baseh;
2603
2604         /* adjust the height to match the width for the aspect ratios.
2605            for this, min size is not substituted for base size ever. */
2606         *w -= self->base_size.width;
2607         *h -= self->base_size.height;
2608
2609         if (!self->fullscreen) {
2610             if (self->min_ratio)
2611                 if (*h * self->min_ratio > *w) {
2612                     *h = (gint)(*w / self->min_ratio);
2613
2614                     /* you cannot resize to nothing */
2615                     if (*h < 1) {
2616                         *h = 1;
2617                         *w = (gint)(*h * self->min_ratio);
2618                     }
2619                 }
2620             if (self->max_ratio)
2621                 if (*h * self->max_ratio < *w) {
2622                     *h = (gint)(*w / self->max_ratio);
2623
2624                     /* you cannot resize to nothing */
2625                     if (*h < 1) {
2626                         *h = 1;
2627                         *w = (gint)(*h * self->min_ratio);
2628                     }
2629                 }
2630         }
2631
2632         *w += self->base_size.width;
2633         *h += self->base_size.height;
2634     }
2635
2636     /* gets the frame's position */
2637     frame_client_gravity(self->frame, x, y, *w, *h);
2638
2639     /* these positions are frame positions, not client positions */
2640
2641     /* set the size and position if fullscreen */
2642     if (self->fullscreen) {
2643         Rect *a;
2644         guint i;
2645
2646         i = screen_find_monitor(&desired_area);
2647         a = screen_physical_area_monitor(i);
2648
2649         *x = a->x;
2650         *y = a->y;
2651         *w = a->width;
2652         *h = a->height;
2653
2654         user = FALSE; /* ignore if the client can't be moved/resized when it
2655                          is entering fullscreen */
2656     } else if (self->max_horz || self->max_vert) {
2657         Rect *a;
2658         guint i;
2659
2660         i = screen_find_monitor(&desired_area);
2661         a = screen_area_monitor(self->desktop, i);
2662
2663         /* set the size and position if maximized */
2664         if (self->max_horz) {
2665             *x = a->x;
2666             *w = a->width - self->frame->size.left - self->frame->size.right;
2667         }
2668         if (self->max_vert) {
2669             *y = a->y;
2670             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2671         }
2672
2673         /* maximizing is not allowed if the user can't move+resize the window
2674          */
2675     }
2676
2677     /* gets the client's position */
2678     frame_frame_gravity(self->frame, x, y, *w, *h);
2679
2680     /* these override the above states! if you cant move you can't move! */
2681     if (user) {
2682         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2683             *x = self->area.x;
2684             *y = self->area.y;
2685         }
2686         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2687             *w = self->area.width;
2688             *h = self->area.height;
2689         }
2690     }
2691
2692     g_assert(*w > 0);
2693     g_assert(*h > 0);
2694 }
2695
2696
2697 void client_configure_full(ObClient *self, gint x, gint y, gint w, gint h,
2698                            gboolean user, gboolean final,
2699                            gboolean force_reply)
2700 {
2701     gint oldw, oldh, oldrx, oldry;
2702     gboolean send_resize_client;
2703     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
2704     guint fdecor = self->frame->decorations;
2705     gboolean fhorz = self->frame->max_horz;
2706     gint logicalw, logicalh;
2707
2708     /* find the new x, y, width, and height (and logical size) */
2709     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2710
2711     /* set the logical size if things changed */
2712     if (!(w == self->area.width && h == self->area.height))
2713         SIZE_SET(self->logical_size, logicalw, logicalh);
2714
2715     /* figure out if we moved or resized or what */
2716     moved = x != self->area.x || y != self->area.y;
2717     resized = w != self->area.width || h != self->area.height;
2718
2719     oldw = self->area.width;
2720     oldh = self->area.height;
2721     RECT_SET(self->area, x, y, w, h);
2722
2723     /* for app-requested resizes, always resize if 'resized' is true.
2724        for user-requested ones, only resize if final is true, or when
2725        resizing in redraw mode */
2726     send_resize_client = ((!user && resized) ||
2727                           (user && (final ||
2728                                     (resized && config_resize_redraw))));
2729
2730     /* if the client is enlarging, then resize the client before the frame */
2731     if (send_resize_client && user && (w > oldw || h > oldh)) {
2732         XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2733         /* resize the plate to show the client padding color underneath */
2734         frame_adjust_client_area(self->frame);
2735     }
2736
2737     /* find the frame's dimensions and move/resize it */
2738     if (self->decorations != fdecor || self->max_horz != fhorz)
2739         moved = resized = TRUE;
2740     if (moved || resized)
2741         frame_adjust_area(self->frame, moved, resized, FALSE);
2742
2743     /* find the client's position relative to the root window */
2744     oldrx = self->root_pos.x;
2745     oldry = self->root_pos.y;
2746     rootmoved = (oldrx != (signed)(self->frame->area.x +
2747                                    self->frame->size.left -
2748                                    self->border_width) ||
2749                  oldry != (signed)(self->frame->area.y +
2750                                    self->frame->size.top -
2751                                    self->border_width));
2752
2753     if (force_reply || ((!user || (user && final)) && rootmoved))
2754     {
2755         XEvent event;
2756
2757         POINT_SET(self->root_pos,
2758                   self->frame->area.x + self->frame->size.left -
2759                   self->border_width,
2760                   self->frame->area.y + self->frame->size.top -
2761                   self->border_width);
2762
2763         event.type = ConfigureNotify;
2764         event.xconfigure.display = ob_display;
2765         event.xconfigure.event = self->window;
2766         event.xconfigure.window = self->window;
2767
2768         /* root window real coords */
2769         event.xconfigure.x = self->root_pos.x;
2770         event.xconfigure.y = self->root_pos.y;
2771         event.xconfigure.width = w;
2772         event.xconfigure.height = h;
2773         event.xconfigure.border_width = 0;
2774         event.xconfigure.above = self->frame->plate;
2775         event.xconfigure.override_redirect = FALSE;
2776         XSendEvent(event.xconfigure.display, event.xconfigure.window,
2777                    FALSE, StructureNotifyMask, &event);
2778     }
2779
2780     /* if the client is shrinking, then resize the frame before the client */
2781     if (send_resize_client && (!user || (w <= oldw || h <= oldh))) {
2782         /* resize the plate to show the client padding color underneath */
2783         frame_adjust_client_area(self->frame);
2784
2785         XResizeWindow(ob_display, self->window, w, h);
2786     }
2787
2788     XFlush(ob_display);
2789 }
2790
2791 void client_fullscreen(ObClient *self, gboolean fs)
2792 {
2793     gint x, y, w, h;
2794
2795     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2796         self->fullscreen == fs) return;                   /* already done */
2797
2798     self->fullscreen = fs;
2799     client_change_state(self); /* change the state hints on the client */
2800     client_calc_layer(self);   /* and adjust out layer/stacking */
2801
2802     if (fs) {
2803         self->pre_fullscreen_area = self->area;
2804         /* if the window is maximized, its area isn't all that meaningful.
2805            save it's premax area instead. */
2806         if (self->max_horz) {
2807             self->pre_fullscreen_area.x = self->pre_max_area.x;
2808             self->pre_fullscreen_area.width = self->pre_max_area.width;
2809         }
2810         if (self->max_vert) {
2811             self->pre_fullscreen_area.y = self->pre_max_area.y;
2812             self->pre_fullscreen_area.height = self->pre_max_area.height;
2813         }
2814
2815         /* these are not actually used cuz client_configure will set them
2816            as appropriate when the window is fullscreened */
2817         x = y = w = h = 0;
2818     } else {
2819         Rect *a;
2820
2821         if (self->pre_fullscreen_area.width > 0 &&
2822             self->pre_fullscreen_area.height > 0)
2823         {
2824             x = self->pre_fullscreen_area.x;
2825             y = self->pre_fullscreen_area.y;
2826             w = self->pre_fullscreen_area.width;
2827             h = self->pre_fullscreen_area.height;
2828             RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2829         } else {
2830             /* pick some fallbacks... */
2831             a = screen_area_monitor(self->desktop, 0);
2832             x = a->x + a->width / 4;
2833             y = a->y + a->height / 4;
2834             w = a->width / 2;
2835             h = a->height / 2;
2836         }
2837     }
2838
2839     client_setup_decor_and_functions(self);
2840
2841     client_move_resize(self, x, y, w, h);
2842
2843     /* try focus us when we go into fullscreen mode */
2844     client_focus(self);
2845 }
2846
2847 static void client_iconify_recursive(ObClient *self,
2848                                      gboolean iconic, gboolean curdesk)
2849 {
2850     GSList *it;
2851     gboolean changed = FALSE;
2852
2853
2854     if (self->iconic != iconic) {
2855         ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2856                  self->window);
2857
2858         if (iconic) {
2859             /* don't let non-normal windows iconify along with their parents
2860                or whatever */
2861             if (client_normal(self)) {
2862                 self->iconic = iconic;
2863
2864                 /* update the focus lists.. iconic windows go to the bottom of
2865                    the list, put the new iconic window at the 'top of the
2866                    bottom'. */
2867                 focus_order_to_top(self);
2868
2869                 changed = TRUE;
2870             }
2871         } else {
2872             self->iconic = iconic;
2873
2874             if (curdesk && self->desktop != screen_desktop &&
2875                 self->desktop != DESKTOP_ALL)
2876                 client_set_desktop(self, screen_desktop, FALSE);
2877
2878             /* this puts it after the current focused window */
2879             focus_order_remove(self);
2880             focus_order_add_new(self);
2881
2882             changed = TRUE;
2883         }
2884     }
2885
2886     if (changed) {
2887         client_change_state(self);
2888         if (ob_state() != OB_STATE_STARTING && config_animate_iconify)
2889             frame_begin_iconify_animation(self->frame, iconic);
2890         /* do this after starting the animation so it doesn't flash */
2891         client_showhide(self);
2892     }
2893
2894     /* iconify all direct transients, and deiconify all transients
2895        (non-direct too) */
2896     for (it = self->transients; it; it = g_slist_next(it))
2897         if (it->data != self)
2898             if (client_is_direct_child(self, it->data) || !iconic)
2899                 client_iconify_recursive(it->data, iconic, curdesk);
2900 }
2901
2902 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2903 {
2904     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
2905         /* move up the transient chain as far as possible first */
2906         self = client_search_top_normal_parent(self);
2907         client_iconify_recursive(self, iconic, curdesk);
2908     }
2909 }
2910
2911 void client_maximize(ObClient *self, gboolean max, gint dir)
2912 {
2913     gint x, y, w, h;
2914      
2915     g_assert(dir == 0 || dir == 1 || dir == 2);
2916     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2917
2918     /* check if already done */
2919     if (max) {
2920         if (dir == 0 && self->max_horz && self->max_vert) return;
2921         if (dir == 1 && self->max_horz) return;
2922         if (dir == 2 && self->max_vert) return;
2923     } else {
2924         if (dir == 0 && !self->max_horz && !self->max_vert) return;
2925         if (dir == 1 && !self->max_horz) return;
2926         if (dir == 2 && !self->max_vert) return;
2927     }
2928
2929     /* we just tell it to configure in the same place and client_configure
2930        worries about filling the screen with the window */
2931     x = self->area.x;
2932     y = self->area.y;
2933     w = self->area.width;
2934     h = self->area.height;
2935
2936     if (max) {
2937         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2938             RECT_SET(self->pre_max_area,
2939                      self->area.x, self->pre_max_area.y,
2940                      self->area.width, self->pre_max_area.height);
2941         }
2942         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2943             RECT_SET(self->pre_max_area,
2944                      self->pre_max_area.x, self->area.y,
2945                      self->pre_max_area.width, self->area.height);
2946         }
2947     } else {
2948         Rect *a;
2949
2950         a = screen_area_monitor(self->desktop, 0);
2951         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2952             if (self->pre_max_area.width > 0) {
2953                 x = self->pre_max_area.x;
2954                 w = self->pre_max_area.width;
2955
2956                 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2957                          0, self->pre_max_area.height);
2958             } else {
2959                 /* pick some fallbacks... */
2960                 x = a->x + a->width / 4;
2961                 w = a->width / 2;
2962             }
2963         }
2964         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2965             if (self->pre_max_area.height > 0) {
2966                 y = self->pre_max_area.y;
2967                 h = self->pre_max_area.height;
2968
2969                 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2970                          self->pre_max_area.width, 0);
2971             } else {
2972                 /* pick some fallbacks... */
2973                 y = a->y + a->height / 4;
2974                 h = a->height / 2;
2975             }
2976         }
2977     }
2978
2979     if (dir == 0 || dir == 1) /* horz */
2980         self->max_horz = max;
2981     if (dir == 0 || dir == 2) /* vert */
2982         self->max_vert = max;
2983
2984     client_change_state(self); /* change the state hints on the client */
2985
2986     client_setup_decor_and_functions(self);
2987
2988     client_move_resize(self, x, y, w, h);
2989 }
2990
2991 void client_shade(ObClient *self, gboolean shade)
2992 {
2993     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2994          shade) ||                         /* can't shade */
2995         self->shaded == shade) return;     /* already done */
2996
2997     self->shaded = shade;
2998     client_change_state(self);
2999     client_change_wm_state(self); /* the window is being hidden/shown */
3000     /* resize the frame to just the titlebar */
3001     frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
3002 }
3003
3004 void client_close(ObClient *self)
3005 {
3006     XEvent ce;
3007
3008     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3009
3010     /* in the case that the client provides no means to requesting that it
3011        close, we just kill it */
3012     if (!self->delete_window)
3013         client_kill(self);
3014     
3015     /*
3016       XXX: itd be cool to do timeouts and shit here for killing the client's
3017       process off
3018       like... if the window is around after 5 seconds, then the close button
3019       turns a nice red, and if this function is called again, the client is
3020       explicitly killed.
3021     */
3022
3023     ce.xclient.type = ClientMessage;
3024     ce.xclient.message_type =  prop_atoms.wm_protocols;
3025     ce.xclient.display = ob_display;
3026     ce.xclient.window = self->window;
3027     ce.xclient.format = 32;
3028     ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
3029     ce.xclient.data.l[1] = event_curtime;
3030     ce.xclient.data.l[2] = 0l;
3031     ce.xclient.data.l[3] = 0l;
3032     ce.xclient.data.l[4] = 0l;
3033     XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3034 }
3035
3036 void client_kill(ObClient *self)
3037 {
3038     XKillClient(ob_display, self->window);
3039 }
3040
3041 void client_hilite(ObClient *self, gboolean hilite)
3042 {
3043     if (self->demands_attention == hilite)
3044         return; /* no change */
3045
3046     /* don't allow focused windows to hilite */
3047     self->demands_attention = hilite && !client_focused(self);
3048     if (self->frame != NULL) { /* if we're mapping, just set the state */
3049         if (self->demands_attention)
3050             frame_flash_start(self->frame);
3051         else
3052             frame_flash_stop(self->frame);
3053         client_change_state(self);
3054     }
3055 }
3056
3057 void client_set_desktop_recursive(ObClient *self,
3058                                   guint target,
3059                                   gboolean donthide)
3060 {
3061     guint old;
3062     GSList *it;
3063
3064     if (target != self->desktop) {
3065
3066         ob_debug("Setting desktop %u\n", target+1);
3067
3068         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3069
3070         old = self->desktop;
3071         self->desktop = target;
3072         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3073         /* the frame can display the current desktop state */
3074         frame_adjust_state(self->frame);
3075         /* 'move' the window to the new desktop */
3076         if (!donthide)
3077             client_showhide(self);
3078         /* raise if it was not already on the desktop */
3079         if (old != DESKTOP_ALL)
3080             client_raise(self);
3081         if (STRUT_EXISTS(self->strut))
3082             screen_update_areas();
3083     }
3084
3085     /* move all transients */
3086     for (it = self->transients; it; it = g_slist_next(it))
3087         if (it->data != self)
3088             if (client_is_direct_child(self, it->data))
3089                 client_set_desktop_recursive(it->data, target, donthide);
3090 }
3091
3092 void client_set_desktop(ObClient *self, guint target,
3093                         gboolean donthide)
3094 {
3095     self = client_search_top_normal_parent(self);
3096     client_set_desktop_recursive(self, target, donthide);
3097 }
3098
3099 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3100 {
3101     while (child != parent &&
3102            child->transient_for && child->transient_for != OB_TRAN_GROUP)
3103         child = child->transient_for;
3104     return child == parent;
3105 }
3106
3107 ObClient *client_search_modal_child(ObClient *self)
3108 {
3109     GSList *it;
3110     ObClient *ret;
3111   
3112     for (it = self->transients; it; it = g_slist_next(it)) {
3113         ObClient *c = it->data;
3114         if ((ret = client_search_modal_child(c))) return ret;
3115         if (c->modal) return c;
3116     }
3117     return NULL;
3118 }
3119
3120 gboolean client_validate(ObClient *self)
3121 {
3122     XEvent e; 
3123
3124     XSync(ob_display, FALSE); /* get all events on the server */
3125
3126     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3127         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3128         XPutBackEvent(ob_display, &e);
3129         return FALSE;
3130     }
3131
3132     return TRUE;
3133 }
3134
3135 void client_set_wm_state(ObClient *self, glong state)
3136 {
3137     if (state == self->wmstate) return; /* no change */
3138   
3139     switch (state) {
3140     case IconicState:
3141         client_iconify(self, TRUE, TRUE);
3142         break;
3143     case NormalState:
3144         client_iconify(self, FALSE, TRUE);
3145         break;
3146     }
3147 }
3148
3149 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3150 {
3151     gboolean shaded = self->shaded;
3152     gboolean fullscreen = self->fullscreen;
3153     gboolean undecorated = self->undecorated;
3154     gboolean max_horz = self->max_horz;
3155     gboolean max_vert = self->max_vert;
3156     gboolean modal = self->modal;
3157     gboolean iconic = self->iconic;
3158     gboolean demands_attention = self->demands_attention;
3159     gboolean above = self->above;
3160     gboolean below = self->below;
3161     gint i;
3162
3163     if (!(action == prop_atoms.net_wm_state_add ||
3164           action == prop_atoms.net_wm_state_remove ||
3165           action == prop_atoms.net_wm_state_toggle))
3166         /* an invalid action was passed to the client message, ignore it */
3167         return; 
3168
3169     for (i = 0; i < 2; ++i) {
3170         Atom state = i == 0 ? data1 : data2;
3171     
3172         if (!state) continue;
3173
3174         /* if toggling, then pick whether we're adding or removing */
3175         if (action == prop_atoms.net_wm_state_toggle) {
3176             if (state == prop_atoms.net_wm_state_modal)
3177                 action = modal ? prop_atoms.net_wm_state_remove :
3178                     prop_atoms.net_wm_state_add;
3179             else if (state == prop_atoms.net_wm_state_maximized_vert)
3180                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3181                     prop_atoms.net_wm_state_add;
3182             else if (state == prop_atoms.net_wm_state_maximized_horz)
3183                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3184                     prop_atoms.net_wm_state_add;
3185             else if (state == prop_atoms.net_wm_state_shaded)
3186                 action = shaded ? prop_atoms.net_wm_state_remove :
3187                     prop_atoms.net_wm_state_add;
3188             else if (state == prop_atoms.net_wm_state_skip_taskbar)
3189                 action = self->skip_taskbar ?
3190                     prop_atoms.net_wm_state_remove :
3191                     prop_atoms.net_wm_state_add;
3192             else if (state == prop_atoms.net_wm_state_skip_pager)
3193                 action = self->skip_pager ?
3194                     prop_atoms.net_wm_state_remove :
3195                     prop_atoms.net_wm_state_add;
3196             else if (state == prop_atoms.net_wm_state_hidden)
3197                 action = self->iconic ?
3198                     prop_atoms.net_wm_state_remove :
3199                     prop_atoms.net_wm_state_add;
3200             else if (state == prop_atoms.net_wm_state_fullscreen)
3201                 action = fullscreen ?
3202                     prop_atoms.net_wm_state_remove :
3203                     prop_atoms.net_wm_state_add;
3204             else if (state == prop_atoms.net_wm_state_above)
3205                 action = self->above ? prop_atoms.net_wm_state_remove :
3206                     prop_atoms.net_wm_state_add;
3207             else if (state == prop_atoms.net_wm_state_below)
3208                 action = self->below ? prop_atoms.net_wm_state_remove :
3209                     prop_atoms.net_wm_state_add;
3210             else if (state == prop_atoms.net_wm_state_demands_attention)
3211                 action = self->demands_attention ?
3212                     prop_atoms.net_wm_state_remove :
3213                     prop_atoms.net_wm_state_add;
3214             else if (state == prop_atoms.openbox_wm_state_undecorated)
3215                 action = undecorated ? prop_atoms.net_wm_state_remove :
3216                     prop_atoms.net_wm_state_add;
3217         }
3218     
3219         if (action == prop_atoms.net_wm_state_add) {
3220             if (state == prop_atoms.net_wm_state_modal) {
3221                 modal = TRUE;
3222             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3223                 max_vert = TRUE;
3224             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3225                 max_horz = TRUE;
3226             } else if (state == prop_atoms.net_wm_state_shaded) {
3227                 shaded = TRUE;
3228             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3229                 self->skip_taskbar = TRUE;
3230             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3231                 self->skip_pager = TRUE;
3232             } else if (state == prop_atoms.net_wm_state_hidden) {
3233                 iconic = TRUE;
3234             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3235                 fullscreen = TRUE;
3236             } else if (state == prop_atoms.net_wm_state_above) {
3237                 above = TRUE;
3238                 below = FALSE;
3239             } else if (state == prop_atoms.net_wm_state_below) {
3240                 above = FALSE;
3241                 below = TRUE;
3242             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3243                 demands_attention = TRUE;
3244             } else if (state == prop_atoms.openbox_wm_state_undecorated) {
3245                 undecorated = TRUE;
3246             }
3247
3248         } else { /* action == prop_atoms.net_wm_state_remove */
3249             if (state == prop_atoms.net_wm_state_modal) {
3250                 modal = FALSE;
3251             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3252                 max_vert = FALSE;
3253             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3254                 max_horz = FALSE;
3255             } else if (state == prop_atoms.net_wm_state_shaded) {
3256                 shaded = FALSE;
3257             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3258                 self->skip_taskbar = FALSE;
3259             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3260                 self->skip_pager = FALSE;
3261             } else if (state == prop_atoms.net_wm_state_hidden) {
3262                 iconic = FALSE;
3263             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3264                 fullscreen = FALSE;
3265             } else if (state == prop_atoms.net_wm_state_above) {
3266                 above = FALSE;
3267             } else if (state == prop_atoms.net_wm_state_below) {
3268                 below = FALSE;
3269             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3270                 demands_attention = FALSE;
3271             } else if (state == prop_atoms.openbox_wm_state_undecorated) {
3272                 undecorated = FALSE;
3273             }
3274         }
3275     }
3276     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3277         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3278             /* toggling both */
3279             if (max_horz == max_vert) { /* both going the same way */
3280                 client_maximize(self, max_horz, 0);
3281             } else {
3282                 client_maximize(self, max_horz, 1);
3283                 client_maximize(self, max_vert, 2);
3284             }
3285         } else {
3286             /* toggling one */
3287             if (max_horz != self->max_horz)
3288                 client_maximize(self, max_horz, 1);
3289             else
3290                 client_maximize(self, max_vert, 2);
3291         }
3292     }
3293     /* change fullscreen state before shading, as it will affect if the window
3294        can shade or not */
3295     if (fullscreen != self->fullscreen)
3296         client_fullscreen(self, fullscreen);
3297     if (shaded != self->shaded)
3298         client_shade(self, shaded);
3299     if (undecorated != self->undecorated)
3300         client_set_undecorated(self, undecorated);
3301     if (modal != self->modal) {
3302         self->modal = modal;
3303         /* when a window changes modality, then its stacking order with its
3304            transients needs to change */
3305         client_raise(self);
3306     }
3307     if (iconic != self->iconic)
3308         client_iconify(self, iconic, FALSE);
3309
3310     if (demands_attention != self->demands_attention)
3311         client_hilite(self, demands_attention);
3312
3313     if (above != self->above || below != self->below) {
3314         self->above = above;
3315         self->below = below;
3316         client_calc_layer(self);
3317     }
3318
3319     client_change_state(self); /* change the hint to reflect these changes */
3320 }
3321
3322 ObClient *client_focus_target(ObClient *self)
3323 {
3324     ObClient *child = NULL;
3325
3326     child = client_search_modal_child(self);
3327     if (child) return child;
3328     return self;
3329 }
3330
3331 gboolean client_can_focus(ObClient *self)
3332 {
3333     XEvent ev;
3334
3335     /* choose the correct target */
3336     self = client_focus_target(self);
3337
3338     if (!self->frame->visible)
3339         return FALSE;
3340
3341     if (!(self->can_focus || self->focus_notify))
3342         return FALSE;
3343
3344     /* do a check to see if the window has already been unmapped or destroyed
3345        do this intelligently while watching out for unmaps we've generated
3346        (ignore_unmaps > 0) */
3347     if (XCheckTypedWindowEvent(ob_display, self->window,
3348                                DestroyNotify, &ev)) {
3349         XPutBackEvent(ob_display, &ev);
3350         return FALSE;
3351     }
3352     while (XCheckTypedWindowEvent(ob_display, self->window,
3353                                   UnmapNotify, &ev)) {
3354         if (self->ignore_unmaps) {
3355             self->ignore_unmaps--;
3356         } else {
3357             XPutBackEvent(ob_display, &ev);
3358             return FALSE;
3359         }
3360     }
3361
3362     return TRUE;
3363 }
3364
3365 gboolean client_focus(ObClient *self)
3366 {
3367     /* choose the correct target */
3368     self = client_focus_target(self);
3369
3370     if (!client_can_focus(self)) {
3371         if (!self->frame->visible) {
3372             /* update the focus lists */
3373             focus_order_to_top(self);
3374         }
3375         return FALSE;
3376     }
3377
3378     ob_debug_type(OB_DEBUG_FOCUS,
3379                   "Focusing client \"%s\" at time %u\n",
3380                   self->title, event_curtime);
3381
3382     if (self->can_focus) {
3383         /* This can cause a BadMatch error with CurrentTime, or if an app
3384            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3385         xerror_set_ignore(TRUE);
3386         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3387                        event_curtime);
3388         xerror_set_ignore(FALSE);
3389     }
3390
3391     if (self->focus_notify) {
3392         XEvent ce;
3393         ce.xclient.type = ClientMessage;
3394         ce.xclient.message_type = prop_atoms.wm_protocols;
3395         ce.xclient.display = ob_display;
3396         ce.xclient.window = self->window;
3397         ce.xclient.format = 32;
3398         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3399         ce.xclient.data.l[1] = event_curtime;
3400         ce.xclient.data.l[2] = 0l;
3401         ce.xclient.data.l[3] = 0l;
3402         ce.xclient.data.l[4] = 0l;
3403         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3404     }
3405
3406 #ifdef DEBUG_FOCUS
3407     ob_debug("%sively focusing %lx at %d\n",
3408              (self->can_focus ? "act" : "pass"),
3409              self->window, (gint) event_curtime);
3410 #endif
3411
3412     /* Cause the FocusIn to come back to us. Important for desktop switches,
3413        since otherwise we'll have no FocusIn on the queue and send it off to
3414        the focus_backup. */
3415     XSync(ob_display, FALSE);
3416     return TRUE;
3417 }
3418
3419 /*! Present the client to the user.
3420   @param raise If the client should be raised or not. You should only set
3421                raise to false if you don't care if the window is completely
3422                hidden.
3423 */
3424 static void client_present(ObClient *self, gboolean here, gboolean raise)
3425 {
3426     /* if using focus_delay, stop the timer now so that focus doesn't
3427        go moving on us */
3428     event_halt_focus_delay();
3429
3430     if (client_normal(self) && screen_showing_desktop)
3431         screen_show_desktop(FALSE, FALSE);
3432     if (self->iconic)
3433         client_iconify(self, FALSE, here);
3434     if (self->desktop != DESKTOP_ALL &&
3435         self->desktop != screen_desktop)
3436     {
3437         if (here)
3438             client_set_desktop(self, screen_desktop, FALSE);
3439         else
3440             screen_set_desktop(self->desktop, FALSE);
3441     } else if (!self->frame->visible)
3442         /* if its not visible for other reasons, then don't mess
3443            with it */
3444         return;
3445     if (self->shaded)
3446         client_shade(self, FALSE);
3447
3448     client_focus(self);
3449
3450     if (raise) {
3451         /* we do this as an action here. this is rather important. this is
3452            because we want the results from the focus change to take place 
3453            BEFORE we go about raising the window. when a fullscreen window 
3454            loses focus, we need this or else the raise wont be able to raise 
3455            above the to-lose-focus fullscreen window. */
3456         client_raise(self);
3457     }
3458 }
3459
3460 void client_activate(ObClient *self, gboolean here, gboolean user)
3461 {
3462     guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3463     gboolean allow = FALSE;
3464
3465     /* if the request came from the user, or if nothing is focused, then grant
3466        the request.
3467        if the currently focused app doesn't set a user_time, then it can't
3468        benefit from any focus stealing prevention.
3469     */
3470     if (user || !focus_client || !last_time)
3471         allow = TRUE;
3472     /* otherwise, if they didn't give a time stamp or if it is too old, they
3473        don't get focus */
3474     else
3475         allow = event_curtime && event_time_after(event_curtime, last_time);
3476
3477     ob_debug_type(OB_DEBUG_FOCUS,
3478                   "Want to activate window 0x%x with time %u (last time %u), "
3479                   "source=%s allowing? %d\n",
3480                   self->window, event_curtime, last_time,
3481                   (user ? "user" : "application"), allow);
3482
3483     if (allow) {
3484         if (event_curtime != CurrentTime)
3485             self->user_time = event_curtime;
3486
3487         client_present(self, here, TRUE);
3488     } else
3489         /* don't focus it but tell the user it wants attention */
3490         client_hilite(self, TRUE);
3491 }
3492
3493 static void client_bring_helper_windows_recursive(ObClient *self,
3494                                                   guint desktop)
3495 {
3496     GSList *it;
3497
3498     for (it = self->transients; it; it = g_slist_next(it))
3499         client_bring_helper_windows_recursive(it->data, desktop);
3500
3501     if (client_helper(self) &&
3502         self->desktop != desktop && self->desktop != DESKTOP_ALL)
3503     {
3504         client_set_desktop(self, desktop, FALSE);
3505     }
3506 }
3507
3508 void client_bring_helper_windows(ObClient *self)
3509 {
3510     client_bring_helper_windows_recursive(self, self->desktop);
3511 }
3512
3513 void client_raise(ObClient *self)
3514 {
3515     action_run_string("Raise", self, CurrentTime);
3516 }
3517
3518 void client_lower(ObClient *self)
3519 {
3520     action_run_string("Lower", self, CurrentTime);
3521 }
3522
3523 gboolean client_focused(ObClient *self)
3524 {
3525     return self == focus_client;
3526 }
3527
3528 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3529 {
3530     guint i;
3531     /* si is the smallest image >= req */
3532     /* li is the largest image < req */
3533     gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3534
3535     if (!self->nicons) {
3536         ObClientIcon *parent = NULL;
3537
3538         if (self->transient_for) {
3539             if (self->transient_for != OB_TRAN_GROUP)
3540                 parent = client_icon_recursive(self->transient_for, w, h);
3541             else {
3542                 GSList *it;
3543                 for (it = self->group->members; it; it = g_slist_next(it)) {
3544                     ObClient *c = it->data;
3545                     if (c != self && !c->transient_for) {
3546                         if ((parent = client_icon_recursive(c, w, h)))
3547                             break;
3548                     }
3549                 }
3550             }
3551         }
3552         
3553         return parent;
3554     }
3555
3556     for (i = 0; i < self->nicons; ++i) {
3557         size = self->icons[i].width * self->icons[i].height;
3558         if (size < smallest && size >= (unsigned)(w * h)) {
3559             smallest = size;
3560             si = i;
3561         }
3562         if (size > largest && size <= (unsigned)(w * h)) {
3563             largest = size;
3564             li = i;
3565         }
3566     }
3567     if (largest == 0) /* didnt find one smaller than the requested size */
3568         return &self->icons[si];
3569     return &self->icons[li];
3570 }
3571
3572 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3573 {
3574     ObClientIcon *ret;
3575     static ObClientIcon deficon;
3576
3577     if (!(ret = client_icon_recursive(self, w, h))) {
3578         deficon.width = deficon.height = 48;
3579         deficon.data = ob_rr_theme->def_win_icon;
3580         ret = &deficon;
3581     }
3582     return ret;
3583 }
3584
3585 void client_set_layer(ObClient *self, gint layer)
3586 {
3587     if (layer < 0) {
3588         self->below = TRUE;
3589         self->above = FALSE;
3590     } else if (layer == 0) {
3591         self->below = self->above = FALSE;
3592     } else {
3593         self->below = FALSE;
3594         self->above = TRUE;
3595     }
3596     client_calc_layer(self);
3597     client_change_state(self); /* reflect this in the state hints */
3598 }
3599
3600 void client_set_undecorated(ObClient *self, gboolean undecorated)
3601 {
3602     if (self->undecorated != undecorated) {
3603         self->undecorated = undecorated;
3604         client_setup_decor_and_functions(self);
3605         /* Make sure the client knows it might have moved. Maybe there is a
3606          * better way of doing this so only one client_configure is sent, but
3607          * since 125 of these are sent per second when moving the window (with
3608          * user = FALSE) i doubt it matters much.
3609          */
3610         client_configure(self, self->area.x, self->area.y,
3611                          self->area.width, self->area.height, TRUE, TRUE);
3612         client_change_state(self); /* reflect this in the state hints */
3613     }
3614 }
3615
3616 guint client_monitor(ObClient *self)
3617 {
3618     return screen_find_monitor(&self->frame->area);
3619 }
3620
3621 ObClient *client_search_top_normal_parent(ObClient *self)
3622 {
3623     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3624            client_normal(self->transient_for))
3625         self = self->transient_for;
3626     return self;
3627 }
3628
3629 static GSList *client_search_all_top_parents_internal(ObClient *self,
3630                                                       gboolean bylayer,
3631                                                       ObStackingLayer layer)
3632 {
3633     GSList *ret = NULL;
3634     
3635     /* move up the direct transient chain as far as possible */
3636     while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3637            (!bylayer || self->transient_for->layer == layer) &&
3638            client_normal(self->transient_for))
3639         self = self->transient_for;
3640
3641     if (!self->transient_for)
3642         ret = g_slist_prepend(ret, self);
3643     else {
3644             GSList *it;
3645
3646             g_assert(self->group);
3647
3648             for (it = self->group->members; it; it = g_slist_next(it)) {
3649                 ObClient *c = it->data;
3650
3651                 if (!c->transient_for && client_normal(c) &&
3652                     (!bylayer || c->layer == layer))
3653                 {
3654                     ret = g_slist_prepend(ret, c);
3655                 }
3656             }
3657
3658             if (ret == NULL) /* no group parents */
3659                 ret = g_slist_prepend(ret, self);
3660     }
3661
3662     return ret;
3663 }
3664
3665 GSList *client_search_all_top_parents(ObClient *self)
3666 {
3667     return client_search_all_top_parents_internal(self, FALSE, 0);
3668 }
3669
3670 GSList *client_search_all_top_parents_layer(ObClient *self)
3671 {
3672     return client_search_all_top_parents_internal(self, TRUE, self->layer);
3673 }
3674
3675 ObClient *client_search_focus_parent(ObClient *self)
3676 {
3677     if (self->transient_for) {
3678         if (self->transient_for != OB_TRAN_GROUP) {
3679             if (client_focused(self->transient_for))
3680                 return self->transient_for;
3681         } else {
3682             GSList *it;
3683
3684             for (it = self->group->members; it; it = g_slist_next(it)) {
3685                 ObClient *c = it->data;
3686
3687                 /* checking transient_for prevents infinate loops! */
3688                 if (c != self && !c->transient_for)
3689                     if (client_focused(c))
3690                         return c;
3691             }
3692         }
3693     }
3694
3695     return NULL;
3696 }
3697
3698 ObClient *client_search_parent(ObClient *self, ObClient *search)
3699 {
3700     if (self->transient_for) {
3701         if (self->transient_for != OB_TRAN_GROUP) {
3702             if (self->transient_for == search)
3703                 return search;
3704         } else {
3705             GSList *it;
3706
3707             for (it = self->group->members; it; it = g_slist_next(it)) {
3708                 ObClient *c = it->data;
3709
3710                 /* checking transient_for prevents infinate loops! */
3711                 if (c != self && !c->transient_for)
3712                     if (c == search)
3713                         return search;
3714             }
3715         }
3716     }
3717
3718     return NULL;
3719 }
3720
3721 ObClient *client_search_transient(ObClient *self, ObClient *search)
3722 {
3723     GSList *sit;
3724
3725     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3726         if (sit->data == search)
3727             return search;
3728         if (client_search_transient(sit->data, search))
3729             return search;
3730     }
3731     return NULL;
3732 }
3733
3734 #define WANT_EDGE(cur, c) \
3735             if(cur == c)                                                      \
3736                 continue;                                                     \
3737             if(!client_normal(cur))                                           \
3738                 continue;                                                     \
3739             if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3740                 continue;                                                     \
3741             if(cur->iconic)                                                   \
3742                 continue;                                                     \
3743             if(cur->layer < c->layer && !config_resist_layers_below)          \
3744                 continue;
3745
3746 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3747             if ((his_edge_start >= my_edge_start && \
3748                  his_edge_start <= my_edge_end) ||  \
3749                 (my_edge_start >= his_edge_start && \
3750                  my_edge_start <= his_edge_end))    \
3751                 dest = his_offset;
3752
3753 /* finds the nearest edge in the given direction from the current client
3754  * note to self: the edge is the -frame- edge (the actual one), not the
3755  * client edge.
3756  */
3757 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3758 {
3759     gint dest, monitor_dest;
3760     gint my_edge_start, my_edge_end, my_offset;
3761     GList *it;
3762     Rect *a, *monitor;
3763     
3764     if(!client_list)
3765         return -1;
3766
3767     a = screen_area(c->desktop);
3768     monitor = screen_area_monitor(c->desktop, client_monitor(c));
3769
3770     switch(dir) {
3771     case OB_DIRECTION_NORTH:
3772         my_edge_start = c->frame->area.x;
3773         my_edge_end = c->frame->area.x + c->frame->area.width;
3774         my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3775         
3776         /* default: top of screen */
3777         dest = a->y + (hang ? c->frame->area.height : 0);
3778         monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3779         /* if the monitor edge comes before the screen edge, */
3780         /* use that as the destination instead. (For xinerama) */
3781         if (monitor_dest != dest && my_offset > monitor_dest)
3782             dest = monitor_dest; 
3783
3784         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3785             gint his_edge_start, his_edge_end, his_offset;
3786             ObClient *cur = it->data;
3787
3788             WANT_EDGE(cur, c)
3789
3790             his_edge_start = cur->frame->area.x;
3791             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3792             his_offset = cur->frame->area.y + 
3793                          (hang ? 0 : cur->frame->area.height);
3794
3795             if(his_offset + 1 > my_offset)
3796                 continue;
3797
3798             if(his_offset < dest)
3799                 continue;
3800
3801             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3802         }
3803         break;
3804     case OB_DIRECTION_SOUTH:
3805         my_edge_start = c->frame->area.x;
3806         my_edge_end = c->frame->area.x + c->frame->area.width;
3807         my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3808
3809         /* default: bottom of screen */
3810         dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3811         monitor_dest = monitor->y + monitor->height -
3812                        (hang ? c->frame->area.height : 0);
3813         /* if the monitor edge comes before the screen edge, */
3814         /* use that as the destination instead. (For xinerama) */
3815         if (monitor_dest != dest && my_offset < monitor_dest)
3816             dest = monitor_dest; 
3817
3818         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3819             gint his_edge_start, his_edge_end, his_offset;
3820             ObClient *cur = it->data;
3821
3822             WANT_EDGE(cur, c)
3823
3824             his_edge_start = cur->frame->area.x;
3825             his_edge_end = cur->frame->area.x + cur->frame->area.width;
3826             his_offset = cur->frame->area.y +
3827                          (hang ? cur->frame->area.height : 0);
3828
3829
3830             if(his_offset - 1 < my_offset)
3831                 continue;
3832             
3833             if(his_offset > dest)
3834                 continue;
3835
3836             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3837         }
3838         break;
3839     case OB_DIRECTION_WEST:
3840         my_edge_start = c->frame->area.y;
3841         my_edge_end = c->frame->area.y + c->frame->area.height;
3842         my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3843
3844         /* default: leftmost egde of screen */
3845         dest = a->x + (hang ? c->frame->area.width : 0);
3846         monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3847         /* if the monitor edge comes before the screen edge, */
3848         /* use that as the destination instead. (For xinerama) */
3849         if (monitor_dest != dest && my_offset > monitor_dest)
3850             dest = monitor_dest;            
3851
3852         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3853             gint his_edge_start, his_edge_end, his_offset;
3854             ObClient *cur = it->data;
3855
3856             WANT_EDGE(cur, c)
3857
3858             his_edge_start = cur->frame->area.y;
3859             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3860             his_offset = cur->frame->area.x +
3861                          (hang ? 0 : cur->frame->area.width);
3862
3863             if(his_offset + 1 > my_offset)
3864                 continue;
3865
3866             if(his_offset < dest)
3867                 continue;
3868
3869             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3870         }
3871        break;
3872     case OB_DIRECTION_EAST:
3873         my_edge_start = c->frame->area.y;
3874         my_edge_end = c->frame->area.y + c->frame->area.height;
3875         my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3876         
3877         /* default: rightmost edge of screen */
3878         dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3879         monitor_dest = monitor->x + monitor->width -
3880                        (hang ? c->frame->area.width : 0);
3881         /* if the monitor edge comes before the screen edge, */
3882         /* use that as the destination instead. (For xinerama) */
3883         if (monitor_dest != dest && my_offset < monitor_dest)
3884             dest = monitor_dest;            
3885
3886         for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3887             gint his_edge_start, his_edge_end, his_offset;
3888             ObClient *cur = it->data;
3889
3890             WANT_EDGE(cur, c)
3891
3892             his_edge_start = cur->frame->area.y;
3893             his_edge_end = cur->frame->area.y + cur->frame->area.height;
3894             his_offset = cur->frame->area.x +
3895                          (hang ? cur->frame->area.width : 0);
3896
3897             if(his_offset - 1 < my_offset)
3898                 continue;
3899             
3900             if(his_offset > dest)
3901                 continue;
3902
3903             HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3904         }
3905         break;
3906     case OB_DIRECTION_NORTHEAST:
3907     case OB_DIRECTION_SOUTHEAST:
3908     case OB_DIRECTION_NORTHWEST:
3909     case OB_DIRECTION_SOUTHWEST:
3910         /* not implemented */
3911     default:
3912         g_assert_not_reached();
3913         dest = 0; /* suppress warning */
3914     }
3915     return dest;
3916 }
3917
3918 ObClient* client_under_pointer()
3919 {
3920     gint x, y;
3921     GList *it;
3922     ObClient *ret = NULL;
3923
3924     if (screen_pointer_pos(&x, &y)) {
3925         for (it = stacking_list; it; it = g_list_next(it)) {
3926             if (WINDOW_IS_CLIENT(it->data)) {
3927                 ObClient *c = WINDOW_AS_CLIENT(it->data);
3928                 if (c->frame->visible &&
3929                     /* ignore all animating windows */
3930                     !frame_iconify_animating(c->frame) &&
3931                     RECT_CONTAINS(c->frame->area, x, y))
3932                 {
3933                     ret = c;
3934                     break;
3935                 }
3936             }
3937         }
3938     }
3939     return ret;
3940 }
3941
3942 gboolean client_has_group_siblings(ObClient *self)
3943 {
3944     return self->group && self->group->members->next;
3945 }