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