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