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