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