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