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