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