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