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