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