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