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