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