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