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