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