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