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