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