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