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