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