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