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