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