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