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