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