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