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