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