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