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