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