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