Merge branch 'backport' into work
[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 "stacking.h"
35 #include "openbox.h"
36 #include "hooks.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43 #include "gettext.h"
44 #include "obt/display.h"
45 #include "obt/prop.h"
46
47 #ifdef HAVE_UNISTD_H
48 #  include <unistd.h>
49 #endif
50
51 #ifdef HAVE_SIGNAL_H
52 #  include <signal.h> /* for kill() */
53 #endif
54
55 #include <glib.h>
56 #include <X11/Xutil.h>
57
58 /*! The event mask to grab on client windows */
59 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | \
60                           ColormapChangeMask)
61
62 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
63                                 ButtonMotionMask)
64
65 typedef struct
66 {
67     ObClientCallback func;
68     gpointer data;
69 } ClientCallback;
70
71 GList          *client_list             = NULL;
72
73 static GSList  *client_destroy_notifies = NULL;
74 static RrImage *client_default_icon     = NULL;
75
76 static void client_get_all(ObClient *self, gboolean real);
77 static void client_get_startup_id(ObClient *self);
78 static void client_get_session_ids(ObClient *self);
79 static void client_get_area(ObClient *self);
80 static void client_get_desktop(ObClient *self);
81 static void client_get_state(ObClient *self);
82 static void client_get_shaped(ObClient *self);
83 static void client_get_mwm_hints(ObClient *self);
84 static void client_get_colormap(ObClient *self);
85 static void client_set_desktop_recursive(ObClient *self,
86                                          guint target,
87                                          gboolean donthide,
88                                          gboolean dontraise);
89 static void client_change_allowed_actions(ObClient *self);
90 static void client_change_state(ObClient *self);
91 static void client_change_wm_state(ObClient *self);
92 static void client_apply_startup_state(ObClient *self,
93                                        gint x, gint y, gint w, gint h);
94 static void client_restore_session_state(ObClient *self);
95 static gboolean client_restore_session_stacking(ObClient *self);
96 static ObAppSettings *client_get_settings_state(ObClient *self);
97 static void client_update_transient_tree(ObClient *self,
98                                          ObGroup *oldgroup, ObGroup *newgroup,
99                                          gboolean oldgtran, gboolean newgtran,
100                                          ObClient* oldparent,
101                                          ObClient *newparent);
102 static void client_present(ObClient *self, gboolean here, gboolean raise,
103                            gboolean unshade);
104 static GSList *client_search_all_top_parents_internal(ObClient *self,
105                                                       gboolean bylayer,
106                                                       ObStackingLayer layer);
107 static void client_call_notifies(ObClient *self, GSList *list);
108 static void client_ping_event(ObClient *self, gboolean dead);
109 static void client_prompt_kill(ObClient *self);
110
111
112 void client_startup(gboolean reconfig)
113 {
114     if ((client_default_icon = RrImageCacheFind(ob_rr_icons,
115                                                 ob_rr_theme->def_win_icon,
116                                                 ob_rr_theme->def_win_icon_w,
117                                                 ob_rr_theme->def_win_icon_h)))
118         RrImageRef(client_default_icon);
119     else {
120         client_default_icon = RrImageNew(ob_rr_icons);
121         RrImageAddPicture(client_default_icon,
122                           ob_rr_theme->def_win_icon,
123                           ob_rr_theme->def_win_icon_w,
124                           ob_rr_theme->def_win_icon_h);
125     }
126
127     if (reconfig) return;
128
129     client_set_list();
130 }
131
132 void client_shutdown(gboolean reconfig)
133 {
134     RrImageUnref(client_default_icon);
135     client_default_icon = NULL;
136
137     if (reconfig) return;
138 }
139
140 static void client_call_notifies(ObClient *self, GSList *list)
141 {
142     GSList *it;
143
144     for (it = list; it; it = g_slist_next(it)) {
145         ClientCallback *d = it->data;
146         d->func(self, d->data);
147     }
148 }
149
150 void client_add_destroy_notify(ObClientCallback func, gpointer data)
151 {
152     ClientCallback *d = g_new(ClientCallback, 1);
153     d->func = func;
154     d->data = data;
155     client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d);
156 }
157
158 void client_remove_destroy_notify(ObClientCallback func)
159 {
160     GSList *it;
161
162     for (it = client_destroy_notifies; it; it = g_slist_next(it)) {
163         ClientCallback *d = it->data;
164         if (d->func == func) {
165             g_free(d);
166             client_destroy_notifies =
167                 g_slist_delete_link(client_destroy_notifies, it);
168             break;
169         }
170     }
171 }
172
173 void client_set_list(void)
174 {
175     Window *windows, *win_it;
176     GList *it;
177     guint size = g_list_length(client_list);
178
179     /* create an array of the window ids */
180     if (size > 0) {
181         windows = g_new(Window, size);
182         win_it = windows;
183         for (it = client_list; it; it = g_list_next(it), ++win_it)
184             *win_it = ((ObClient*)it->data)->window;
185     } else
186         windows = NULL;
187
188     OBT_PROP_SETA32(obt_root(ob_screen), NET_CLIENT_LIST, WINDOW,
189                     (gulong*)windows, size);
190
191     if (windows)
192         g_free(windows);
193
194     stacking_set_list();
195 }
196
197 void client_manage(Window window, ObPrompt *prompt)
198 {
199     ObClient *self;
200     XSetWindowAttributes attrib_set;
201     gboolean activate = FALSE;
202     ObAppSettings *settings;
203     gboolean transient = FALSE;
204     Rect place, *monitor;
205     Time launch_time, map_time;
206
207     ob_debug("Managing window: 0x%lx", window);
208
209     map_time = event_get_server_time();
210
211     /* choose the events we want to receive on the CLIENT window
212        (ObPrompt windows can request events too) */
213     attrib_set.event_mask = CLIENT_EVENTMASK |
214         (prompt ? prompt->event_mask : 0);
215     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
216     XChangeWindowAttributes(obt_display, window,
217                             CWEventMask|CWDontPropagate, &attrib_set);
218
219     /* create the ObClient struct, and populate it from the hints on the
220        window */
221     self = g_new0(ObClient, 1);
222     self->obwin.type = OB_WINDOW_CLASS_CLIENT;
223     self->window = window;
224     self->prompt = prompt;
225
226     /* non-zero defaults */
227     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
228     self->gravity = NorthWestGravity;
229     self->desktop = screen_num_desktops; /* always an invalid value */
230
231     /* get all the stuff off the window */
232     client_get_all(self, TRUE);
233
234     ob_debug("Window type: %d", self->type);
235     ob_debug("Window group: 0x%x", self->group?self->group->leader:0);
236
237     /* now we have all of the window's information so we can set this up.
238        do this before creating the frame, so it can tell that we are still
239        mapping and doesn't go applying things right away */
240     client_setup_decor_and_functions(self, FALSE);
241
242     /* specify that if we exit, the window should not be destroyed and
243        should be reparented back to root automatically, unless we are managing
244        an internal ObPrompt window  */
245     if (!self->prompt)
246         XChangeSaveSet(obt_display, window, SetModeInsert);
247
248     /* create the decoration frame for the client window */
249     self->frame = frame_new(self);
250
251     frame_grab_client(self->frame);
252
253     /* we've grabbed everything and set everything that we need to at mapping
254        time now */
255     grab_server(FALSE);
256
257     /* per-app settings override stuff from client_get_all, and return the
258        settings for other uses too. the returned settings is a shallow copy,
259        that needs to be freed with g_free(). */
260     settings = client_get_settings_state(self);
261     /* the session should get the last say though */
262     client_restore_session_state(self);
263
264     /* tell startup notification that this app started */
265     launch_time = sn_app_started(self->startup_id, self->class, self->name);
266
267     /* do this after we have a frame.. it uses the frame to help determine the
268        WM_STATE to apply. */
269     client_change_state(self);
270
271     /* add ourselves to the focus order */
272     focus_order_add_new(self);
273
274     /* do this to add ourselves to the stacking list in a non-intrusive way */
275     client_calc_layer(self);
276
277     /* focus the new window? */
278     if (ob_state() != OB_STATE_STARTING &&
279         (!self->session || self->session->focused) &&
280         /* this means focus=true for window is same as config_focus_new=true */
281         ((config_focus_new || (settings && settings->focus == 1)) ||
282          client_search_focus_tree_full(self)) &&
283         /* this checks for focus=false for the window */
284         (!settings || settings->focus != 0) &&
285         focus_valid_target(self, FALSE, FALSE, TRUE, FALSE, FALSE))
286     {
287         activate = TRUE;
288     }
289
290     /* remove the client's border */
291     XSetWindowBorderWidth(obt_display, self->window, 0);
292
293     /* adjust the frame to the client's size before showing or placing
294        the window */
295     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
296     frame_adjust_client_area(self->frame);
297
298     /* where the frame was placed is where the window was originally */
299     place = self->area;
300     monitor = screen_physical_area_monitor(screen_find_monitor(&place));
301
302     /* figure out placement for the window if the window is new */
303     if (ob_state() == OB_STATE_RUNNING) {
304         ob_debug("Positioned: %s @ %d %d",
305                  (!self->positioned ? "no" :
306                   (self->positioned == PPosition ? "program specified" :
307                    (self->positioned == USPosition ? "user specified" :
308                     (self->positioned == (PPosition | USPosition) ?
309                      "program + user specified" :
310                      "BADNESS !?")))), place.x, place.y);
311
312         ob_debug("Sized: %s @ %d %d",
313                  (!self->sized ? "no" :
314                   (self->sized == PSize ? "program specified" :
315                    (self->sized == USSize ? "user specified" :
316                     (self->sized == (PSize | USSize) ?
317                      "program + user specified" :
318                      "BADNESS !?")))), place.width, place.height);
319
320         /* splash screens are also returned as TRUE for transient,
321            and so will be forced on screen below */
322         transient = place_client(self, &place.x, &place.y, settings);
323
324         /* make sure the window is visible. */
325         client_find_onscreen(self, &place.x, &place.y,
326                              place.width, place.height,
327                              /* non-normal clients has less rules, and
328                                 windows that are being restored from a
329                                 session do also. we can assume you want
330                                 it back where you saved it. Clients saying
331                                 they placed themselves are subjected to
332                                 harder rules, ones that are placed by
333                                 place.c or by the user are allowed partially
334                                 off-screen and on xinerama divides (ie,
335                                 it is up to the placement routines to avoid
336                                 the xinerama divides)
337
338                                 splash screens get "transient" set to TRUE by
339                                 the place_client call
340                              */
341                              ob_state() == OB_STATE_RUNNING &&
342                              (transient ||
343                               (!((self->positioned & USPosition) ||
344                                  (settings && settings->pos_given)) &&
345                                client_normal(self) &&
346                                !self->session &&
347                                /* don't move oldschool fullscreen windows to
348                                   fit inside the struts (fixes Acroread, which
349                                   makes its fullscreen window fit the screen
350                                   but it is not USSize'd or USPosition'd) */
351                                !(self->decorations == 0 &&
352                                  RECT_EQUAL(place, *monitor)))));
353     }
354
355     /* if the window isn't user-sized, then make it fit inside
356        the visible screen area on its monitor. Use basically the same rules
357        for forcing the window on screen in the client_find_onscreen call.
358
359        do this after place_client, it chooses the monitor!
360
361        splash screens get "transient" set to TRUE by
362        the place_client call
363     */
364     if (ob_state() == OB_STATE_RUNNING &&
365         (transient ||
366          (!(self->sized & USSize || self->positioned & USPosition) &&
367           client_normal(self) &&
368           !self->session &&
369           /* don't shrink oldschool fullscreen windows to fit inside the
370              struts (fixes Acroread, which makes its fullscreen window
371              fit the screen but it is not USSize'd or USPosition'd) */
372           !(self->decorations == 0 && RECT_EQUAL(place, *monitor)))))
373     {
374         Rect *a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &place);
375
376         /* get the size of the frame */
377         place.width += self->frame->size.left + self->frame->size.right;
378         place.height += self->frame->size.top + self->frame->size.bottom;
379
380         /* fit the window inside the area */
381         place.width = MIN(place.width, a->width);
382         place.height = MIN(place.height, a->height);
383
384         ob_debug("setting window size to %dx%d", place.width, place.height);
385
386         /* get the size of the client back */
387         place.width -= self->frame->size.left + self->frame->size.right;
388         place.height -= self->frame->size.top + self->frame->size.bottom;
389
390         g_free(a);
391     }
392
393     ob_debug("placing window 0x%x at %d, %d with size %d x %d. "
394              "some restrictions may apply",
395              self->window, place.x, place.y, place.width, place.height);
396     if (self->session)
397         ob_debug("  but session requested %d, %d  %d x %d instead, "
398                  "overriding",
399                  self->session->x, self->session->y,
400                  self->session->w, self->session->h);
401
402     /* do this after the window is placed, so the premax/prefullscreen numbers
403        won't be all wacko!!
404
405        this also places the window
406     */
407     client_apply_startup_state(self, place.x, place.y,
408                                place.width, place.height);
409
410     g_free(monitor);
411     monitor = NULL;
412
413     ob_debug_type(OB_DEBUG_FOCUS, "Going to try activate new window? %s",
414                   activate ? "yes" : "no");
415     if (activate) {
416         gboolean raise = FALSE;
417         gboolean relative_focused;
418         gboolean parent_focused;
419
420         parent_focused = (focus_client != NULL &&
421                           client_search_focus_parent(self));
422         relative_focused = (focus_client != NULL &&
423                             (client_search_focus_tree_full(self) != NULL ||
424                              client_search_focus_group_full(self) != NULL));
425
426         /* This is focus stealing prevention */
427         ob_debug_type(OB_DEBUG_FOCUS,
428                       "Want to focus new window 0x%x at time %u "
429                       "launched at %u (last user interaction time %u)",
430                       self->window, map_time, launch_time,
431                       event_last_user_time);
432         ob_debug_type(OB_DEBUG_FOCUS,
433                       "Current focus_client: %s",
434                       (focus_client ? focus_client->title : "(none)"));
435         ob_debug_type(OB_DEBUG_FOCUS,
436                       "parent focuesed: %d  relative focused: %d",
437                       parent_focused, relative_focused);
438
439         if (menu_frame_visible || moveresize_in_progress) {
440             activate = FALSE;
441             raise = TRUE;
442             ob_debug_type(OB_DEBUG_FOCUS,
443                           "Not focusing the window because the user is inside "
444                           "an Openbox menu or is move/resizing a window and "
445                           "we don't want to interrupt them");
446         }
447
448         /* if it's on another desktop */
449         else if (!(self->desktop == screen_desktop ||
450                    self->desktop == DESKTOP_ALL) &&
451                  /* the timestamp is from before you changed desktops */
452                  launch_time && screen_desktop_user_time &&
453                  !event_time_after(launch_time, screen_desktop_user_time))
454         {
455             activate = FALSE;
456             raise = TRUE;
457             ob_debug_type(OB_DEBUG_FOCUS,
458                           "Not focusing the window because its on another "
459                           "desktop");
460         }
461         /* If something is focused... */
462         else if (focus_client) {
463             /* If the user is working in another window right now, then don't
464                steal focus */
465             if (!parent_focused &&
466                 event_last_user_time && launch_time &&
467                 event_time_after(event_last_user_time, launch_time) &&
468                 event_last_user_time != launch_time &&
469                 event_time_after(event_last_user_time,
470                                  map_time - OB_EVENT_USER_TIME_DELAY))
471             {
472                 activate = FALSE;
473                 ob_debug_type(OB_DEBUG_FOCUS,
474                               "Not focusing the window because the user is "
475                               "working in another window that is not "
476                               "its parent");
477             }
478             /* If the new window is a transient (and its relatives aren't
479                focused) */
480             else if (client_has_parent(self) && !relative_focused) {
481                 activate = FALSE;
482                 ob_debug_type(OB_DEBUG_FOCUS,
483                               "Not focusing the window because it is a "
484                               "transient, and its relatives aren't focused");
485             }
486             /* Don't steal focus from globally active clients.
487                I stole this idea from KWin. It seems nice.
488              */
489             else if (!(focus_client->can_focus ||
490                        focus_client->focus_notify))
491             {
492                 activate = FALSE;
493                 ob_debug_type(OB_DEBUG_FOCUS,
494                               "Not focusing the window because a globally "
495                               "active client has focus");
496             }
497             /* Don't move focus if it's not going to go to this window
498                anyway */
499             else if (client_focus_target(self) != self) {
500                 activate = FALSE;
501                 raise = TRUE;
502                 ob_debug_type(OB_DEBUG_FOCUS,
503                               "Not focusing the window because another window "
504                               "would get the focus anyway");
505             }
506             /* Don't move focus if the window is not visible on the current
507                desktop and none of its relatives are focused */
508             else if (!(self->desktop == screen_desktop ||
509                        self->desktop == DESKTOP_ALL) &&
510                      !relative_focused)
511             {
512                 activate = FALSE;
513                 raise = TRUE;
514                 ob_debug_type(OB_DEBUG_FOCUS,
515                               "Not focusing the window because it is on "
516                               "another desktop and no relatives are focused ");
517             }
518         }
519
520         if (!activate) {
521             ob_debug_type(OB_DEBUG_FOCUS,
522                           "Focus stealing prevention activated for %s at "
523                           "time %u (last user interaction time %u)",
524                           self->title, map_time, event_last_user_time);
525             /* if the client isn't focused, then hilite it so the user
526                knows it is there */
527             client_hilite(self, TRUE);
528             /* we may want to raise it even tho we're not activating it */
529             if (raise && !client_restore_session_stacking(self))
530                 stacking_raise(CLIENT_AS_WINDOW(self));
531         }
532     }
533     else {
534         /* This may look rather odd. Well it's because new windows are added
535            to the stacking order non-intrusively. If we're not going to focus
536            the new window or hilite it, then we raise it to the top. This will
537            take affect for things that don't get focused like splash screens.
538            Also if you don't have focus_new enabled, then it's going to get
539            raised to the top. Legacy begets legacy I guess?
540         */
541         if (!client_restore_session_stacking(self))
542             stacking_raise(CLIENT_AS_WINDOW(self));
543     }
544
545     mouse_grab_for_client(self, TRUE);
546
547     /* this has to happen before we try focus the window, but we want it to
548        happen after the client's stacking has been determined or it looks bad
549     */
550     {
551         gulong ignore_start;
552         if (!config_focus_under_mouse)
553             ignore_start = event_start_ignore_all_enters();
554
555         client_show(self);
556
557         if (!config_focus_under_mouse)
558             event_end_ignore_all_enters(ignore_start);
559     }
560
561     if (activate) {
562         gboolean stacked = client_restore_session_stacking(self);
563         client_present(self, FALSE, !stacked, TRUE);
564     }
565
566     /* add to client list/map */
567     client_list = g_list_append(client_list, self);
568     window_add(&self->window, CLIENT_AS_WINDOW(self));
569
570     /* this has to happen after we're in the client_list */
571     if (STRUT_EXISTS(self->strut))
572         screen_update_areas();
573
574     /* update the list hints */
575     client_set_list();
576
577     /* free the ObAppSettings shallow copy */
578     g_free(settings);
579
580     ob_debug("Managed window 0x%lx plate 0x%x (%s)",
581              window, self->frame->window, self->class);
582
583     hooks_queue(OB_HOOK_WIN_NEW, self);
584 }
585
586
587 ObClient *client_fake_manage(Window window)
588 {
589     ObClient *self;
590     ObAppSettings *settings;
591
592     ob_debug("Pretend-managing window: %lx", window);
593
594     /* do this minimal stuff to figure out the client's decorations */
595
596     self = g_new0(ObClient, 1);
597     self->window = window;
598
599     client_get_all(self, FALSE);
600     /* per-app settings override stuff, and return the settings for other
601        uses too. this returns a shallow copy that needs to be freed */
602     settings = client_get_settings_state(self);
603
604     client_setup_decor_and_functions(self, FALSE);
605
606     /* create the decoration frame for the client window and adjust its size */
607     self->frame = frame_new(self);
608     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
609
610     ob_debug("gave extents left %d right %d top %d bottom %d",
611              self->frame->size.left, self->frame->size.right,
612              self->frame->size.top, self->frame->size.bottom);
613
614     /* free the ObAppSettings shallow copy */
615     g_free(settings);
616
617     return self;
618 }
619
620 void client_unmanage_all(void)
621 {
622     while (client_list)
623         client_unmanage(client_list->data);
624 }
625
626 void client_unmanage(ObClient *self)
627 {
628     GSList *it;
629     gulong ignore_start;
630
631     ob_debug("Unmanaging window: 0x%x plate 0x%x (%s) (%s)",
632              self->window, self->frame->window,
633              self->class, self->title ? self->title : "");
634
635     g_assert(self != NULL);
636
637     /* we dont want events no more. do this before hiding the frame so we
638        don't generate more events */
639     XSelectInput(obt_display, self->window, NoEventMask);
640
641     /* ignore enter events from the unmap so it doesnt mess with the focus */
642     if (!config_focus_under_mouse)
643         ignore_start = event_start_ignore_all_enters();
644
645     frame_hide(self->frame);
646     /* flush to send the hide to the server quickly */
647     XFlush(obt_display);
648
649     if (!config_focus_under_mouse)
650         event_end_ignore_all_enters(ignore_start);
651
652     mouse_grab_for_client(self, FALSE);
653
654     /* remove the window from our save set, unless we are managing an internal
655        ObPrompt window */
656     if (!self->prompt)
657         XChangeSaveSet(obt_display, self->window, SetModeDelete);
658
659     /* this can't be queued to run later */
660     hooks_run(OB_HOOK_WIN_CLOSE, self);
661
662     /* update the focus lists */
663     focus_order_remove(self);
664     if (client_focused(self)) {
665         /* don't leave an invalid focus_client */
666         focus_client = NULL;
667     }
668
669     /* if we're prompting to kill the client, close that */
670     prompt_unref(self->kill_prompt);
671     self->kill_prompt = NULL;
672
673     client_list = g_list_remove(client_list, self);
674     stacking_remove(self);
675     window_remove(self->window);
676
677     /* once the client is out of the list, update the struts to remove its
678        influence */
679     if (STRUT_EXISTS(self->strut))
680         screen_update_areas();
681
682     client_call_notifies(self, client_destroy_notifies);
683
684     /* tell our parent(s) that we're gone */
685     for (it = self->parents; it; it = g_slist_next(it))
686         ((ObClient*)it->data)->transients =
687             g_slist_remove(((ObClient*)it->data)->transients,self);
688
689     /* tell our transients that we're gone */
690     for (it = self->transients; it; it = g_slist_next(it)) {
691         ((ObClient*)it->data)->parents =
692             g_slist_remove(((ObClient*)it->data)->parents, self);
693         /* we could be keeping our children in a higher layer */
694         client_calc_layer(it->data);
695     }
696
697     /* remove from its group */
698     if (self->group) {
699         group_remove(self->group, self);
700         self->group = NULL;
701     }
702
703     /* restore the window's original geometry so it is not lost */
704     {
705         Rect a;
706
707         a = self->area;
708
709         if (self->fullscreen)
710             a = self->pre_fullscreen_area;
711         else if (self->max_horz || self->max_vert) {
712             if (self->max_horz) {
713                 a.x = self->pre_max_area.x;
714                 a.width = self->pre_max_area.width;
715             }
716             if (self->max_vert) {
717                 a.y = self->pre_max_area.y;
718                 a.height = self->pre_max_area.height;
719             }
720         }
721
722         self->fullscreen = self->max_horz = self->max_vert = FALSE;
723         /* let it be moved and resized no matter what */
724         self->functions = OB_CLIENT_FUNC_MOVE | OB_CLIENT_FUNC_RESIZE;
725         self->decorations = 0; /* unmanaged windows have no decor */
726
727         /* give the client its border back */
728         XSetWindowBorderWidth(obt_display, self->window, self->border_width);
729
730         client_move_resize(self, a.x, a.y, a.width, a.height);
731     }
732
733     /* reparent the window out of the frame, and free the frame */
734     frame_release_client(self->frame);
735     frame_free(self->frame);
736     self->frame = NULL;
737
738     if (ob_state() != OB_STATE_EXITING) {
739         /* these values should not be persisted across a window
740            unmapping/mapping */
741         OBT_PROP_ERASE(self->window, NET_WM_DESKTOP);
742         OBT_PROP_ERASE(self->window, NET_WM_STATE);
743         OBT_PROP_ERASE(self->window, WM_STATE);
744     } else {
745         /* if we're left in an unmapped state, the client wont be mapped.
746            this is bad, since we will no longer be managing the window on
747            restart */
748         XMapWindow(obt_display, self->window);
749     }
750
751     /* these should not be left on the window ever.  other window managers
752        don't necessarily use them and it will mess them up (like compiz) */
753     OBT_PROP_ERASE(self->window, NET_WM_VISIBLE_NAME);
754     OBT_PROP_ERASE(self->window, NET_WM_VISIBLE_ICON_NAME);
755
756     /* update the list hints */
757     client_set_list();
758
759     ob_debug("Unmanaged window 0x%lx", self->window);
760
761     /* free all data allocated in the client struct */
762     RrImageUnref(self->icon_set);
763     g_slist_free(self->transients);
764     g_free(self->startup_id);
765     g_free(self->wm_command);
766     g_free(self->title);
767     g_free(self->icon_title);
768     g_free(self->original_title);
769     g_free(self->name);
770     g_free(self->class);
771     g_free(self->role);
772     g_free(self->client_machine);
773     g_free(self->sm_client_id);
774     g_free(self);
775 }
776
777 void client_fake_unmanage(ObClient *self)
778 {
779     /* this is all that got allocated to get the decorations */
780
781     frame_free(self->frame);
782     g_free(self);
783 }
784
785 /*! Returns a new structure containing the per-app settings for this client.
786   The returned structure needs to be freed with g_free. */
787 static ObAppSettings *client_get_settings_state(ObClient *self)
788 {
789     ObAppSettings *settings;
790     GSList *it;
791
792     settings = config_create_app_settings();
793
794     for (it = config_per_app_settings; it; it = g_slist_next(it)) {
795         ObAppSettings *app = it->data;
796         gboolean match = TRUE;
797
798         g_assert(app->name != NULL || app->class != NULL);
799
800         /* we know that either name or class is not NULL so it will have to
801            match to use the rule */
802         if (app->name &&
803             !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
804             match = FALSE;
805         else if (app->class &&
806                  !g_pattern_match(app->class,
807                                   strlen(self->class), self->class, NULL))
808             match = FALSE;
809         else if (app->role &&
810                  !g_pattern_match(app->role,
811                                   strlen(self->role), self->role, NULL))
812             match = FALSE;
813         else if ((signed)app->type >= 0 && app->type != self->type)
814             match = FALSE;
815
816         if (match) {
817             ob_debug("Window matching: %s", app->name);
818
819             /* copy the settings to our struct, overriding the existing
820                settings if they are not defaults */
821             config_app_settings_copy_non_defaults(app, settings);
822         }
823     }
824
825     if (settings->shade != -1)
826         self->shaded = !!settings->shade;
827     if (settings->decor != -1)
828         self->undecorated = !settings->decor;
829     if (settings->iconic != -1)
830         self->iconic = !!settings->iconic;
831     if (settings->skip_pager != -1)
832         self->skip_pager = !!settings->skip_pager;
833     if (settings->skip_taskbar != -1)
834         self->skip_taskbar = !!settings->skip_taskbar;
835
836     if (settings->max_vert != -1)
837         self->max_vert = !!settings->max_vert;
838     if (settings->max_horz != -1)
839         self->max_horz = !!settings->max_horz;
840
841     if (settings->fullscreen != -1)
842         self->fullscreen = !!settings->fullscreen;
843
844     if (settings->desktop) {
845         if (settings->desktop == DESKTOP_ALL)
846             self->desktop = settings->desktop;
847         else if (settings->desktop > 0 &&
848                  settings->desktop <= screen_num_desktops)
849             self->desktop = settings->desktop - 1;
850     }
851
852     if (settings->layer == -1) {
853         self->below = TRUE;
854         self->above = FALSE;
855     }
856     else if (settings->layer == 0) {
857         self->below = FALSE;
858         self->above = FALSE;
859     }
860     else if (settings->layer == 1) {
861         self->below = FALSE;
862         self->above = TRUE;
863     }
864     return settings;
865 }
866
867 static void client_restore_session_state(ObClient *self)
868 {
869     GList *it;
870
871     ob_debug_type(OB_DEBUG_SM,
872                   "Restore session for client %s", self->title);
873
874     if (!(it = session_state_find(self))) {
875         ob_debug_type(OB_DEBUG_SM,
876                       "Session data not found for client %s", self->title);
877         return;
878     }
879
880     self->session = it->data;
881
882     ob_debug_type(OB_DEBUG_SM, "Session data loaded for client %s",
883                   self->title);
884
885     RECT_SET_POINT(self->area, self->session->x, self->session->y);
886     self->positioned = USPosition;
887     self->sized = USSize;
888     if (self->session->w > 0)
889         self->area.width = self->session->w;
890     if (self->session->h > 0)
891         self->area.height = self->session->h;
892     XResizeWindow(obt_display, self->window,
893                   self->area.width, self->area.height);
894
895     self->desktop = (self->session->desktop == DESKTOP_ALL ?
896                      self->session->desktop :
897                      MIN(screen_num_desktops - 1, self->session->desktop));
898     OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, self->desktop);
899
900     self->shaded = self->session->shaded;
901     self->iconic = self->session->iconic;
902     self->skip_pager = self->session->skip_pager;
903     self->skip_taskbar = self->session->skip_taskbar;
904     self->fullscreen = self->session->fullscreen;
905     self->above = self->session->above;
906     self->below = self->session->below;
907     self->max_horz = self->session->max_horz;
908     self->max_vert = self->session->max_vert;
909     self->undecorated = self->session->undecorated;
910 }
911
912 static gboolean client_restore_session_stacking(ObClient *self)
913 {
914     GList *it, *mypos;
915
916     if (!self->session) return FALSE;
917
918     mypos = g_list_find(session_saved_state, self->session);
919     if (!mypos) return FALSE;
920
921     /* start above me and look for the first client */
922     for (it = g_list_previous(mypos); it; it = g_list_previous(it)) {
923         GList *cit;
924
925         for (cit = client_list; cit; cit = g_list_next(cit)) {
926             ObClient *c = cit->data;
927             /* found a client that was in the session, so go below it */
928             if (c->session == it->data) {
929                 stacking_below(CLIENT_AS_WINDOW(self),
930                                CLIENT_AS_WINDOW(cit->data));
931                 return TRUE;
932             }
933         }
934     }
935     return FALSE;
936 }
937
938 void client_move_onscreen(ObClient *self, gboolean rude)
939 {
940     gint x = self->area.x;
941     gint y = self->area.y;
942     if (client_find_onscreen(self, &x, &y,
943                              self->area.width,
944                              self->area.height, rude)) {
945         client_move(self, x, y);
946     }
947 }
948
949 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
950                               gboolean rude)
951 {
952     gint ox = *x, oy = *y;
953     gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude;
954     gint fw, fh;
955     Rect desired;
956     guint i;
957     gboolean found_mon;
958
959     RECT_SET(desired, *x, *y, w, h);
960     frame_rect_to_frame(self->frame, &desired);
961
962     /* get where the frame would be */
963     frame_client_gravity(self->frame, x, y);
964
965     /* get the requested size of the window with decorations */
966     fw = self->frame->size.left + w + self->frame->size.right;
967     fh = self->frame->size.top + h + self->frame->size.bottom;
968
969     /* If rudeness wasn't requested, then still be rude in a given direction
970        if the client is not moving, only resizing in that direction */
971     if (!rude) {
972         Point oldtl, oldtr, oldbl, oldbr;
973         Point newtl, newtr, newbl, newbr;
974         gboolean stationary_l, stationary_r, stationary_t, stationary_b;
975
976         POINT_SET(oldtl, self->frame->area.x, self->frame->area.y);
977         POINT_SET(oldbr, self->frame->area.x + self->frame->area.width - 1,
978                   self->frame->area.y + self->frame->area.height - 1);
979         POINT_SET(oldtr, oldbr.x, oldtl.y);
980         POINT_SET(oldbl, oldtl.x, oldbr.y);
981
982         POINT_SET(newtl, *x, *y);
983         POINT_SET(newbr, *x + fw - 1, *y + fh - 1);
984         POINT_SET(newtr, newbr.x, newtl.y);
985         POINT_SET(newbl, newtl.x, newbr.y);
986
987         /* is it moving or just resizing from some corner? */
988         stationary_l = oldtl.x == newtl.x;
989         stationary_r = oldtr.x == newtr.x;
990         stationary_t = oldtl.y == newtl.y;
991         stationary_b = oldbl.y == newbl.y;
992
993         /* if left edge is growing and didnt move right edge */
994         if (stationary_r && newtl.x < oldtl.x)
995             rudel = TRUE;
996         /* if right edge is growing and didnt move left edge */
997         if (stationary_l && newtr.x > oldtr.x)
998             ruder = TRUE;
999         /* if top edge is growing and didnt move bottom edge */
1000         if (stationary_b && newtl.y < oldtl.y)
1001             rudet = TRUE;
1002         /* if bottom edge is growing and didnt move top edge */
1003         if (stationary_t && newbl.y > oldbl.y)
1004             rudeb = TRUE;
1005     }
1006
1007     /* we iterate through every monitor that the window is at least partially
1008        on, to make sure it is obeying the rules on them all
1009
1010        if the window does not appear on any monitors, then use the first one
1011     */
1012     found_mon = FALSE;
1013     for (i = 0; i < screen_num_monitors; ++i) {
1014         Rect *a;
1015
1016         if (!screen_physical_area_monitor_contains(i, &desired)) {
1017             if (i < screen_num_monitors - 1 || found_mon)
1018                 continue;
1019
1020             /* the window is not inside any monitor! so just use the first
1021                one */
1022             a = screen_area(self->desktop, 0, NULL);
1023         } else {
1024             found_mon = TRUE;
1025             a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &desired);
1026         }
1027
1028         /* This makes sure windows aren't entirely outside of the screen so you
1029            can't see them at all.
1030            It makes sure 10% of the window is on the screen at least. And don't
1031            let it move itself off the top of the screen, which would hide the
1032            titlebar on you. (The user can still do this if they want too, it's
1033            only limiting the application.
1034         */
1035         if (client_normal(self)) {
1036             if (!self->strut.right && *x + fw/10 >= a->x + a->width - 1)
1037                 *x = a->x + a->width - fw/10;
1038             if (!self->strut.bottom && *y + fh/10 >= a->y + a->height - 1)
1039                 *y = a->y + a->height - fh/10;
1040             if (!self->strut.left && *x + fw*9/10 - 1 < a->x)
1041                 *x = a->x - fw*9/10;
1042             if (!self->strut.top && *y + fh*9/10 - 1 < a->y)
1043                 *y = a->y - fh*9/10;
1044         }
1045
1046         /* This here doesn't let windows even a pixel outside the
1047            struts/screen. When called from client_manage, programs placing
1048            themselves are forced completely onscreen, while things like
1049            xterm -geometry resolution-width/2 will work fine. Trying to
1050            place it completely offscreen will be handled in the above code.
1051            Sorry for this confused comment, i am tired. */
1052         if (rudel && !self->strut.left && *x < a->x) *x = a->x;
1053         if (ruder && !self->strut.right && *x + fw > a->x + a->width)
1054             *x = a->x + MAX(0, a->width - fw);
1055
1056         if (rudet && !self->strut.top && *y < a->y) *y = a->y;
1057         if (rudeb && !self->strut.bottom && *y + fh > a->y + a->height)
1058             *y = a->y + MAX(0, a->height - fh);
1059
1060         g_free(a);
1061     }
1062
1063     /* get where the client should be */
1064     frame_frame_gravity(self->frame, x, y);
1065
1066     return ox != *x || oy != *y;
1067 }
1068
1069 static void client_get_all(ObClient *self, gboolean real)
1070 {
1071     /* this is needed for the frame to set itself up */
1072     client_get_area(self);
1073
1074     /* these things can change the decor and functions of the window */
1075
1076     client_get_mwm_hints(self);
1077     /* this can change the mwmhints for special cases */
1078     client_get_type_and_transientness(self);
1079     client_get_state(self);
1080     client_update_normal_hints(self);
1081
1082     /* get the session related properties, these can change decorations
1083        from per-app settings */
1084     client_get_session_ids(self);
1085
1086     /* now we got everything that can affect the decorations */
1087     if (!real)
1088         return;
1089
1090     /* get this early so we have it for debugging */
1091     client_update_title(self);
1092
1093     client_update_protocols(self);
1094
1095     client_update_wmhints(self);
1096     /* this may have already been called from client_update_wmhints */
1097     if (!self->parents && !self->transient_for_group)
1098         client_update_transient_for(self);
1099
1100     client_get_startup_id(self);
1101     client_get_desktop(self);/* uses transient data/group/startup id if a
1102                                 desktop is not specified */
1103     client_get_shaped(self);
1104
1105     {
1106         /* a couple type-based defaults for new windows */
1107
1108         /* this makes sure that these windows appear on all desktops */
1109         if (self->type == OB_CLIENT_TYPE_DESKTOP)
1110             self->desktop = DESKTOP_ALL;
1111     }
1112
1113 #ifdef SYNC
1114     client_update_sync_request_counter(self);
1115 #endif
1116
1117     client_get_colormap(self);
1118     client_update_strut(self);
1119     client_update_icons(self);
1120     client_update_icon_geometry(self);
1121 }
1122
1123 static void client_get_startup_id(ObClient *self)
1124 {
1125     if (!(OBT_PROP_GETS(self->window, NET_STARTUP_ID, utf8,
1126                         &self->startup_id)))
1127         if (self->group)
1128             OBT_PROP_GETS(self->group->leader,
1129                           NET_STARTUP_ID, utf8, &self->startup_id);
1130 }
1131
1132 static void client_get_area(ObClient *self)
1133 {
1134     XWindowAttributes wattrib;
1135     Status ret;
1136
1137     ret = XGetWindowAttributes(obt_display, self->window, &wattrib);
1138     g_assert(ret != BadWindow);
1139
1140     RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
1141     POINT_SET(self->root_pos, wattrib.x, wattrib.y);
1142     self->border_width = wattrib.border_width;
1143
1144     ob_debug("client area: %d %d  %d %d  bw %d", wattrib.x, wattrib.y,
1145              wattrib.width, wattrib.height, wattrib.border_width);
1146 }
1147
1148 static void client_get_desktop(ObClient *self)
1149 {
1150     guint32 d = screen_num_desktops; /* an always-invalid value */
1151
1152     if (OBT_PROP_GET32(self->window, NET_WM_DESKTOP, CARDINAL, &d)) {
1153         if (d >= screen_num_desktops && d != DESKTOP_ALL)
1154             self->desktop = screen_num_desktops - 1;
1155         else
1156             self->desktop = d;
1157         ob_debug("client requested desktop 0x%x", self->desktop);
1158     } else {
1159         GSList *it;
1160         gboolean first = TRUE;
1161         guint all = screen_num_desktops; /* not a valid value */
1162
1163         /* if they are all on one desktop, then open it on the
1164            same desktop */
1165         for (it = self->parents; it; it = g_slist_next(it)) {
1166             ObClient *c = it->data;
1167
1168             if (c->desktop == DESKTOP_ALL) continue;
1169
1170             if (first) {
1171                 all = c->desktop;
1172                 first = FALSE;
1173             }
1174             else if (all != c->desktop)
1175                 all = screen_num_desktops; /* make it invalid */
1176         }
1177         if (all != screen_num_desktops) {
1178             self->desktop = all;
1179
1180             ob_debug("client desktop set from parents: 0x%x",
1181                      self->desktop);
1182         }
1183         /* try get from the startup-notification protocol */
1184         else if (sn_get_desktop(self->startup_id, &self->desktop)) {
1185             if (self->desktop >= screen_num_desktops &&
1186                 self->desktop != DESKTOP_ALL)
1187                 self->desktop = screen_num_desktops - 1;
1188             ob_debug("client desktop set from startup-notification: 0x%x",
1189                      self->desktop);
1190         }
1191         /* defaults to the current desktop */
1192         else {
1193             self->desktop = screen_desktop;
1194             ob_debug("client desktop set to the current desktop: %d",
1195                      self->desktop);
1196         }
1197     }
1198 }
1199
1200 static void client_get_state(ObClient *self)
1201 {
1202     guint32 *state;
1203     guint num;
1204
1205     if (OBT_PROP_GETA32(self->window, NET_WM_STATE, ATOM, &state, &num)) {
1206         gulong i;
1207         for (i = 0; i < num; ++i) {
1208             if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
1209                 self->modal = TRUE;
1210             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
1211                 self->shaded = TRUE;
1212             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
1213                 self->iconic = TRUE;
1214             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
1215                 self->skip_taskbar = TRUE;
1216             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
1217                 self->skip_pager = TRUE;
1218             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
1219                 self->fullscreen = TRUE;
1220             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT))
1221                 self->max_vert = TRUE;
1222             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ))
1223                 self->max_horz = TRUE;
1224             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
1225                 self->above = TRUE;
1226             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
1227                 self->below = TRUE;
1228             else if (state[i] == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
1229                 self->demands_attention = TRUE;
1230             else if (state[i] == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
1231                 self->undecorated = TRUE;
1232         }
1233
1234         g_free(state);
1235     }
1236 }
1237
1238 static void client_get_shaped(ObClient *self)
1239 {
1240     self->shaped = FALSE;
1241 #ifdef SHAPE
1242     if (obt_display_extension_shape) {
1243         gint foo;
1244         guint ufoo;
1245         gint s;
1246
1247         XShapeSelectInput(obt_display, self->window, ShapeNotifyMask);
1248
1249         XShapeQueryExtents(obt_display, self->window, &s, &foo,
1250                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1251                            &ufoo);
1252         self->shaped = !!s;
1253     }
1254 #endif
1255 }
1256
1257 void client_update_transient_for(ObClient *self)
1258 {
1259     Window t = None;
1260     ObClient *target = NULL;
1261     gboolean trangroup = FALSE;
1262
1263     if (XGetTransientForHint(obt_display, self->window, &t)) {
1264         if (t != self->window) { /* can't be transient to itself! */
1265             ObWindow *tw = window_find(t);
1266             /* if this happens then we need to check for it */
1267             g_assert(tw != CLIENT_AS_WINDOW(self));
1268             if (tw && WINDOW_IS_CLIENT(tw)) {
1269                 /* watch out for windows with a parent that is something
1270                    different, like a dockapp for example */
1271                 target = WINDOW_AS_CLIENT(tw);
1272             }
1273         }
1274
1275         /* Setting the transient_for to Root is actually illegal, however
1276            applications from time have done this to specify transient for
1277            their group */
1278         if (!target && self->group && t == obt_root(ob_screen))
1279             trangroup = TRUE;
1280     } else if (self->group && self->transient)
1281         trangroup = TRUE;
1282
1283     client_update_transient_tree(self, self->group, self->group,
1284                                  self->transient_for_group, trangroup,
1285                                  client_direct_parent(self), target);
1286     self->transient_for_group = trangroup;
1287
1288 }
1289
1290 static void client_update_transient_tree(ObClient *self,
1291                                          ObGroup *oldgroup, ObGroup *newgroup,
1292                                          gboolean oldgtran, gboolean newgtran,
1293                                          ObClient* oldparent,
1294                                          ObClient *newparent)
1295 {
1296     GSList *it, *next;
1297     ObClient *c;
1298
1299     g_assert(!oldgtran || oldgroup);
1300     g_assert(!newgtran || newgroup);
1301     g_assert((!oldgtran && !oldparent) ||
1302              (oldgtran && !oldparent) ||
1303              (!oldgtran && oldparent));
1304     g_assert((!newgtran && !newparent) ||
1305              (newgtran && !newparent) ||
1306              (!newgtran && newparent));
1307
1308     /* * *
1309       Group transient windows are not allowed to have other group
1310       transient windows as their children.
1311       * * */
1312
1313
1314     /* No change has occured */
1315     if (oldgroup == newgroup &&
1316         oldgtran == newgtran &&
1317         oldparent == newparent) return;
1318
1319     /** Remove the client from the transient tree **/
1320
1321     for (it = self->transients; it; it = next) {
1322         next = g_slist_next(it);
1323         c = it->data;
1324         self->transients = g_slist_delete_link(self->transients, it);
1325         c->parents = g_slist_remove(c->parents, self);
1326     }
1327     for (it = self->parents; it; it = next) {
1328         next = g_slist_next(it);
1329         c = it->data;
1330         self->parents = g_slist_delete_link(self->parents, it);
1331         c->transients = g_slist_remove(c->transients, self);
1332     }
1333
1334     /** Re-add the client to the transient tree **/
1335
1336     /* If we're transient for a group then we need to add ourselves to all our
1337        parents */
1338     if (newgtran) {
1339         for (it = newgroup->members; it; it = g_slist_next(it)) {
1340             c = it->data;
1341             if (c != self &&
1342                 !client_search_top_direct_parent(c)->transient_for_group &&
1343                 client_normal(c))
1344             {
1345                 c->transients = g_slist_prepend(c->transients, self);
1346                 self->parents = g_slist_prepend(self->parents, c);
1347             }
1348         }
1349     }
1350
1351     /* If we are now transient for a single window we need to add ourselves to
1352        its children
1353
1354        WARNING: Cyclical transient-ness is possible if two windows are
1355        transient for eachother.
1356     */
1357     else if (newparent &&
1358              /* don't make ourself its child if it is already our child */
1359              !client_is_direct_child(self, newparent) &&
1360              client_normal(newparent))
1361     {
1362         newparent->transients = g_slist_prepend(newparent->transients, self);
1363         self->parents = g_slist_prepend(self->parents, newparent);
1364     }
1365
1366     /* Add any group transient windows to our children. But if we're transient
1367        for the group, then other group transients are not our children.
1368
1369        WARNING: Cyclical transient-ness is possible. For e.g. if:
1370        A is transient for the group
1371        B is transient for A
1372        C is transient for B
1373        A can't be transient for C or we have a cycle
1374     */
1375     if (!newgtran && newgroup &&
1376         (!newparent ||
1377          !client_search_top_direct_parent(newparent)->transient_for_group) &&
1378         client_normal(self))
1379     {
1380         for (it = newgroup->members; it; it = g_slist_next(it)) {
1381             c = it->data;
1382             if (c != self && c->transient_for_group &&
1383                 /* Don't make it our child if it is already our parent */
1384                 !client_is_direct_child(c, self))
1385             {
1386                 self->transients = g_slist_prepend(self->transients, c);
1387                 c->parents = g_slist_prepend(c->parents, self);
1388             }
1389         }
1390     }
1391
1392     /** If we change our group transient-ness, our children change their
1393         effective group transient-ness, which affects how they relate to other
1394         group windows **/
1395
1396     for (it = self->transients; it; it = g_slist_next(it)) {
1397         c = it->data;
1398         if (!c->transient_for_group)
1399             client_update_transient_tree(c, c->group, c->group,
1400                                          c->transient_for_group,
1401                                          c->transient_for_group,
1402                                          client_direct_parent(c),
1403                                          client_direct_parent(c));
1404     }
1405 }
1406
1407 static void client_get_mwm_hints(ObClient *self)
1408 {
1409     guint num;
1410     guint32 *hints;
1411
1412     self->mwmhints.flags = 0; /* default to none */
1413
1414     if (OBT_PROP_GETA32(self->window, MOTIF_WM_HINTS, MOTIF_WM_HINTS,
1415                         &hints, &num)) {
1416         if (num >= OB_MWM_ELEMENTS) {
1417             self->mwmhints.flags = hints[0];
1418             self->mwmhints.functions = hints[1];
1419             self->mwmhints.decorations = hints[2];
1420         }
1421         g_free(hints);
1422     }
1423 }
1424
1425 void client_get_type_and_transientness(ObClient *self)
1426 {
1427     guint num, i;
1428     guint32 *val;
1429     Window t;
1430
1431     self->type = -1;
1432     self->transient = FALSE;
1433
1434     if (OBT_PROP_GETA32(self->window, NET_WM_WINDOW_TYPE, ATOM, &val, &num)) {
1435         /* use the first value that we know about in the array */
1436         for (i = 0; i < num; ++i) {
1437             if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DESKTOP))
1438                 self->type = OB_CLIENT_TYPE_DESKTOP;
1439             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DOCK))
1440                 self->type = OB_CLIENT_TYPE_DOCK;
1441             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_TOOLBAR))
1442                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1443             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_MENU))
1444                 self->type = OB_CLIENT_TYPE_MENU;
1445             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_UTILITY))
1446                 self->type = OB_CLIENT_TYPE_UTILITY;
1447             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_SPLASH))
1448                 self->type = OB_CLIENT_TYPE_SPLASH;
1449             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DIALOG))
1450                 self->type = OB_CLIENT_TYPE_DIALOG;
1451             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_NORMAL))
1452                 self->type = OB_CLIENT_TYPE_NORMAL;
1453             else if (val[i] == OBT_PROP_ATOM(KDE_NET_WM_WINDOW_TYPE_OVERRIDE))
1454             {
1455                 /* prevent this window from getting any decor or
1456                    functionality */
1457                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1458                                          OB_MWM_FLAG_DECORATIONS);
1459                 self->mwmhints.decorations = 0;
1460                 self->mwmhints.functions = 0;
1461             }
1462             if (self->type != (ObClientType) -1)
1463                 break; /* grab the first legit type */
1464         }
1465         g_free(val);
1466     }
1467
1468     if (XGetTransientForHint(obt_display, self->window, &t))
1469         self->transient = TRUE;
1470
1471     if (self->type == (ObClientType) -1) {
1472         /*the window type hint was not set, which means we either classify
1473           ourself as a normal window or a dialog, depending on if we are a
1474           transient. */
1475         if (self->transient)
1476             self->type = OB_CLIENT_TYPE_DIALOG;
1477         else
1478             self->type = OB_CLIENT_TYPE_NORMAL;
1479     }
1480
1481     /* then, based on our type, we can update our transientness.. */
1482     if (self->type == OB_CLIENT_TYPE_DIALOG ||
1483         self->type == OB_CLIENT_TYPE_TOOLBAR ||
1484         self->type == OB_CLIENT_TYPE_MENU ||
1485         self->type == OB_CLIENT_TYPE_UTILITY)
1486     {
1487         self->transient = TRUE;
1488     }
1489 }
1490
1491 void client_update_protocols(ObClient *self)
1492 {
1493     guint32 *proto;
1494     guint num_ret, i;
1495
1496     self->focus_notify = FALSE;
1497     self->delete_window = FALSE;
1498
1499     if (OBT_PROP_GETA32(self->window, WM_PROTOCOLS, ATOM, &proto, &num_ret)) {
1500         for (i = 0; i < num_ret; ++i) {
1501             if (proto[i] == OBT_PROP_ATOM(WM_DELETE_WINDOW))
1502                 /* this means we can request the window to close */
1503                 self->delete_window = TRUE;
1504             else if (proto[i] == OBT_PROP_ATOM(WM_TAKE_FOCUS))
1505                 /* if this protocol is requested, then the window will be
1506                    notified whenever we want it to receive focus */
1507                 self->focus_notify = TRUE;
1508             else if (proto[i] == OBT_PROP_ATOM(NET_WM_PING))
1509                 /* if this protocol is requested, then the window will allow
1510                    pings to determine if it is still alive */
1511                 self->ping = TRUE;
1512 #ifdef SYNC
1513             else if (proto[i] == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST))
1514                 /* if this protocol is requested, then resizing the
1515                    window will be synchronized between the frame and the
1516                    client */
1517                 self->sync_request = TRUE;
1518 #endif
1519         }
1520         g_free(proto);
1521     }
1522 }
1523
1524 #ifdef SYNC
1525 void client_update_sync_request_counter(ObClient *self)
1526 {
1527     guint32 i;
1528
1529     if (OBT_PROP_GET32(self->window, NET_WM_SYNC_REQUEST_COUNTER, CARDINAL,&i))
1530     {
1531         self->sync_counter = i;
1532     } else
1533         self->sync_counter = None;
1534 }
1535 #endif
1536
1537 static void client_get_colormap(ObClient *self)
1538 {
1539     XWindowAttributes wa;
1540
1541     if (XGetWindowAttributes(obt_display, self->window, &wa))
1542         client_update_colormap(self, wa.colormap);
1543 }
1544
1545 void client_update_colormap(ObClient *self, Colormap colormap)
1546 {
1547     if (colormap == self->colormap) return;
1548
1549     ob_debug("Setting client %s colormap: 0x%x", self->title, colormap);
1550
1551     if (client_focused(self)) {
1552         screen_install_colormap(self, FALSE); /* uninstall old one */
1553         self->colormap = colormap;
1554         screen_install_colormap(self, TRUE); /* install new one */
1555     } else
1556         self->colormap = colormap;
1557 }
1558
1559 void client_update_normal_hints(ObClient *self)
1560 {
1561     XSizeHints size;
1562     glong ret;
1563
1564     /* defaults */
1565     self->min_ratio = 0.0f;
1566     self->max_ratio = 0.0f;
1567     SIZE_SET(self->size_inc, 1, 1);
1568     SIZE_SET(self->base_size, -1, -1);
1569     SIZE_SET(self->min_size, 0, 0);
1570     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1571
1572     /* get the hints from the window */
1573     if (XGetWMNormalHints(obt_display, self->window, &size, &ret)) {
1574         /* normal windows can't request placement! har har
1575         if (!client_normal(self))
1576         */
1577         self->positioned = (size.flags & (PPosition|USPosition));
1578         self->sized = (size.flags & (PSize|USSize));
1579
1580         if (size.flags & PWinGravity)
1581             self->gravity = size.win_gravity;
1582
1583         if (size.flags & PAspect) {
1584             if (size.min_aspect.y)
1585                 self->min_ratio =
1586                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1587             if (size.max_aspect.y)
1588                 self->max_ratio =
1589                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1590         }
1591
1592         if (size.flags & PMinSize)
1593             SIZE_SET(self->min_size, size.min_width, size.min_height);
1594
1595         if (size.flags & PMaxSize)
1596             SIZE_SET(self->max_size, size.max_width, size.max_height);
1597
1598         if (size.flags & PBaseSize)
1599             SIZE_SET(self->base_size, size.base_width, size.base_height);
1600
1601         if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1602             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1603
1604         ob_debug("Normal hints: min size (%d %d) max size (%d %d)",
1605                  self->min_size.width, self->min_size.height,
1606                  self->max_size.width, self->max_size.height);
1607         ob_debug("size inc (%d %d) base size (%d %d)",
1608                  self->size_inc.width, self->size_inc.height,
1609                  self->base_size.width, self->base_size.height);
1610     }
1611     else
1612         ob_debug("Normal hints: not set");
1613 }
1614
1615 void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
1616 {
1617     /* start with everything (cept fullscreen) */
1618     self->decorations =
1619         (OB_FRAME_DECOR_TITLEBAR |
1620          OB_FRAME_DECOR_HANDLE |
1621          OB_FRAME_DECOR_GRIPS |
1622          OB_FRAME_DECOR_BORDER |
1623          OB_FRAME_DECOR_ICON |
1624          OB_FRAME_DECOR_ALLDESKTOPS |
1625          OB_FRAME_DECOR_ICONIFY |
1626          OB_FRAME_DECOR_MAXIMIZE |
1627          OB_FRAME_DECOR_SHADE |
1628          OB_FRAME_DECOR_CLOSE);
1629     self->functions =
1630         (OB_CLIENT_FUNC_RESIZE |
1631          OB_CLIENT_FUNC_MOVE |
1632          OB_CLIENT_FUNC_ICONIFY |
1633          OB_CLIENT_FUNC_MAXIMIZE |
1634          OB_CLIENT_FUNC_SHADE |
1635          OB_CLIENT_FUNC_CLOSE |
1636          OB_CLIENT_FUNC_BELOW |
1637          OB_CLIENT_FUNC_ABOVE |
1638          OB_CLIENT_FUNC_UNDECORATE);
1639
1640     if (!(self->min_size.width < self->max_size.width ||
1641           self->min_size.height < self->max_size.height))
1642         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1643
1644     switch (self->type) {
1645     case OB_CLIENT_TYPE_NORMAL:
1646         /* normal windows retain all of the possible decorations and
1647            functionality, and can be fullscreen */
1648         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1649         break;
1650
1651     case OB_CLIENT_TYPE_DIALOG:
1652         /* sometimes apps make dialog windows fullscreen for some reason (for
1653            e.g. kpdf does this..) */
1654         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1655         break;
1656
1657     case OB_CLIENT_TYPE_UTILITY:
1658         /* these windows don't have anything added or removed by default */
1659         break;
1660
1661     case OB_CLIENT_TYPE_MENU:
1662     case OB_CLIENT_TYPE_TOOLBAR:
1663         /* these windows can't iconify or maximize */
1664         self->decorations &= ~(OB_FRAME_DECOR_ICONIFY |
1665                                OB_FRAME_DECOR_MAXIMIZE);
1666         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY |
1667                              OB_CLIENT_FUNC_MAXIMIZE);
1668         break;
1669
1670     case OB_CLIENT_TYPE_SPLASH:
1671         /* these don't get get any decorations, and the only thing you can
1672            do with them is move them */
1673         self->decorations = 0;
1674         self->functions = OB_CLIENT_FUNC_MOVE;
1675         break;
1676
1677     case OB_CLIENT_TYPE_DESKTOP:
1678         /* these windows are not manipulated by the window manager */
1679         self->decorations = 0;
1680         self->functions = 0;
1681         break;
1682
1683     case OB_CLIENT_TYPE_DOCK:
1684         /* these windows are not manipulated by the window manager, but they
1685            can set below layer which has a special meaning */
1686         self->decorations = 0;
1687         self->functions = OB_CLIENT_FUNC_BELOW;
1688         break;
1689     }
1690
1691     /* Mwm Hints are applied subtractively to what has already been chosen for
1692        decor and functionality */
1693     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1694         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1695             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1696                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1697             {
1698                 /* if the mwm hints request no handle or title, then all
1699                    decorations are disabled, but keep the border if that's
1700                    specified */
1701                 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1702                     self->decorations = OB_FRAME_DECOR_BORDER;
1703                 else
1704                     self->decorations = 0;
1705             }
1706         }
1707     }
1708
1709     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1710         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1711             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1712                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1713             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1714                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1715             /* dont let mwm hints kill any buttons
1716                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1717                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1718                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1719                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1720             */
1721             /* dont let mwm hints kill the close button
1722                if (! (self->mwmhints.functions & MwmFunc_Close))
1723                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1724         }
1725     }
1726
1727     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1728         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1729     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1730         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1731     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1732         self->decorations &= ~(OB_FRAME_DECOR_GRIPS | OB_FRAME_DECOR_HANDLE);
1733
1734     /* can't maximize without moving/resizing */
1735     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1736           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1737           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1738         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1739         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1740     }
1741
1742     if (self->max_horz && self->max_vert) {
1743         /* you can't resize fully maximized windows */
1744         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1745         /* kill the handle on fully maxed windows */
1746         self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS);
1747     }
1748
1749     /* If there are no decorations to remove, don't allow the user to try
1750        toggle the state */
1751     if (self->decorations == 0)
1752         self->functions &= ~OB_CLIENT_FUNC_UNDECORATE;
1753
1754     /* finally, the user can have requested no decorations, which overrides
1755        everything (but doesnt give it a border if it doesnt have one) */
1756     if (self->undecorated)
1757         self->decorations = 0;
1758
1759     /* if we don't have a titlebar, then we cannot shade! */
1760     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1761         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1762
1763     /* now we need to check against rules for the client's current state */
1764     if (self->fullscreen) {
1765         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1766                             OB_CLIENT_FUNC_FULLSCREEN |
1767                             OB_CLIENT_FUNC_ICONIFY);
1768         self->decorations = 0;
1769     }
1770
1771     client_change_allowed_actions(self);
1772
1773     if (reconfig)
1774         /* force reconfigure to make sure decorations are updated */
1775         client_reconfigure(self, TRUE);
1776 }
1777
1778 static void client_change_allowed_actions(ObClient *self)
1779 {
1780     gulong actions[12];
1781     gint num = 0;
1782
1783     /* desktop windows are kept on all desktops */
1784     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1785         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_CHANGE_DESKTOP);
1786
1787     if (self->functions & OB_CLIENT_FUNC_SHADE)
1788         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_SHADE);
1789     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1790         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_CLOSE);
1791     if (self->functions & OB_CLIENT_FUNC_MOVE)
1792         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MOVE);
1793     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1794         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MINIMIZE);
1795     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1796         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_RESIZE);
1797     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1798         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_FULLSCREEN);
1799     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1800         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MAXIMIZE_HORZ);
1801         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MAXIMIZE_VERT);
1802     }
1803     if (self->functions & OB_CLIENT_FUNC_ABOVE)
1804         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_ABOVE);
1805     if (self->functions & OB_CLIENT_FUNC_BELOW)
1806         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_BELOW);
1807     if (self->functions & OB_CLIENT_FUNC_UNDECORATE)
1808         actions[num++] = OBT_PROP_ATOM(OB_WM_ACTION_UNDECORATE);
1809
1810     OBT_PROP_SETA32(self->window, NET_WM_ALLOWED_ACTIONS, ATOM, actions, num);
1811
1812     /* make sure the window isn't breaking any rules now
1813
1814        don't check ICONIFY here.  just cuz a window can't iconify doesnt mean
1815        it can't be iconified with its parent
1816     */
1817
1818     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1819         if (self->frame) client_shade(self, FALSE);
1820         else self->shaded = FALSE;
1821     }
1822     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1823         if (self->frame) client_fullscreen(self, FALSE);
1824         else self->fullscreen = FALSE;
1825     }
1826     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1827                                                          self->max_vert)) {
1828         if (self->frame) client_maximize(self, FALSE, 0);
1829         else self->max_vert = self->max_horz = FALSE;
1830     }
1831 }
1832
1833 void client_update_wmhints(ObClient *self)
1834 {
1835     XWMHints *hints;
1836
1837     /* assume a window takes input if it doesn't specify */
1838     self->can_focus = TRUE;
1839
1840     if ((hints = XGetWMHints(obt_display, self->window)) != NULL) {
1841         gboolean ur;
1842
1843         if (hints->flags & InputHint)
1844             self->can_focus = hints->input;
1845
1846         /* only do this when first managing the window *AND* when we aren't
1847            starting up! */
1848         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1849             if (hints->flags & StateHint)
1850                 self->iconic = hints->initial_state == IconicState;
1851
1852         ur = self->urgent;
1853         self->urgent = (hints->flags & XUrgencyHint);
1854         if (self->urgent && !ur)
1855             client_hilite(self, TRUE);
1856         else if (!self->urgent && ur && self->demands_attention)
1857             client_hilite(self, FALSE);
1858
1859         if (!(hints->flags & WindowGroupHint))
1860             hints->window_group = None;
1861
1862         /* did the group state change? */
1863         if (hints->window_group !=
1864             (self->group ? self->group->leader : None))
1865         {
1866             ObGroup *oldgroup = self->group;
1867
1868             /* remove from the old group if there was one */
1869             if (self->group) {
1870                 group_remove(self->group, self);
1871                 self->group = NULL;
1872             }
1873
1874             /* add ourself to the group if we have one */
1875             if (hints->window_group != None) {
1876                 self->group = group_add(hints->window_group, self);
1877             }
1878
1879             /* Put ourselves into the new group's transient tree, and remove
1880                ourselves from the old group's */
1881             client_update_transient_tree(self, oldgroup, self->group,
1882                                          self->transient_for_group,
1883                                          self->transient_for_group,
1884                                          client_direct_parent(self),
1885                                          client_direct_parent(self));
1886
1887             /* Lastly, being in a group, or not, can change if the window is
1888                transient for anything.
1889
1890                The logic for this is:
1891                self->transient = TRUE always if the window wants to be
1892                transient for something, even if transient_for was NULL because
1893                it wasn't in a group before.
1894
1895                If parents was NULL and oldgroup was NULL we can assume
1896                that when we add the new group, it will become transient for
1897                something.
1898
1899                If transient_for_group is TRUE, then it must have already
1900                had a group. If it is getting a new group, the above call to
1901                client_update_transient_tree has already taken care of
1902                everything ! If it is losing all group status then it will
1903                no longer be transient for anything and that needs to be
1904                updated.
1905             */
1906             if (self->transient &&
1907                 ((self->parents == NULL && oldgroup == NULL) ||
1908                  (self->transient_for_group && !self->group)))
1909                 client_update_transient_for(self);
1910         }
1911
1912         /* the WM_HINTS can contain an icon */
1913         if (hints->flags & IconPixmapHint)
1914             client_update_icons(self);
1915
1916         XFree(hints);
1917     }
1918 }
1919
1920 void client_update_title(ObClient *self)
1921 {
1922     gchar *data = NULL;
1923     gchar *visible = NULL;
1924
1925     g_free(self->title);
1926     g_free(self->original_title);
1927
1928     /* try netwm */
1929     if (!OBT_PROP_GETS(self->window, NET_WM_NAME, utf8, &data)) {
1930         /* try old x stuff */
1931         if (!(OBT_PROP_GETS(self->window, WM_NAME, locale, &data)
1932               || OBT_PROP_GETS(self->window, WM_NAME, utf8, &data))) {
1933             if (self->transient) {
1934     /*
1935     GNOME alert windows are not given titles:
1936     http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1937     */
1938                 data = g_strdup("");
1939             } else
1940                 data = g_strdup(_("Unnamed Window"));
1941         }
1942     }
1943     self->original_title = g_strdup(data);
1944
1945     if (self->client_machine) {
1946         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1947         g_free(data);
1948     } else
1949         visible = data;
1950
1951     if (self->not_responding) {
1952         data = visible;
1953         if (self->kill_level > 0)
1954             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
1955         else
1956             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
1957         g_free(data);
1958     }
1959
1960     OBT_PROP_SETS(self->window, NET_WM_VISIBLE_NAME, utf8, visible);
1961     self->title = visible;
1962
1963     if (self->frame)
1964         frame_adjust_title(self->frame);
1965
1966     /* update the icon title */
1967     data = NULL;
1968     g_free(self->icon_title);
1969
1970     /* try netwm */
1971     if (!OBT_PROP_GETS(self->window, NET_WM_ICON_NAME, utf8, &data))
1972         /* try old x stuff */
1973         if (!(OBT_PROP_GETS(self->window, WM_ICON_NAME, locale, &data) ||
1974               OBT_PROP_GETS(self->window, WM_ICON_NAME, utf8, &data)))
1975             data = g_strdup(self->title);
1976
1977     if (self->client_machine) {
1978         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1979         g_free(data);
1980     } else
1981         visible = data;
1982
1983     if (self->not_responding) {
1984         data = visible;
1985         if (self->kill_level > 0)
1986             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
1987         else
1988             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
1989         g_free(data);
1990     }
1991
1992     OBT_PROP_SETS(self->window, NET_WM_VISIBLE_ICON_NAME, utf8, visible);
1993     self->icon_title = visible;
1994 }
1995
1996 void client_update_strut(ObClient *self)
1997 {
1998     guint num;
1999     guint32 *data;
2000     gboolean got = FALSE;
2001     StrutPartial strut;
2002
2003     if (OBT_PROP_GETA32(self->window, NET_WM_STRUT_PARTIAL, CARDINAL,
2004                         &data, &num))
2005     {
2006         if (num == 12) {
2007             got = TRUE;
2008             STRUT_PARTIAL_SET(strut,
2009                               data[0], data[2], data[1], data[3],
2010                               data[4], data[5], data[8], data[9],
2011                               data[6], data[7], data[10], data[11]);
2012         }
2013         g_free(data);
2014     }
2015
2016     if (!got &&
2017         OBT_PROP_GETA32(self->window, NET_WM_STRUT, CARDINAL, &data, &num)) {
2018         if (num == 4) {
2019             Rect *a;
2020
2021             got = TRUE;
2022
2023             /* use the screen's width/height */
2024             a = screen_physical_area_all_monitors();
2025
2026             STRUT_PARTIAL_SET(strut,
2027                               data[0], data[2], data[1], data[3],
2028                               a->y, a->y + a->height - 1,
2029                               a->x, a->x + a->width - 1,
2030                               a->y, a->y + a->height - 1,
2031                               a->x, a->x + a->width - 1);
2032             g_free(a);
2033         }
2034         g_free(data);
2035     }
2036
2037     if (!got)
2038         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
2039                           0, 0, 0, 0, 0, 0, 0, 0);
2040
2041     if (!STRUT_EQUAL(strut, self->strut)) {
2042         self->strut = strut;
2043
2044         /* updating here is pointless while we're being mapped cuz we're not in
2045            the client list yet */
2046         if (self->frame)
2047             screen_update_areas();
2048     }
2049 }
2050
2051 void client_update_icons(ObClient *self)
2052 {
2053     guint num;
2054     guint32 *data;
2055     guint w, h, i, j;
2056     guint num_seen;  /* number of icons present */
2057     RrImage *img;
2058
2059     img = NULL;
2060
2061     /* grab the server, because we might be setting the window's icon and
2062        we don't want them to set it in between and we overwrite their own
2063        icon */
2064     grab_server(TRUE);
2065
2066     if (OBT_PROP_GETA32(self->window, NET_WM_ICON, CARDINAL, &data, &num)) {
2067         /* figure out how many valid icons are in here */
2068         i = 0;
2069         num_seen = 0;
2070         while (i + 2 < num) { /* +2 is to make sure there is a w and h */
2071             w = data[i++];
2072             h = data[i++];
2073             /* watch for the data being too small for the specified size,
2074                or for zero sized icons. */
2075             if (i + w*h > num || w == 0 || h == 0) break;
2076
2077             /* convert it to the right bit order for ObRender */
2078             for (j = 0; j < w*h; ++j)
2079                 data[i+j] =
2080                     (((data[i+j] >> 24) & 0xff) << RrDefaultAlphaOffset) +
2081                     (((data[i+j] >> 16) & 0xff) << RrDefaultRedOffset)   +
2082                     (((data[i+j] >>  8) & 0xff) << RrDefaultGreenOffset) +
2083                     (((data[i+j] >>  0) & 0xff) << RrDefaultBlueOffset);
2084
2085             /* is it in the cache? */
2086             img = RrImageCacheFind(ob_rr_icons, &data[i], w, h);
2087             if (img) RrImageRef(img); /* own it */
2088
2089             i += w*h;
2090             ++num_seen;
2091
2092             /* don't bother looping anymore if we already found it in the cache
2093                since we'll just use that! */
2094             if (img) break;
2095         }
2096
2097         /* if it's not in the cache yet, then add it to the cache now.
2098            we have already converted it to the correct bit order above */
2099         if (!img && num_seen > 0) {
2100             img = RrImageNew(ob_rr_icons);
2101             i = 0;
2102             for (j = 0; j < num_seen; ++j) {
2103                 w = data[i++];
2104                 h = data[i++];
2105                 RrImageAddPicture(img, &data[i], w, h);
2106                 i += w*h;
2107             }
2108         }
2109
2110         g_free(data);
2111     }
2112
2113     /* if we didn't find an image from the NET_WM_ICON stuff, then try the
2114        legacy X hints */
2115     if (!img) {
2116         XWMHints *hints;
2117
2118         if ((hints = XGetWMHints(obt_display, self->window))) {
2119             if (hints->flags & IconPixmapHint) {
2120                 gboolean xicon;
2121                 obt_display_ignore_errors(TRUE);
2122                 xicon = RrPixmapToRGBA(ob_rr_inst,
2123                                        hints->icon_pixmap,
2124                                        (hints->flags & IconMaskHint ?
2125                                         hints->icon_mask : None),
2126                                        (gint*)&w, (gint*)&h, &data);
2127                 obt_display_ignore_errors(FALSE);
2128
2129
2130                 if (xicon) {
2131                     if (w > 0 && h > 0) {
2132                         /* is this icon in the cache yet? */
2133                         img = RrImageCacheFind(ob_rr_icons, data, w, h);
2134                         if (img) RrImageRef(img); /* own it */
2135
2136                         /* if not, then add it */
2137                         if (!img) {
2138                             img = RrImageNew(ob_rr_icons);
2139                             RrImageAddPicture(img, data, w, h);
2140                         }
2141                     }
2142
2143                     g_free(data);
2144                 }
2145             }
2146             XFree(hints);
2147         }
2148     }
2149
2150     /* set the client's icons to be whatever we found */
2151     RrImageUnref(self->icon_set);
2152     self->icon_set = img;
2153
2154     /* if the client has no icon at all, then we set a default icon onto it.
2155        but, if it has parents, then one of them will have an icon already
2156     */
2157     if (!self->icon_set && !self->parents) {
2158         RrPixel32 *icon = ob_rr_theme->def_win_icon;
2159         gulong *ldata; /* use a long here to satisfy OBT_PROP_SETA32 */
2160
2161         w = ob_rr_theme->def_win_icon_w;
2162         h = ob_rr_theme->def_win_icon_h;
2163         ldata = g_new(gulong, w*h+2);
2164         ldata[0] = w;
2165         ldata[1] = h;
2166         for (i = 0; i < w*h; ++i)
2167             ldata[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2168                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2169                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2170                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2171         OBT_PROP_SETA32(self->window, NET_WM_ICON, CARDINAL, ldata, w*h+2);
2172         g_free(ldata);
2173     } else if (self->frame)
2174         /* don't draw the icon empty if we're just setting one now anyways,
2175            we'll get the property change any second */
2176         frame_adjust_icon(self->frame);
2177
2178     grab_server(FALSE);
2179 }
2180
2181 void client_update_icon_geometry(ObClient *self)
2182 {
2183     guint num;
2184     guint32 *data;
2185
2186     RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2187
2188     if (OBT_PROP_GETA32(self->window, NET_WM_ICON_GEOMETRY, CARDINAL,
2189                         &data, &num))
2190     {
2191         if (num == 4)
2192             /* don't let them set it with an area < 0 */
2193             RECT_SET(self->icon_geometry, data[0], data[1],
2194                      MAX(data[2],0), MAX(data[3],0));
2195         g_free(data);
2196     }
2197 }
2198
2199 static void client_get_session_ids(ObClient *self)
2200 {
2201     guint32 leader;
2202     gboolean got;
2203     gchar *s;
2204     gchar **ss;
2205
2206     if (!OBT_PROP_GET32(self->window, WM_CLIENT_LEADER, WINDOW, &leader))
2207         leader = None;
2208
2209     /* get the SM_CLIENT_ID */
2210     got = FALSE;
2211     if (leader)
2212         got = OBT_PROP_GETS(leader, SM_CLIENT_ID, locale, &self->sm_client_id);
2213     if (!got)
2214         OBT_PROP_GETS(self->window, SM_CLIENT_ID, locale, &self->sm_client_id);
2215
2216     /* get the WM_CLASS (name and class). make them "" if they are not
2217        provided */
2218     got = FALSE;
2219     if (leader)
2220         got = OBT_PROP_GETSS(leader, WM_CLASS, locale, &ss);
2221     if (!got)
2222         got = OBT_PROP_GETSS(self->window, WM_CLASS, locale, &ss);
2223
2224     if (got) {
2225         if (ss[0]) {
2226             self->name = g_strdup(ss[0]);
2227             if (ss[1])
2228                 self->class = g_strdup(ss[1]);
2229         }
2230         g_strfreev(ss);
2231     }
2232
2233     if (self->name == NULL) self->name = g_strdup("");
2234     if (self->class == NULL) self->class = g_strdup("");
2235
2236     /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2237     got = FALSE;
2238     if (leader)
2239         got = OBT_PROP_GETS(leader, WM_WINDOW_ROLE, locale, &s);
2240     if (!got)
2241         got = OBT_PROP_GETS(self->window, WM_WINDOW_ROLE, locale, &s);
2242
2243     if (got)
2244         self->role = s;
2245     else
2246         self->role = g_strdup("");
2247
2248     /* get the WM_COMMAND */
2249     got = FALSE;
2250
2251     if (leader)
2252         got = OBT_PROP_GETSS(leader, WM_COMMAND, locale, &ss);
2253     if (!got)
2254         got = OBT_PROP_GETSS(self->window, WM_COMMAND, locale, &ss);
2255
2256     if (got) {
2257         /* merge/mash them all together */
2258         gchar *merge = NULL;
2259         gint i;
2260
2261         for (i = 0; ss[i]; ++i) {
2262             gchar *tmp = merge;
2263             if (merge)
2264                 merge = g_strconcat(merge, ss[i], NULL);
2265             else
2266                 merge = g_strconcat(ss[i], NULL);
2267             g_free(tmp);
2268         }
2269         g_strfreev(ss);
2270
2271         self->wm_command = merge;
2272     }
2273
2274     /* get the WM_CLIENT_MACHINE */
2275     got = FALSE;
2276     if (leader)
2277         got = OBT_PROP_GETS(leader, WM_CLIENT_MACHINE, locale, &s);
2278     if (!got)
2279         got = OBT_PROP_GETS(self->window, WM_CLIENT_MACHINE, locale, &s);
2280
2281     if (got) {
2282         gchar localhost[128];
2283         guint32 pid;
2284
2285         gethostname(localhost, 127);
2286         localhost[127] = '\0';
2287         if (strcmp(localhost, s) != 0)
2288             self->client_machine = s;
2289         else
2290             g_free(s);
2291
2292         /* see if it has the PID set too (the PID requires that the
2293            WM_CLIENT_MACHINE be set) */
2294         if (OBT_PROP_GET32(self->window, NET_WM_PID, CARDINAL, &pid))
2295             self->pid = pid;
2296     }
2297 }
2298
2299 static void client_change_wm_state(ObClient *self)
2300 {
2301     gulong state[2];
2302     glong old;
2303
2304     old = self->wmstate;
2305
2306     if (self->shaded || self->iconic ||
2307         (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2308     {
2309         self->wmstate = IconicState;
2310     } else
2311         self->wmstate = NormalState;
2312
2313     if (old != self->wmstate) {
2314         OBT_PROP_MSG(ob_screen, self->window, KDE_WM_CHANGE_STATE,
2315                      self->wmstate, 1, 0, 0, 0);
2316
2317         state[0] = self->wmstate;
2318         state[1] = None;
2319         OBT_PROP_SETA32(self->window, WM_STATE, WM_STATE, state, 2);
2320     }
2321 }
2322
2323 static void client_change_state(ObClient *self)
2324 {
2325     gulong netstate[12];
2326     guint num;
2327
2328     num = 0;
2329     if (self->modal)
2330         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MODAL);
2331     if (self->shaded)
2332         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SHADED);
2333     if (self->iconic)
2334         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_HIDDEN);
2335     if (self->skip_taskbar)
2336         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR);
2337     if (self->skip_pager)
2338         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER);
2339     if (self->fullscreen)
2340         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN);
2341     if (self->max_vert)
2342         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT);
2343     if (self->max_horz)
2344         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ);
2345     if (self->above)
2346         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_ABOVE);
2347     if (self->below)
2348         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_BELOW);
2349     if (self->demands_attention)
2350         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION);
2351     if (self->undecorated)
2352         netstate[num++] = OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED);
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
2627 static void client_apply_startup_state(ObClient *self,
2628                                        gint x, gint y, gint w, gint h)
2629 {
2630     /* save the states that we are going to apply */
2631     gboolean iconic = self->iconic;
2632     gboolean fullscreen = self->fullscreen;
2633     gboolean undecorated = self->undecorated;
2634     gboolean shaded = self->shaded;
2635     gboolean demands_attention = self->demands_attention;
2636     gboolean max_horz = self->max_horz;
2637     gboolean max_vert = self->max_vert;
2638     Rect oldarea;
2639     gint l;
2640
2641     /* turn them all off in the client, so they won't affect the window
2642        being placed */
2643     self->iconic = self->fullscreen = self->undecorated = self->shaded =
2644         self->demands_attention = self->max_horz = self->max_vert = FALSE;
2645
2646     /* move the client to its placed position, or it it's already there,
2647        generate a ConfigureNotify telling the client where it is.
2648
2649        do this after adjusting the frame. otherwise it gets all weird and
2650        clients don't work right
2651
2652        do this before applying the states so they have the correct
2653        pre-max/pre-fullscreen values
2654     */
2655     client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE);
2656     ob_debug("placed window 0x%x at %d, %d with size %d x %d",
2657              self->window, x, y, w, h);
2658     /* save the area, and make it where it should be for the premax stuff */
2659     oldarea = self->area;
2660     RECT_SET(self->area, x, y, w, h);
2661
2662     /* apply the states. these are in a carefully crafted order.. */
2663
2664     if (iconic)
2665         client_iconify(self, TRUE, FALSE, TRUE);
2666     if (fullscreen)
2667         client_fullscreen(self, TRUE);
2668     if (undecorated)
2669         client_set_undecorated(self, TRUE);
2670     if (shaded)
2671         client_shade(self, TRUE);
2672     if (demands_attention)
2673         client_hilite(self, TRUE);
2674
2675     if (max_vert && max_horz)
2676         client_maximize(self, TRUE, 0);
2677     else if (max_vert)
2678         client_maximize(self, TRUE, 2);
2679     else if (max_horz)
2680         client_maximize(self, TRUE, 1);
2681
2682     /* if the window hasn't been configured yet, then do so now, in fact the
2683        x,y,w,h may _not_ be the same as the area rect, which can end up
2684        meaning that the client isn't properly moved/resized by the fullscreen
2685        function
2686        pho can cause this because it maps at size of the screen but not 0,0
2687        so openbox moves it on screen to 0,0 (thus x,y=0,0 and area.x,y don't).
2688        then fullscreen'ing makes it go to 0,0 which it thinks it already is at
2689        cuz thats where the pre-fullscreen will be. however the actual area is
2690        not, so this needs to be called even if we have fullscreened/maxed
2691     */
2692     self->area = oldarea;
2693     client_configure(self, x, y, w, h, FALSE, TRUE, FALSE);
2694
2695     /* set the desktop hint, to make sure that it always exists */
2696     OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, self->desktop);
2697
2698     /* nothing to do for the other states:
2699        skip_taskbar
2700        skip_pager
2701        modal
2702        above
2703        below
2704     */
2705 }
2706
2707 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2708 {
2709     /* these should be the current values. this is for when you're not moving,
2710        just resizing */
2711     g_assert(*x == self->area.x);
2712     g_assert(oldw == self->area.width);
2713
2714     /* horizontal */
2715     switch (self->gravity) {
2716     default:
2717     case NorthWestGravity:
2718     case WestGravity:
2719     case SouthWestGravity:
2720     case StaticGravity:
2721     case ForgetGravity:
2722         break;
2723     case NorthGravity:
2724     case CenterGravity:
2725     case SouthGravity:
2726         *x -= (neww - oldw) / 2;
2727         break;
2728     case NorthEastGravity:
2729     case EastGravity:
2730     case SouthEastGravity:
2731         *x -= neww - oldw;
2732         break;
2733     }
2734 }
2735
2736 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2737 {
2738     /* these should be the current values. this is for when you're not moving,
2739        just resizing */
2740     g_assert(*y == self->area.y);
2741     g_assert(oldh == self->area.height);
2742
2743     /* vertical */
2744     switch (self->gravity) {
2745     default:
2746     case NorthWestGravity:
2747     case NorthGravity:
2748     case NorthEastGravity:
2749     case StaticGravity:
2750     case ForgetGravity:
2751         break;
2752     case WestGravity:
2753     case CenterGravity:
2754     case EastGravity:
2755         *y -= (newh - oldh) / 2;
2756         break;
2757     case SouthWestGravity:
2758     case SouthGravity:
2759     case SouthEastGravity:
2760         *y -= newh - oldh;
2761         break;
2762     }
2763 }
2764
2765 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2766                           gint *logicalw, gint *logicalh,
2767                           gboolean user)
2768 {
2769     Rect desired = {*x, *y, *w, *h};
2770     frame_rect_to_frame(self->frame, &desired);
2771
2772     /* make the frame recalculate its dimensions n shit without changing
2773        anything visible for real, this way the constraints below can work with
2774        the updated frame dimensions. */
2775     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
2776
2777     /* gets the frame's position */
2778     frame_client_gravity(self->frame, x, y);
2779
2780     /* these positions are frame positions, not client positions */
2781
2782     /* set the size and position if fullscreen */
2783     if (self->fullscreen) {
2784         Rect *a;
2785         guint i;
2786
2787         i = screen_find_monitor(&desired);
2788         a = screen_physical_area_monitor(i);
2789
2790         *x = a->x;
2791         *y = a->y;
2792         *w = a->width;
2793         *h = a->height;
2794
2795         user = FALSE; /* ignore if the client can't be moved/resized when it
2796                          is fullscreening */
2797
2798         g_free(a);
2799     } else if (self->max_horz || self->max_vert) {
2800         Rect *a;
2801         guint i;
2802
2803         /* use all possible struts when maximizing to the full screen */
2804         i = screen_find_monitor(&desired);
2805         a = screen_area(self->desktop, i,
2806                         (self->max_horz && self->max_vert ? NULL : &desired));
2807
2808         /* set the size and position if maximized */
2809         if (self->max_horz) {
2810             *x = a->x;
2811             *w = a->width - self->frame->size.left - self->frame->size.right;
2812         }
2813         if (self->max_vert) {
2814             *y = a->y;
2815             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2816         }
2817
2818         user = FALSE; /* ignore if the client can't be moved/resized when it
2819                          is maximizing */
2820
2821         g_free(a);
2822     }
2823
2824     /* gets the client's position */
2825     frame_frame_gravity(self->frame, x, y);
2826
2827     /* work within the preferred sizes given by the window, these may have
2828        changed rather than it's requested width and height, so always run
2829        through this code */
2830     {
2831         gint basew, baseh, minw, minh;
2832         gint incw, inch;
2833         gfloat minratio, maxratio;
2834
2835         incw = self->fullscreen || self->max_horz ? 1 : self->size_inc.width;
2836         inch = self->fullscreen || self->max_vert ? 1 : self->size_inc.height;
2837         minratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2838             0 : self->min_ratio;
2839         maxratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2840             0 : self->max_ratio;
2841
2842         /* base size is substituted with min size if not specified */
2843         if (self->base_size.width >= 0 || self->base_size.height >= 0) {
2844             basew = self->base_size.width;
2845             baseh = self->base_size.height;
2846         } else {
2847             basew = self->min_size.width;
2848             baseh = self->min_size.height;
2849         }
2850         /* min size is substituted with base size if not specified */
2851         if (self->min_size.width || self->min_size.height) {
2852             minw = self->min_size.width;
2853             minh = self->min_size.height;
2854         } else {
2855             minw = self->base_size.width;
2856             minh = self->base_size.height;
2857         }
2858
2859         /* This comment is no longer true */
2860         /* if this is a user-requested resize, then check against min/max
2861            sizes */
2862
2863         /* smaller than min size or bigger than max size? */
2864         if (*w > self->max_size.width) *w = self->max_size.width;
2865         if (*w < minw) *w = minw;
2866         if (*h > self->max_size.height) *h = self->max_size.height;
2867         if (*h < minh) *h = minh;
2868
2869         *w -= basew;
2870         *h -= baseh;
2871
2872         /* keep to the increments */
2873         *w /= incw;
2874         *h /= inch;
2875
2876         /* you cannot resize to nothing */
2877         if (basew + *w < 1) *w = 1 - basew;
2878         if (baseh + *h < 1) *h = 1 - baseh;
2879
2880         /* save the logical size */
2881         *logicalw = incw > 1 ? *w : *w + basew;
2882         *logicalh = inch > 1 ? *h : *h + baseh;
2883
2884         *w *= incw;
2885         *h *= inch;
2886
2887         *w += basew;
2888         *h += baseh;
2889
2890         /* adjust the height to match the width for the aspect ratios.
2891            for this, min size is not substituted for base size ever. */
2892         *w -= self->base_size.width;
2893         *h -= self->base_size.height;
2894
2895         if (minratio)
2896             if (*h * minratio > *w) {
2897                 *h = (gint)(*w / minratio);
2898
2899                 /* you cannot resize to nothing */
2900                 if (*h < 1) {
2901                     *h = 1;
2902                     *w = (gint)(*h * minratio);
2903                 }
2904             }
2905         if (maxratio)
2906             if (*h * maxratio < *w) {
2907                 *h = (gint)(*w / maxratio);
2908
2909                 /* you cannot resize to nothing */
2910                 if (*h < 1) {
2911                     *h = 1;
2912                     *w = (gint)(*h * minratio);
2913                 }
2914             }
2915
2916         *w += self->base_size.width;
2917         *h += self->base_size.height;
2918     }
2919
2920     /* these override the above states! if you cant move you can't move! */
2921     if (user) {
2922         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2923             *x = self->area.x;
2924             *y = self->area.y;
2925         }
2926         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2927             *w = self->area.width;
2928             *h = self->area.height;
2929         }
2930     }
2931
2932     g_assert(*w > 0);
2933     g_assert(*h > 0);
2934 }
2935
2936
2937 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
2938                       gboolean user, gboolean final, gboolean force_reply)
2939 {
2940     Rect oldframe;
2941     gint oldw, oldh;
2942     gboolean send_resize_client;
2943     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
2944     gboolean fmoved, fresized;
2945     guint fdecor = self->frame->decorations;
2946     gboolean fhorz = self->frame->max_horz;
2947     gboolean fvert = self->frame->max_vert;
2948     gint logicalw, logicalh;
2949
2950     /* find the new x, y, width, and height (and logical size) */
2951     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2952
2953     /* set the logical size if things changed */
2954     if (!(w == self->area.width && h == self->area.height))
2955         SIZE_SET(self->logical_size, logicalw, logicalh);
2956
2957     /* figure out if we moved or resized or what */
2958     moved = (x != self->area.x || y != self->area.y);
2959     resized = (w != self->area.width || h != self->area.height);
2960
2961     oldw = self->area.width;
2962     oldh = self->area.height;
2963     oldframe = self->frame->area;
2964     RECT_SET(self->area, x, y, w, h);
2965
2966     /* for app-requested resizes, always resize if 'resized' is true.
2967        for user-requested ones, only resize if final is true, or when
2968        resizing in redraw mode */
2969     send_resize_client = ((!user && resized) ||
2970                           (user && (final ||
2971                                     (resized && config_resize_redraw))));
2972
2973     /* if the client is enlarging, then resize the client before the frame */
2974     if (send_resize_client && (w > oldw || h > oldh)) {
2975         XMoveResizeWindow(obt_display, self->window,
2976                           self->frame->size.left, self->frame->size.top,
2977                           MAX(w, oldw), MAX(h, oldh));
2978         frame_adjust_client_area(self->frame);
2979     }
2980
2981     /* find the frame's dimensions and move/resize it */
2982     fmoved = moved;
2983     fresized = resized;
2984
2985     /* if decorations changed, then readjust everything for the frame */
2986     if (self->decorations != fdecor ||
2987         self->max_horz != fhorz || self->max_vert != fvert)
2988     {
2989         fmoved = fresized = TRUE;
2990     }
2991
2992     /* adjust the frame */
2993     if (fmoved || fresized) {
2994         gulong ignore_start;
2995         if (!user)
2996             ignore_start = event_start_ignore_all_enters();
2997
2998         /* replay pending pointer event before move the window, in case it
2999            would change what window gets the event */
3000         mouse_replay_pointer();
3001
3002         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
3003
3004         if (!user)
3005             event_end_ignore_all_enters(ignore_start);
3006     }
3007
3008     if (!user || final) {
3009         gint oldrx = self->root_pos.x;
3010         gint oldry = self->root_pos.y;
3011         /* we have reset the client to 0 border width, so don't include
3012            it in these coords */
3013         POINT_SET(self->root_pos,
3014                   self->frame->area.x + self->frame->size.left -
3015                   self->border_width,
3016                   self->frame->area.y + self->frame->size.top -
3017                   self->border_width);
3018         if (self->root_pos.x != oldrx || self->root_pos.y != oldry)
3019             rootmoved = TRUE;
3020     }
3021
3022     /* This is kinda tricky and should not be changed.. let me explain!
3023
3024        When user = FALSE, then the request is coming from the application
3025        itself, and we are more strict about when to send a synthetic
3026        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
3027        in this case (if force_reply is true)
3028
3029        When user = TRUE, then the request is coming from "us", like when we
3030        maximize a window or something.  In this case we are more lenient.  We
3031        used to follow the same rules as above, but _Java_ Swing can't handle
3032        this. So just to appease Swing, when user = TRUE, we always send
3033        a synthetic ConfigureNotify to give the window its root coordinates.
3034     */
3035     if ((!user && !resized && (rootmoved || force_reply)) ||
3036         (user && final && rootmoved))
3037     {
3038         XEvent event;
3039
3040         event.type = ConfigureNotify;
3041         event.xconfigure.display = obt_display;
3042         event.xconfigure.event = self->window;
3043         event.xconfigure.window = self->window;
3044
3045         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d",
3046                  self->title, self->root_pos.x, self->root_pos.y, w, h);
3047
3048         /* root window real coords */
3049         event.xconfigure.x = self->root_pos.x;
3050         event.xconfigure.y = self->root_pos.y;
3051         event.xconfigure.width = w;
3052         event.xconfigure.height = h;
3053         event.xconfigure.border_width = self->border_width;
3054         event.xconfigure.above = None;
3055         event.xconfigure.override_redirect = FALSE;
3056         XSendEvent(event.xconfigure.display, event.xconfigure.window,
3057                    FALSE, StructureNotifyMask, &event);
3058     }
3059
3060     /* if the client is shrinking, then resize the frame before the client.
3061
3062        both of these resize sections may run, because the top one only resizes
3063        in the direction that is growing
3064      */
3065     if (send_resize_client && (w <= oldw || h <= oldh)) {
3066         frame_adjust_client_area(self->frame);
3067         XMoveResizeWindow(obt_display, self->window,
3068                           self->frame->size.left, self->frame->size.top, w, h);
3069     }
3070
3071     XFlush(obt_display);
3072
3073     /* if it moved between monitors, then this can affect the stacking
3074        layer of this window or others - for fullscreen windows */
3075     if (screen_find_monitor(&self->frame->area) !=
3076         screen_find_monitor(&oldframe))
3077     {
3078         client_calc_layer(self);
3079     }
3080 }
3081
3082 void client_fullscreen(ObClient *self, gboolean fs)
3083 {
3084     gint x, y, w, h;
3085
3086     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
3087         self->fullscreen == fs) return;                   /* already done */
3088
3089     self->fullscreen = fs;
3090     client_change_state(self); /* change the state hints on the client */
3091
3092     if (fs) {
3093         self->pre_fullscreen_area = self->area;
3094         /* if the window is maximized, its area isn't all that meaningful.
3095            save its premax area instead. */
3096         if (self->max_horz) {
3097             self->pre_fullscreen_area.x = self->pre_max_area.x;
3098             self->pre_fullscreen_area.width = self->pre_max_area.width;
3099         }
3100         if (self->max_vert) {
3101             self->pre_fullscreen_area.y = self->pre_max_area.y;
3102             self->pre_fullscreen_area.height = self->pre_max_area.height;
3103         }
3104
3105         /* these will help configure_full figure out where to fullscreen
3106            the window */
3107         x = self->area.x;
3108         y = self->area.y;
3109         w = self->area.width;
3110         h = self->area.height;
3111     } else {
3112         g_assert(self->pre_fullscreen_area.width > 0 &&
3113                  self->pre_fullscreen_area.height > 0);
3114
3115         x = self->pre_fullscreen_area.x;
3116         y = self->pre_fullscreen_area.y;
3117         w = self->pre_fullscreen_area.width;
3118         h = self->pre_fullscreen_area.height;
3119         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3120     }
3121
3122     ob_debug("Window %s going fullscreen (%d)",
3123              self->title, self->fullscreen);
3124
3125     client_setup_decor_and_functions(self, FALSE);
3126     client_move_resize(self, x, y, w, h);
3127
3128     /* and adjust our layer/stacking. do this after resizing the window,
3129        and applying decorations, because windows which fill the screen are
3130        considered "fullscreen" and it affects their layer */
3131     client_calc_layer(self);
3132
3133     if (fs) {
3134         /* try focus us when we go into fullscreen mode */
3135         client_focus(self);
3136     }
3137 }
3138
3139 static void client_iconify_recursive(ObClient *self,
3140                                      gboolean iconic, gboolean curdesk,
3141                                      gboolean hide_animation)
3142 {
3143     GSList *it;
3144     gboolean changed = FALSE;
3145
3146     if (self->iconic != iconic) {
3147         ob_debug("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
3148                  self->window);
3149
3150         if (iconic) {
3151             /* don't let non-normal windows iconify along with their parents
3152                or whatever */
3153             if (client_normal(self)) {
3154                 self->iconic = iconic;
3155
3156                 /* update the focus lists.. iconic windows go to the bottom of
3157                    the list */
3158                 focus_order_to_bottom(self);
3159
3160                 changed = TRUE;
3161             }
3162         } else {
3163             self->iconic = iconic;
3164
3165             if (curdesk && self->desktop != screen_desktop &&
3166                 self->desktop != DESKTOP_ALL)
3167                 client_set_desktop(self, screen_desktop, FALSE, FALSE);
3168
3169             /* this puts it after the current focused window */
3170             focus_order_remove(self);
3171             focus_order_add_new(self);
3172
3173             changed = TRUE;
3174         }
3175     }
3176
3177     if (changed) {
3178         client_change_state(self);
3179         if (config_animate_iconify && !hide_animation)
3180             frame_begin_iconify_animation(self->frame, iconic);
3181         /* do this after starting the animation so it doesn't flash */
3182         client_showhide(self);
3183
3184         hooks_queue((iconic ? OB_HOOK_WIN_ICONIC : OB_HOOK_WIN_UNICONIC),
3185                     self);
3186     }
3187
3188     /* iconify all direct transients, and deiconify all transients
3189        (non-direct too) */
3190     for (it = self->transients; it; it = g_slist_next(it))
3191         if (it->data != self)
3192             if (client_is_direct_child(self, it->data) || !iconic)
3193                 client_iconify_recursive(it->data, iconic, curdesk,
3194                                          hide_animation);
3195 }
3196
3197 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3198                     gboolean hide_animation)
3199 {
3200     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3201         /* move up the transient chain as far as possible first */
3202         self = client_search_top_direct_parent(self);
3203         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3204     }
3205 }
3206
3207 void client_maximize(ObClient *self, gboolean max, gint dir)
3208 {
3209     gint x, y, w, h;
3210
3211     g_assert(dir == 0 || dir == 1 || dir == 2);
3212     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && max) return;/* can't */
3213
3214     /* check if already done */
3215     if (max) {
3216         if (dir == 0 && self->max_horz && self->max_vert) return;
3217         if (dir == 1 && self->max_horz) return;
3218         if (dir == 2 && self->max_vert) return;
3219     } else {
3220         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3221         if (dir == 1 && !self->max_horz) return;
3222         if (dir == 2 && !self->max_vert) return;
3223     }
3224
3225     /* these will help configure_full figure out which screen to fill with
3226        the window */
3227     x = self->area.x;
3228     y = self->area.y;
3229     w = self->area.width;
3230     h = self->area.height;
3231
3232     if (max) {
3233         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3234             RECT_SET(self->pre_max_area,
3235                      self->area.x, self->pre_max_area.y,
3236                      self->area.width, self->pre_max_area.height);
3237         }
3238         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3239             RECT_SET(self->pre_max_area,
3240                      self->pre_max_area.x, self->area.y,
3241                      self->pre_max_area.width, self->area.height);
3242         }
3243     } else {
3244         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3245             g_assert(self->pre_max_area.width > 0);
3246
3247             x = self->pre_max_area.x;
3248             w = self->pre_max_area.width;
3249
3250             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3251                      0, self->pre_max_area.height);
3252         }
3253         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3254             g_assert(self->pre_max_area.height > 0);
3255
3256             y = self->pre_max_area.y;
3257             h = self->pre_max_area.height;
3258
3259             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3260                      self->pre_max_area.width, 0);
3261         }
3262     }
3263
3264     if (dir == 0 || dir == 1) /* horz */
3265         self->max_horz = max;
3266     if (dir == 0 || dir == 2) /* vert */
3267         self->max_vert = max;
3268
3269     client_change_state(self); /* change the state hints on the client */
3270
3271     client_setup_decor_and_functions(self, FALSE);
3272     client_move_resize(self, x, y, w, h);
3273
3274     hooks_queue((max ? OB_HOOK_WIN_MAX : OB_HOOK_WIN_UNMAX), self);
3275 }
3276
3277 void client_shade(ObClient *self, gboolean shade)
3278 {
3279     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3280          shade) ||                         /* can't shade */
3281         self->shaded == shade) return;     /* already done */
3282
3283     self->shaded = shade;
3284     client_change_state(self);
3285     client_change_wm_state(self); /* the window is being hidden/shown */
3286     /* resize the frame to just the titlebar */
3287     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
3288
3289     hooks_queue((shade ? OB_HOOK_WIN_SHADE : OB_HOOK_WIN_UNSHADE), self);
3290 }
3291
3292 static void client_ping_event(ObClient *self, gboolean dead)
3293 {
3294     if (self->not_responding != dead) {
3295         self->not_responding = dead;
3296         client_update_title(self);
3297
3298         if (dead)
3299             /* the client isn't responding, so ask to kill it */
3300             client_prompt_kill(self);
3301         else {
3302             /* it came back to life ! */
3303
3304             if (self->kill_prompt) {
3305                 prompt_unref(self->kill_prompt);
3306                 self->kill_prompt = NULL;
3307             }
3308
3309             self->kill_level = 0;
3310         }
3311     }
3312 }
3313
3314 void client_close(ObClient *self)
3315 {
3316     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3317
3318     /* if closing an internal obprompt, that is just cancelling it */
3319     if (self->prompt) {
3320         prompt_cancel(self->prompt);
3321         return;
3322     }
3323
3324     /* in the case that the client provides no means to requesting that it
3325        close, we just kill it */
3326     if (!self->delete_window)
3327         /* don't use client_kill(), we should only kill based on PID in
3328            response to a lack of PING replies */
3329         XKillClient(obt_display, self->window);
3330     else {
3331         /* request the client to close with WM_DELETE_WINDOW */
3332         OBT_PROP_MSG_TO(self->window, self->window, WM_PROTOCOLS,
3333                         OBT_PROP_ATOM(WM_DELETE_WINDOW), event_curtime,
3334                         0, 0, 0, NoEventMask);
3335
3336         /* we're trying to close the window, so see if it is responding. if it
3337            is not, then we will let them kill the window */
3338         if (self->ping)
3339             ping_start(self, client_ping_event);
3340
3341         /* if we already know the window isn't responding (maybe they clicked
3342            no in the kill dialog but it hasn't come back to life), then show
3343            the kill dialog */
3344         if (self->not_responding)
3345             client_prompt_kill(self);
3346     }
3347 }
3348
3349 #define OB_KILL_RESULT_NO 0
3350 #define OB_KILL_RESULT_YES 1
3351
3352 static gboolean client_kill_requested(ObPrompt *p, gint result, gpointer data)
3353 {
3354     ObClient *self = data;
3355
3356     if (result == OB_KILL_RESULT_YES)
3357         client_kill(self);
3358     return TRUE; /* call the cleanup func */
3359 }
3360
3361 static void client_kill_cleanup(ObPrompt *p, gpointer data)
3362 {
3363     ObClient *self = data;
3364
3365     g_assert(p == self->kill_prompt);
3366
3367     prompt_unref(self->kill_prompt);
3368     self->kill_prompt = NULL;
3369 }
3370
3371 static void client_prompt_kill(ObClient *self)
3372 {
3373     /* check if we're already prompting */
3374     if (!self->kill_prompt) {
3375         ObPromptAnswer answers[] = {
3376             { 0, OB_KILL_RESULT_NO },
3377             { 0, OB_KILL_RESULT_YES }
3378         };
3379         gchar *m;
3380         const gchar *y, *title;
3381
3382         title = self->original_title;
3383         if (title[0] == '\0') {
3384             /* empty string, so use its parent */
3385             ObClient *p = client_search_top_direct_parent(self);
3386             if (p) title = p->original_title;
3387         }
3388
3389         if (client_on_localhost(self)) {
3390             const gchar *sig;
3391
3392             if (self->kill_level == 0)
3393                 sig = "terminate";
3394             else
3395                 sig = "kill";
3396
3397             m = g_strdup_printf
3398                 (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"),
3399                  title, sig);
3400             y = _("End Process");
3401         }
3402         else {
3403             m = g_strdup_printf
3404                 (_("The window \"%s\" does not seem to be responding.  Do you want to disconnect it from the X server?"),
3405                  title);
3406             y = _("Disconnect");
3407         }
3408         /* set the dialog buttons' text */
3409         answers[0].text = _("Cancel");  /* "no" */
3410         answers[1].text = y;            /* "yes" */
3411
3412         self->kill_prompt = prompt_new(m, NULL, answers,
3413                                        sizeof(answers)/sizeof(answers[0]),
3414                                        OB_KILL_RESULT_NO, /* default = no */
3415                                        OB_KILL_RESULT_NO, /* cancel = no */
3416                                        client_kill_requested,
3417                                        client_kill_cleanup,
3418                                        self);
3419         g_free(m);
3420     }
3421
3422     prompt_show(self->kill_prompt, self, TRUE);
3423 }
3424
3425 void client_kill(ObClient *self)
3426 {
3427     /* don't kill our own windows */
3428     if (self->prompt) return;
3429
3430     if (client_on_localhost(self) && self->pid) {
3431         /* running on the local host */
3432         if (self->kill_level == 0) {
3433             ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
3434                      self->window, self->pid);
3435             kill(self->pid, SIGTERM);
3436             ++self->kill_level;
3437
3438             /* show that we're trying to kill it */
3439             client_update_title(self);
3440         }
3441         else {
3442             ob_debug("killing window 0x%x with pid %lu, with SIGKILL",
3443                      self->window, self->pid);
3444             kill(self->pid, SIGKILL); /* kill -9 */
3445         }
3446     }
3447     else {
3448         /* running on a remote host */
3449         XKillClient(obt_display, self->window);
3450     }
3451 }
3452
3453 void client_hilite(ObClient *self, gboolean hilite)
3454 {
3455     if (self->demands_attention == hilite)
3456         return; /* no change */
3457
3458     /* don't allow focused windows to hilite */
3459     self->demands_attention = hilite && !client_focused(self);
3460     if (self->frame != NULL) { /* if we're mapping, just set the state */
3461         if (self->demands_attention)
3462             frame_flash_start(self->frame);
3463         else
3464             frame_flash_stop(self->frame);
3465         client_change_state(self);
3466     }
3467 }
3468
3469 static void client_set_desktop_recursive(ObClient *self,
3470                                          guint target,
3471                                          gboolean donthide,
3472                                          gboolean dontraise)
3473 {
3474     guint old;
3475     GSList *it;
3476
3477     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3478
3479         ob_debug("Setting desktop %u", target+1);
3480
3481         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3482
3483         old = self->desktop;
3484         self->desktop = target;
3485         OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, target);
3486         /* the frame can display the current desktop state */
3487         frame_adjust_state(self->frame);
3488         /* 'move' the window to the new desktop */
3489         if (!donthide)
3490             client_hide(self);
3491         client_show(self);
3492         /* raise if it was not already on the desktop */
3493         if (old != DESKTOP_ALL && !dontraise)
3494             stacking_raise(CLIENT_AS_WINDOW(self));
3495         if (STRUT_EXISTS(self->strut))
3496             screen_update_areas();
3497         else
3498             /* the new desktop's geometry may be different, so we may need to
3499                resize, for example if we are maximized */
3500             client_reconfigure(self, FALSE);
3501
3502         if (old != self->desktop)
3503             hooks_queue(OB_HOOK_WIN_DESK_CHANGE, self);
3504     }
3505
3506     /* move all transients */
3507     for (it = self->transients; it; it = g_slist_next(it))
3508         if (it->data != self)
3509             if (client_is_direct_child(self, it->data))
3510                 client_set_desktop_recursive(it->data, target,
3511                                              donthide, dontraise);
3512 }
3513
3514 void client_set_desktop(ObClient *self, guint target,
3515                         gboolean donthide, gboolean dontraise)
3516 {
3517     self = client_search_top_direct_parent(self);
3518     client_set_desktop_recursive(self, target, donthide, dontraise);
3519 }
3520
3521 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3522 {
3523     while (child != parent && (child = client_direct_parent(child)));
3524     return child == parent;
3525 }
3526
3527 ObClient *client_search_modal_child(ObClient *self)
3528 {
3529     GSList *it;
3530     ObClient *ret;
3531
3532     for (it = self->transients; it; it = g_slist_next(it)) {
3533         ObClient *c = it->data;