dummy child of all mikabox branches at 3.4.7
[mikachu/openbox.git] /
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 it's 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 interaction 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)
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. And 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             else if (state[i] == OBT_PROP_ATOM(OB_WM_STATE_LOCKED))
1209                 self->locked = TRUE;
1210         }
1211
1212         g_free(state);
1213     }
1214 }
1215
1216 static void client_get_shaped(ObClient *self)
1217 {
1218     self->shaped = FALSE;
1219 #ifdef SHAPE
1220     if (obt_display_extension_shape) {
1221         gint foo;
1222         guint ufoo;
1223         gint s;
1224
1225         XShapeSelectInput(obt_display, self->window, ShapeNotifyMask);
1226
1227         XShapeQueryExtents(obt_display, self->window, &s, &foo,
1228                            &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1229                            &ufoo);
1230         self->shaped = !!s;
1231     }
1232 #endif
1233 }
1234
1235 void client_update_transient_for(ObClient *self)
1236 {
1237     Window t = None;
1238     ObClient *target = NULL;
1239     gboolean trangroup = FALSE;
1240
1241     if (XGetTransientForHint(obt_display, self->window, &t)) {
1242         if (t != self->window) { /* can't be transient to itself! */
1243             ObWindow *tw = window_find(t);
1244             /* if this happens then we need to check for it */
1245             g_assert(tw != CLIENT_AS_WINDOW(self));
1246             if (tw && WINDOW_IS_CLIENT(tw)) {
1247                 /* watch out for windows with a parent that is something
1248                    different, like a dockapp for example */
1249                 target = WINDOW_AS_CLIENT(tw);
1250             }
1251         }
1252
1253         /* Setting the transient_for to Root is actually illegal, however
1254            applications from time have done this to specify transient for
1255            their group */
1256         if (!target && self->group && t == obt_root(ob_screen))
1257             trangroup = TRUE;
1258     } else if (self->group && self->transient)
1259         trangroup = TRUE;
1260
1261     client_update_transient_tree(self, self->group, self->group,
1262                                  self->transient_for_group, trangroup,
1263                                  client_direct_parent(self), target);
1264     self->transient_for_group = trangroup;
1265
1266 }
1267
1268 static void client_update_transient_tree(ObClient *self,
1269                                          ObGroup *oldgroup, ObGroup *newgroup,
1270                                          gboolean oldgtran, gboolean newgtran,
1271                                          ObClient* oldparent,
1272                                          ObClient *newparent)
1273 {
1274     GSList *it, *next;
1275     ObClient *c;
1276
1277     g_assert(!oldgtran || oldgroup);
1278     g_assert(!newgtran || newgroup);
1279     g_assert((!oldgtran && !oldparent) ||
1280              (oldgtran && !oldparent) ||
1281              (!oldgtran && oldparent));
1282     g_assert((!newgtran && !newparent) ||
1283              (newgtran && !newparent) ||
1284              (!newgtran && newparent));
1285
1286     /* * *
1287       Group transient windows are not allowed to have other group
1288       transient windows as their children.
1289       * * */
1290
1291
1292     /* No change has occured */
1293     if (oldgroup == newgroup &&
1294         oldgtran == newgtran &&
1295         oldparent == newparent) return;
1296
1297     /** Remove the client from the transient tree **/
1298
1299     for (it = self->transients; it; it = next) {
1300         next = g_slist_next(it);
1301         c = it->data;
1302         self->transients = g_slist_delete_link(self->transients, it);
1303         c->parents = g_slist_remove(c->parents, self);
1304     }
1305     for (it = self->parents; it; it = next) {
1306         next = g_slist_next(it);
1307         c = it->data;
1308         self->parents = g_slist_delete_link(self->parents, it);
1309         c->transients = g_slist_remove(c->transients, self);
1310     }
1311
1312     /** Re-add the client to the transient tree **/
1313
1314     /* If we're transient for a group then we need to add ourselves to all our
1315        parents */
1316     if (newgtran) {
1317         for (it = newgroup->members; it; it = g_slist_next(it)) {
1318             c = it->data;
1319             if (c != self &&
1320                 !client_search_top_direct_parent(c)->transient_for_group &&
1321                 client_normal(c))
1322             {
1323                 c->transients = g_slist_prepend(c->transients, self);
1324                 self->parents = g_slist_prepend(self->parents, c);
1325             }
1326         }
1327     }
1328
1329     /* If we are now transient for a single window we need to add ourselves to
1330        its children
1331
1332        WARNING: Cyclical transient-ness is possible if two windows are
1333        transient for eachother.
1334     */
1335     else if (newparent &&
1336              /* don't make ourself its child if it is already our child */
1337              !client_is_direct_child(self, newparent) &&
1338              client_normal(newparent))
1339     {
1340         newparent->transients = g_slist_prepend(newparent->transients, self);
1341         self->parents = g_slist_prepend(self->parents, newparent);
1342     }
1343
1344     /* Add any group transient windows to our children. But if we're transient
1345        for the group, then other group transients are not our children.
1346
1347        WARNING: Cyclical transient-ness is possible. For e.g. if:
1348        A is transient for the group
1349        B is transient for A
1350        C is transient for B
1351        A can't be transient for C or we have a cycle
1352     */
1353     if (!newgtran && newgroup &&
1354         (!newparent ||
1355          !client_search_top_direct_parent(newparent)->transient_for_group) &&
1356         client_normal(self))
1357     {
1358         for (it = newgroup->members; it; it = g_slist_next(it)) {
1359             c = it->data;
1360             if (c != self && c->transient_for_group &&
1361                 /* Don't make it our child if it is already our parent */
1362                 !client_is_direct_child(c, self))
1363             {
1364                 self->transients = g_slist_prepend(self->transients, c);
1365                 c->parents = g_slist_prepend(c->parents, self);
1366             }
1367         }
1368     }
1369
1370     /** If we change our group transient-ness, our children change their
1371         effective group transient-ness, which affects how they relate to other
1372         group windows **/
1373
1374     for (it = self->transients; it; it = g_slist_next(it)) {
1375         c = it->data;
1376         if (!c->transient_for_group)
1377             client_update_transient_tree(c, c->group, c->group,
1378                                          c->transient_for_group,
1379                                          c->transient_for_group,
1380                                          client_direct_parent(c),
1381                                          client_direct_parent(c));
1382     }
1383 }
1384
1385 static void client_get_mwm_hints(ObClient *self)
1386 {
1387     guint num;
1388     guint32 *hints;
1389
1390     self->mwmhints.flags = 0; /* default to none */
1391
1392     if (OBT_PROP_GETA32(self->window, MOTIF_WM_HINTS, MOTIF_WM_HINTS,
1393                         &hints, &num)) {
1394         if (num >= OB_MWM_ELEMENTS) {
1395             self->mwmhints.flags = hints[0];
1396             self->mwmhints.functions = hints[1];
1397             self->mwmhints.decorations = hints[2];
1398         }
1399         g_free(hints);
1400     }
1401 }
1402
1403 void client_get_type_and_transientness(ObClient *self)
1404 {
1405     guint num, i;
1406     guint32 *val;
1407     Window t;
1408
1409     self->type = -1;
1410     self->transient = FALSE;
1411
1412     if (OBT_PROP_GETA32(self->window, NET_WM_WINDOW_TYPE, ATOM, &val, &num)) {
1413         /* use the first value that we know about in the array */
1414         for (i = 0; i < num; ++i) {
1415             if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DESKTOP))
1416                 self->type = OB_CLIENT_TYPE_DESKTOP;
1417             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DOCK))
1418                 self->type = OB_CLIENT_TYPE_DOCK;
1419             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_TOOLBAR))
1420                 self->type = OB_CLIENT_TYPE_TOOLBAR;
1421             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_MENU))
1422                 self->type = OB_CLIENT_TYPE_MENU;
1423             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_UTILITY))
1424                 self->type = OB_CLIENT_TYPE_UTILITY;
1425             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_SPLASH))
1426                 self->type = OB_CLIENT_TYPE_SPLASH;
1427             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_DIALOG))
1428                 self->type = OB_CLIENT_TYPE_DIALOG;
1429             else if (val[i] == OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_NORMAL))
1430                 self->type = OB_CLIENT_TYPE_NORMAL;
1431             else if (val[i] == OBT_PROP_ATOM(KDE_NET_WM_WINDOW_TYPE_OVERRIDE))
1432             {
1433                 /* prevent this window from getting any decor or
1434                    functionality */
1435                 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1436                                          OB_MWM_FLAG_DECORATIONS);
1437                 self->mwmhints.decorations = 0;
1438                 self->mwmhints.functions = 0;
1439             }
1440             if (self->type != (ObClientType) -1)
1441                 break; /* grab the first legit type */
1442         }
1443         g_free(val);
1444     }
1445
1446     if (XGetTransientForHint(obt_display, self->window, &t))
1447         self->transient = TRUE;
1448
1449     if (self->type == (ObClientType) -1) {
1450         /*the window type hint was not set, which means we either classify
1451           ourself as a normal window or a dialog, depending on if we are a
1452           transient. */
1453         if (self->transient)
1454             self->type = OB_CLIENT_TYPE_DIALOG;
1455         else
1456             self->type = OB_CLIENT_TYPE_NORMAL;
1457     }
1458
1459     /* then, based on our type, we can update our transientness.. */
1460     if (self->type == OB_CLIENT_TYPE_DIALOG ||
1461         self->type == OB_CLIENT_TYPE_TOOLBAR ||
1462         self->type == OB_CLIENT_TYPE_MENU ||
1463         self->type == OB_CLIENT_TYPE_UTILITY)
1464     {
1465         self->transient = TRUE;
1466     }
1467 }
1468
1469 void client_update_protocols(ObClient *self)
1470 {
1471     guint32 *proto;
1472     guint num_ret, i;
1473
1474     self->focus_notify = FALSE;
1475     self->delete_window = FALSE;
1476
1477     if (OBT_PROP_GETA32(self->window, WM_PROTOCOLS, ATOM, &proto, &num_ret)) {
1478         for (i = 0; i < num_ret; ++i) {
1479             if (proto[i] == OBT_PROP_ATOM(WM_DELETE_WINDOW))
1480                 /* this means we can request the window to close */
1481                 self->delete_window = TRUE;
1482             else if (proto[i] == OBT_PROP_ATOM(WM_TAKE_FOCUS))
1483                 /* if this protocol is requested, then the window will be
1484                    notified whenever we want it to receive focus */
1485                 self->focus_notify = TRUE;
1486             else if (proto[i] == OBT_PROP_ATOM(NET_WM_PING))
1487                 /* if this protocol is requested, then the window will allow
1488                    pings to determine if it is still alive */
1489                 self->ping = TRUE;
1490 #ifdef SYNC
1491             else if (proto[i] == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST))
1492                 /* if this protocol is requested, then resizing the
1493                    window will be synchronized between the frame and the
1494                    client */
1495                 self->sync_request = TRUE;
1496 #endif
1497         }
1498         g_free(proto);
1499     }
1500 }
1501
1502 #ifdef SYNC
1503 void client_update_sync_request_counter(ObClient *self)
1504 {
1505     guint32 i;
1506
1507     if (OBT_PROP_GET32(self->window, NET_WM_SYNC_REQUEST_COUNTER, CARDINAL,&i))
1508     {
1509         self->sync_counter = i;
1510     } else
1511         self->sync_counter = None;
1512 }
1513 #endif
1514
1515 static void client_get_colormap(ObClient *self)
1516 {
1517     XWindowAttributes wa;
1518
1519     if (XGetWindowAttributes(obt_display, self->window, &wa))
1520         client_update_colormap(self, wa.colormap);
1521 }
1522
1523 void client_update_colormap(ObClient *self, Colormap colormap)
1524 {
1525     if (colormap == self->colormap) return;
1526
1527     ob_debug("Setting client %s colormap: 0x%x", self->title, colormap);
1528
1529     if (client_focused(self)) {
1530         screen_install_colormap(self, FALSE); /* uninstall old one */
1531         self->colormap = colormap;
1532         screen_install_colormap(self, TRUE); /* install new one */
1533     } else
1534         self->colormap = colormap;
1535 }
1536
1537 void client_update_normal_hints(ObClient *self)
1538 {
1539     XSizeHints size;
1540     glong ret;
1541
1542     /* defaults */
1543     self->min_ratio = 0.0f;
1544     self->max_ratio = 0.0f;
1545     SIZE_SET(self->size_inc, 1, 1);
1546     SIZE_SET(self->base_size, -1, -1);
1547     SIZE_SET(self->min_size, 0, 0);
1548     SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1549
1550     /* get the hints from the window */
1551     if (XGetWMNormalHints(obt_display, self->window, &size, &ret)) {
1552         /* normal windows can't request placement! har har
1553         if (!client_normal(self))
1554         */
1555         self->positioned = (size.flags & (PPosition|USPosition));
1556         self->sized = (size.flags & (PSize|USSize));
1557
1558         if (size.flags & PWinGravity)
1559             self->gravity = size.win_gravity;
1560
1561         if (size.flags & PAspect) {
1562             if (size.min_aspect.y)
1563                 self->min_ratio =
1564                     (gfloat) size.min_aspect.x / size.min_aspect.y;
1565             if (size.max_aspect.y)
1566                 self->max_ratio =
1567                     (gfloat) size.max_aspect.x / size.max_aspect.y;
1568         }
1569
1570         if (size.flags & PMinSize)
1571             SIZE_SET(self->min_size, size.min_width, size.min_height);
1572
1573         if (size.flags & PMaxSize)
1574             SIZE_SET(self->max_size, size.max_width, size.max_height);
1575
1576         if (size.flags & PBaseSize)
1577             SIZE_SET(self->base_size, size.base_width, size.base_height);
1578
1579         if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1580             SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1581
1582         ob_debug("Normal hints: min size (%d %d) max size (%d %d)",
1583                  self->min_size.width, self->min_size.height,
1584                  self->max_size.width, self->max_size.height);
1585         ob_debug("size inc (%d %d) base size (%d %d)",
1586                  self->size_inc.width, self->size_inc.height,
1587                  self->base_size.width, self->base_size.height);
1588     }
1589     else
1590         ob_debug("Normal hints: not set");
1591 }
1592
1593 void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
1594 {
1595     /* start with everything (cept fullscreen) */
1596     self->decorations =
1597         (OB_FRAME_DECOR_TITLEBAR |
1598          OB_FRAME_DECOR_HANDLE |
1599          OB_FRAME_DECOR_GRIPS |
1600          OB_FRAME_DECOR_BORDER |
1601          OB_FRAME_DECOR_ICON |
1602          OB_FRAME_DECOR_ALLDESKTOPS |
1603          OB_FRAME_DECOR_ICONIFY |
1604          OB_FRAME_DECOR_MAXIMIZE |
1605          OB_FRAME_DECOR_SHADE |
1606          OB_FRAME_DECOR_CLOSE);
1607     self->functions =
1608         (OB_CLIENT_FUNC_RESIZE |
1609          OB_CLIENT_FUNC_MOVE |
1610          OB_CLIENT_FUNC_ICONIFY |
1611          OB_CLIENT_FUNC_MAXIMIZE |
1612          OB_CLIENT_FUNC_SHADE |
1613          OB_CLIENT_FUNC_CLOSE |
1614          OB_CLIENT_FUNC_BELOW |
1615          OB_CLIENT_FUNC_ABOVE |
1616          OB_CLIENT_FUNC_UNDECORATE);
1617
1618     if (!(self->min_size.width < self->max_size.width ||
1619           self->min_size.height < self->max_size.height))
1620         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1621
1622     switch (self->type) {
1623     case OB_CLIENT_TYPE_NORMAL:
1624         /* normal windows retain all of the possible decorations and
1625            functionality, and can be fullscreen */
1626         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1627         break;
1628
1629     case OB_CLIENT_TYPE_DIALOG:
1630         /* sometimes apps make dialog windows fullscreen for some reason (for
1631            e.g. kpdf does this..) */
1632         self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1633         break;
1634
1635     case OB_CLIENT_TYPE_UTILITY:
1636         /* these windows don't have anything added or removed by default */
1637         break;
1638
1639     case OB_CLIENT_TYPE_MENU:
1640     case OB_CLIENT_TYPE_TOOLBAR:
1641         /* these windows can't iconify or maximize */
1642         self->decorations &= ~(OB_FRAME_DECOR_ICONIFY |
1643                                OB_FRAME_DECOR_MAXIMIZE);
1644         self->functions &= ~(OB_CLIENT_FUNC_ICONIFY |
1645                              OB_CLIENT_FUNC_MAXIMIZE);
1646         break;
1647
1648     case OB_CLIENT_TYPE_SPLASH:
1649         /* these don't get get any decorations, and the only thing you can
1650            do with them is move them */
1651         self->decorations = 0;
1652         self->functions = OB_CLIENT_FUNC_MOVE;
1653         break;
1654
1655     case OB_CLIENT_TYPE_DESKTOP:
1656         /* these windows are not manipulated by the window manager */
1657         self->decorations = 0;
1658         self->functions = 0;
1659         break;
1660
1661     case OB_CLIENT_TYPE_DOCK:
1662         /* these windows are not manipulated by the window manager, but they
1663            can set below layer which has a special meaning */
1664         self->decorations = 0;
1665         self->functions = OB_CLIENT_FUNC_BELOW;
1666         break;
1667     }
1668
1669     /* Mwm Hints are applied subtractively to what has already been chosen for
1670        decor and functionality */
1671     if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1672         if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1673             if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1674                    (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1675             {
1676                 /* if the mwm hints request no handle or title, then all
1677                    decorations are disabled, but keep the border if that's
1678                    specified */
1679                 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1680                     self->decorations = OB_FRAME_DECOR_BORDER;
1681                 else
1682                     self->decorations = 0;
1683             }
1684         }
1685     }
1686
1687     if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1688         if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1689             if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1690                 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1691             if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1692                 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1693             /* dont let mwm hints kill any buttons
1694                if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1695                self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1696                if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1697                self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1698             */
1699             /* dont let mwm hints kill the close button
1700                if (! (self->mwmhints.functions & MwmFunc_Close))
1701                self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1702         }
1703     }
1704
1705     if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1706         self->decorations &= ~OB_FRAME_DECOR_SHADE;
1707     if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1708         self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1709     if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1710         self->decorations &= ~(OB_FRAME_DECOR_GRIPS | OB_FRAME_DECOR_HANDLE);
1711
1712     /* can't maximize without moving/resizing */
1713     if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1714           (self->functions & OB_CLIENT_FUNC_MOVE) &&
1715           (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1716         self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1717         self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1718     }
1719
1720     if (self->max_horz && self->max_vert) {
1721         /* you can't resize fully maximized windows */
1722         self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1723         /* kill the handle on fully maxed windows */
1724         self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS);
1725     }
1726
1727     /* If there are no decorations to remove, don't allow the user to try
1728        toggle the state */
1729     if (self->decorations == 0)
1730         self->functions &= ~OB_CLIENT_FUNC_UNDECORATE;
1731
1732     /* finally, the user can have requested no decorations, which overrides
1733        everything (but doesnt give it a border if it doesnt have one) */
1734     if (self->undecorated)
1735         self->decorations = 0;
1736
1737     /* if we don't have a titlebar, then we cannot shade! */
1738     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1739         self->functions &= ~OB_CLIENT_FUNC_SHADE;
1740
1741     /* now we need to check against rules for the client's current state */
1742     if (self->fullscreen) {
1743         self->functions &= (OB_CLIENT_FUNC_CLOSE |
1744                             OB_CLIENT_FUNC_FULLSCREEN |
1745                             OB_CLIENT_FUNC_ICONIFY);
1746         self->decorations = 0;
1747     }
1748
1749     client_change_allowed_actions(self);
1750
1751     if (reconfig)
1752         /* force reconfigure to make sure decorations are updated */
1753         client_reconfigure(self, TRUE);
1754 }
1755
1756 static void client_change_allowed_actions(ObClient *self)
1757 {
1758     gulong actions[12];
1759     gint num = 0;
1760
1761     /* desktop windows are kept on all desktops */
1762     if (self->type != OB_CLIENT_TYPE_DESKTOP)
1763         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_CHANGE_DESKTOP);
1764
1765     if (self->functions & OB_CLIENT_FUNC_SHADE)
1766         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_SHADE);
1767     if (self->functions & OB_CLIENT_FUNC_CLOSE)
1768         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_CLOSE);
1769     if (self->functions & OB_CLIENT_FUNC_MOVE)
1770         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MOVE);
1771     if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1772         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MINIMIZE);
1773     if (self->functions & OB_CLIENT_FUNC_RESIZE)
1774         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_RESIZE);
1775     if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1776         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_FULLSCREEN);
1777     if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1778         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MAXIMIZE_HORZ);
1779         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_MAXIMIZE_VERT);
1780     }
1781     if (self->functions & OB_CLIENT_FUNC_ABOVE)
1782         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_ABOVE);
1783     if (self->functions & OB_CLIENT_FUNC_BELOW)
1784         actions[num++] = OBT_PROP_ATOM(NET_WM_ACTION_BELOW);
1785     if (self->functions & OB_CLIENT_FUNC_UNDECORATE)
1786         actions[num++] = OBT_PROP_ATOM(OB_WM_ACTION_UNDECORATE);
1787
1788     OBT_PROP_SETA32(self->window, NET_WM_ALLOWED_ACTIONS, ATOM, actions, num);
1789
1790     /* make sure the window isn't breaking any rules now
1791
1792        don't check ICONIFY here.  just cuz a window can't iconify doesnt mean
1793        it can't be iconified with its parent
1794     */
1795
1796     if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1797         if (self->frame) client_shade(self, FALSE);
1798         else self->shaded = FALSE;
1799     }
1800     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1801         if (self->frame) client_fullscreen(self, FALSE);
1802         else self->fullscreen = FALSE;
1803     }
1804     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1805                                                          self->max_vert)) {
1806         if (self->frame) client_maximize(self, FALSE, 0);
1807         else self->max_vert = self->max_horz = FALSE;
1808     }
1809 }
1810
1811 void client_update_wmhints(ObClient *self)
1812 {
1813     XWMHints *hints;
1814
1815     /* assume a window takes input if it doesn't specify */
1816     self->can_focus = TRUE;
1817
1818     if ((hints = XGetWMHints(obt_display, self->window)) != NULL) {
1819         gboolean ur;
1820
1821         if (hints->flags & InputHint)
1822             self->can_focus = hints->input;
1823
1824         /* only do this when first managing the window *AND* when we aren't
1825            starting up! */
1826         if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1827             if (hints->flags & StateHint)
1828                 self->iconic = hints->initial_state == IconicState;
1829
1830         ur = self->urgent;
1831         self->urgent = (hints->flags & XUrgencyHint);
1832         if (self->urgent && !ur)
1833             client_hilite(self, TRUE);
1834         else if (!self->urgent && ur && self->demands_attention)
1835             client_hilite(self, FALSE);
1836
1837         if (!(hints->flags & WindowGroupHint))
1838             hints->window_group = None;
1839
1840         /* did the group state change? */
1841         if (hints->window_group !=
1842             (self->group ? self->group->leader : None))
1843         {
1844             ObGroup *oldgroup = self->group;
1845
1846             /* remove from the old group if there was one */
1847             if (self->group) {
1848                 group_remove(self->group, self);
1849                 self->group = NULL;
1850             }
1851
1852             /* add ourself to the group if we have one */
1853             if (hints->window_group != None) {
1854                 self->group = group_add(hints->window_group, self);
1855             }
1856
1857             /* Put ourselves into the new group's transient tree, and remove
1858                ourselves from the old group's */
1859             client_update_transient_tree(self, oldgroup, self->group,
1860                                          self->transient_for_group,
1861                                          self->transient_for_group,
1862                                          client_direct_parent(self),
1863                                          client_direct_parent(self));
1864
1865             /* Lastly, being in a group, or not, can change if the window is
1866                transient for anything.
1867
1868                The logic for this is:
1869                self->transient = TRUE always if the window wants to be
1870                transient for something, even if transient_for was NULL because
1871                it wasn't in a group before.
1872
1873                If parents was NULL and oldgroup was NULL we can assume
1874                that when we add the new group, it will become transient for
1875                something.
1876
1877                If transient_for_group is TRUE, then it must have already
1878                had a group. If it is getting a new group, the above call to
1879                client_update_transient_tree has already taken care of
1880                everything ! If it is losing all group status then it will
1881                no longer be transient for anything and that needs to be
1882                updated.
1883             */
1884             if (self->transient &&
1885                 ((self->parents == NULL && oldgroup == NULL) ||
1886                  (self->transient_for_group && !self->group)))
1887                 client_update_transient_for(self);
1888         }
1889
1890         /* the WM_HINTS can contain an icon */
1891         if (hints->flags & IconPixmapHint)
1892             client_update_icons(self);
1893
1894         XFree(hints);
1895     }
1896 }
1897
1898 void client_update_title(ObClient *self)
1899 {
1900     gchar *data = NULL;
1901     gchar *visible = NULL;
1902
1903     g_free(self->title);
1904     g_free(self->original_title);
1905
1906     /* try netwm */
1907     if (!OBT_PROP_GETS(self->window, NET_WM_NAME, utf8, &data)) {
1908         /* try old x stuff */
1909         if (!(OBT_PROP_GETS(self->window, WM_NAME, locale, &data)
1910               || OBT_PROP_GETS(self->window, WM_NAME, utf8, &data))) {
1911             if (self->transient) {
1912     /*
1913     GNOME alert windows are not given titles:
1914     http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1915     */
1916                 data = g_strdup("");
1917             } else
1918                 data = g_strdup(_("Unnamed Window"));
1919         }
1920     }
1921     self->original_title = g_strdup(data);
1922
1923     if (self->client_machine) {
1924         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1925         g_free(data);
1926     } else
1927         visible = data;
1928
1929     if (self->not_responding) {
1930         data = visible;
1931         if (self->kill_level > 0)
1932             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
1933         else
1934             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
1935         g_free(data);
1936     }
1937
1938     OBT_PROP_SETS(self->window, NET_WM_VISIBLE_NAME, utf8, visible);
1939     self->title = visible;
1940
1941     if (self->frame)
1942         frame_adjust_title(self->frame);
1943
1944     /* update the icon title */
1945     data = NULL;
1946     g_free(self->icon_title);
1947
1948     /* try netwm */
1949     if (!OBT_PROP_GETS(self->window, NET_WM_ICON_NAME, utf8, &data))
1950         /* try old x stuff */
1951         if (!(OBT_PROP_GETS(self->window, WM_ICON_NAME, locale, &data) ||
1952               OBT_PROP_GETS(self->window, WM_ICON_NAME, utf8, &data)))
1953             data = g_strdup(self->title);
1954
1955     if (self->client_machine) {
1956         visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1957         g_free(data);
1958     } else
1959         visible = data;
1960
1961     if (self->not_responding) {
1962         data = visible;
1963         if (self->kill_level > 0)
1964             visible = g_strdup_printf("%s - [%s]", data, _("Killing..."));
1965         else
1966             visible = g_strdup_printf("%s - [%s]", data, _("Not Responding"));
1967         g_free(data);
1968     }
1969
1970     OBT_PROP_SETS(self->window, NET_WM_VISIBLE_ICON_NAME, utf8, visible);
1971     self->icon_title = visible;
1972 }
1973
1974 void client_update_strut(ObClient *self)
1975 {
1976     guint num;
1977     guint32 *data;
1978     gboolean got = FALSE;
1979     StrutPartial strut;
1980
1981     if (OBT_PROP_GETA32(self->window, NET_WM_STRUT_PARTIAL, CARDINAL,
1982                         &data, &num))
1983     {
1984         if (num == 12) {
1985             got = TRUE;
1986             STRUT_PARTIAL_SET(strut,
1987                               data[0], data[2], data[1], data[3],
1988                               data[4], data[5], data[8], data[9],
1989                               data[6], data[7], data[10], data[11]);
1990         }
1991         g_free(data);
1992     }
1993
1994     if (!got &&
1995         OBT_PROP_GETA32(self->window, NET_WM_STRUT, CARDINAL, &data, &num)) {
1996         if (num == 4) {
1997             Rect *a;
1998
1999             got = TRUE;
2000
2001             /* use the screen's width/height */
2002             a = screen_physical_area_all_monitors();
2003
2004             STRUT_PARTIAL_SET(strut,
2005                               data[0], data[2], data[1], data[3],
2006                               a->y, a->y + a->height - 1,
2007                               a->x, a->x + a->width - 1,
2008                               a->y, a->y + a->height - 1,
2009                               a->x, a->x + a->width - 1);
2010             g_free(a);
2011         }
2012         g_free(data);
2013     }
2014
2015     if (!got)
2016         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
2017                           0, 0, 0, 0, 0, 0, 0, 0);
2018
2019     if (!STRUT_EQUAL(strut, self->strut)) {
2020         self->strut = strut;
2021
2022         /* updating here is pointless while we're being mapped cuz we're not in
2023            the client list yet */
2024         if (self->frame)
2025             screen_update_areas();
2026     }
2027 }
2028
2029 void client_update_icons(ObClient *self)
2030 {
2031     guint num;
2032     guint32 *data;
2033     guint w, h, i, j;
2034     guint num_seen;  /* number of icons present */
2035     RrImage *img;
2036
2037     img = NULL;
2038
2039     /* grab the server, because we might be setting the window's icon and
2040        we don't want them to set it in between and we overwrite their own
2041        icon */
2042     grab_server(TRUE);
2043
2044     if (OBT_PROP_GETA32(self->window, NET_WM_ICON, CARDINAL, &data, &num)) {
2045         /* figure out how many valid icons are in here */
2046         i = 0;
2047         num_seen = 0;
2048         while (i + 2 < num) { /* +2 is to make sure there is a w and h */
2049             w = data[i++];
2050             h = data[i++];
2051             /* watch for the data being too small for the specified size,
2052                or for zero sized icons. */
2053             if (i + w*h > num || w == 0 || h == 0) break;
2054
2055             /* convert it to the right bit order for ObRender */
2056             for (j = 0; j < w*h; ++j)
2057                 data[i+j] =
2058                     (((data[i+j] >> 24) & 0xff) << RrDefaultAlphaOffset) +
2059                     (((data[i+j] >> 16) & 0xff) << RrDefaultRedOffset)   +
2060                     (((data[i+j] >>  8) & 0xff) << RrDefaultGreenOffset) +
2061                     (((data[i+j] >>  0) & 0xff) << RrDefaultBlueOffset);
2062
2063             /* is it in the cache? */
2064             img = RrImageCacheFind(ob_rr_icons, &data[i], w, h);
2065             if (img) RrImageRef(img); /* own it */
2066
2067             i += w*h;
2068             ++num_seen;
2069
2070             /* don't bother looping anymore if we already found it in the cache
2071                since we'll just use that! */
2072             if (img) break;
2073         }
2074
2075         /* if it's not in the cache yet, then add it to the cache now.
2076            we have already converted it to the correct bit order above */
2077         if (!img && num_seen > 0) {
2078             img = RrImageNew(ob_rr_icons);
2079             i = 0;
2080             for (j = 0; j < num_seen; ++j) {
2081                 w = data[i++];
2082                 h = data[i++];
2083                 RrImageAddPicture(img, &data[i], w, h);
2084                 i += w*h;
2085             }
2086         }
2087
2088         g_free(data);
2089     }
2090
2091     /* if we didn't find an image from the NET_WM_ICON stuff, then try the
2092        legacy X hints */
2093     if (!img) {
2094         XWMHints *hints;
2095
2096         if ((hints = XGetWMHints(obt_display, self->window))) {
2097             if (hints->flags & IconPixmapHint) {
2098                 gboolean xicon;
2099                 obt_display_ignore_errors(TRUE);
2100                 xicon = RrPixmapToRGBA(ob_rr_inst,
2101                                        hints->icon_pixmap,
2102                                        (hints->flags & IconMaskHint ?
2103                                         hints->icon_mask : None),
2104                                        (gint*)&w, (gint*)&h, &data);
2105                 obt_display_ignore_errors(FALSE);
2106
2107
2108                 if (xicon) {
2109                     if (w > 0 && h > 0) {
2110                         /* is this icon in the cache yet? */
2111                         img = RrImageCacheFind(ob_rr_icons, data, w, h);
2112                         if (img) RrImageRef(img); /* own it */
2113
2114                         /* if not, then add it */
2115                         if (!img) {
2116                             img = RrImageNew(ob_rr_icons);
2117                             RrImageAddPicture(img, data, w, h);
2118                         }
2119                     }
2120
2121                     g_free(data);
2122                 }
2123             }
2124             XFree(hints);
2125         }
2126     }
2127
2128     /* set the client's icons to be whatever we found */
2129     RrImageUnref(self->icon_set);
2130     self->icon_set = img;
2131
2132     /* if the client has no icon at all, then we set a default icon onto it.
2133        but, if it has parents, then one of them will have an icon already
2134     */
2135     if (!self->icon_set && !self->parents) {
2136         RrPixel32 *icon = ob_rr_theme->def_win_icon;
2137         gulong *ldata; /* use a long here to satisfy OBT_PROP_SETA32 */
2138
2139         w = ob_rr_theme->def_win_icon_w;
2140         h = ob_rr_theme->def_win_icon_h;
2141         ldata = g_new(gulong, w*h+2);
2142         ldata[0] = w;
2143         ldata[1] = h;
2144         for (i = 0; i < w*h; ++i)
2145             ldata[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2146                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2147                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2148                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2149         OBT_PROP_SETA32(self->window, NET_WM_ICON, CARDINAL, ldata, w*h+2);
2150         g_free(ldata);
2151     } else if (self->frame)
2152         /* don't draw the icon empty if we're just setting one now anyways,
2153            we'll get the property change any second */
2154         frame_adjust_icon(self->frame);
2155
2156     grab_server(FALSE);
2157 }
2158
2159 void client_update_icon_geometry(ObClient *self)
2160 {
2161     guint num;
2162     guint32 *data;
2163
2164     RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2165
2166     if (OBT_PROP_GETA32(self->window, NET_WM_ICON_GEOMETRY, CARDINAL,
2167                         &data, &num))
2168     {
2169         if (num == 4)
2170             /* don't let them set it with an area < 0 */
2171             RECT_SET(self->icon_geometry, data[0], data[1],
2172                      MAX(data[2],0), MAX(data[3],0));
2173         g_free(data);
2174     }
2175 }
2176
2177 static void client_get_session_ids(ObClient *self)
2178 {
2179     guint32 leader;
2180     gboolean got;
2181     gchar *s;
2182     gchar **ss;
2183
2184     if (!OBT_PROP_GET32(self->window, WM_CLIENT_LEADER, WINDOW, &leader))
2185         leader = None;
2186
2187     /* get the SM_CLIENT_ID */
2188     got = FALSE;
2189     if (leader)
2190         got = OBT_PROP_GETS(leader, SM_CLIENT_ID, locale, &self->sm_client_id);
2191     if (!got)
2192         OBT_PROP_GETS(self->window, SM_CLIENT_ID, locale, &self->sm_client_id);
2193
2194     /* get the WM_CLASS (name and class). make them "" if they are not
2195        provided */
2196     got = FALSE;
2197     if (leader)
2198         got = OBT_PROP_GETSS(leader, WM_CLASS, locale, &ss);
2199     if (!got)
2200         got = OBT_PROP_GETSS(self->window, WM_CLASS, locale, &ss);
2201
2202     if (got) {
2203         if (ss[0]) {
2204             self->name = g_strdup(ss[0]);
2205             if (ss[1])
2206                 self->class = g_strdup(ss[1]);
2207         }
2208         g_strfreev(ss);
2209     }
2210
2211     if (self->name == NULL) self->name = g_strdup("");
2212     if (self->class == NULL) self->class = g_strdup("");
2213
2214     /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2215     got = FALSE;
2216     if (leader)
2217         got = OBT_PROP_GETS(leader, WM_WINDOW_ROLE, locale, &s);
2218     if (!got)
2219         got = OBT_PROP_GETS(self->window, WM_WINDOW_ROLE, locale, &s);
2220
2221     if (got)
2222         self->role = s;
2223     else
2224         self->role = g_strdup("");
2225
2226     /* get the WM_COMMAND */
2227     got = FALSE;
2228
2229     if (leader)
2230         got = OBT_PROP_GETSS(leader, WM_COMMAND, locale, &ss);
2231     if (!got)
2232         got = OBT_PROP_GETSS(self->window, WM_COMMAND, locale, &ss);
2233
2234     if (got) {
2235         /* merge/mash them all together */
2236         gchar *merge = NULL;
2237         gint i;
2238
2239         for (i = 0; ss[i]; ++i) {
2240             gchar *tmp = merge;
2241             if (merge)
2242                 merge = g_strconcat(merge, ss[i], NULL);
2243             else
2244                 merge = g_strconcat(ss[i], NULL);
2245             g_free(tmp);
2246         }
2247         g_strfreev(ss);
2248
2249         self->wm_command = merge;
2250     }
2251
2252     /* get the WM_CLIENT_MACHINE */
2253     got = FALSE;
2254     if (leader)
2255         got = OBT_PROP_GETS(leader, WM_CLIENT_MACHINE, locale, &s);
2256     if (!got)
2257         got = OBT_PROP_GETS(self->window, WM_CLIENT_MACHINE, locale, &s);
2258
2259     if (got) {
2260         gchar localhost[128];
2261         guint32 pid;
2262
2263         gethostname(localhost, 127);
2264         localhost[127] = '\0';
2265         if (strcmp(localhost, s) != 0)
2266             self->client_machine = s;
2267         else
2268             g_free(s);
2269
2270         /* see if it has the PID set too (the PID requires that the
2271            WM_CLIENT_MACHINE be set) */
2272         if (OBT_PROP_GET32(self->window, NET_WM_PID, CARDINAL, &pid))
2273             self->pid = pid;
2274     }
2275 }
2276
2277 static void client_change_wm_state(ObClient *self)
2278 {
2279     gulong state[2];
2280     glong old;
2281
2282     old = self->wmstate;
2283
2284     if (self->shaded || self->iconic ||
2285         (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2286     {
2287         self->wmstate = IconicState;
2288     } else
2289         self->wmstate = NormalState;
2290
2291     if (old != self->wmstate) {
2292         OBT_PROP_MSG(ob_screen, self->window, KDE_WM_CHANGE_STATE,
2293                      self->wmstate, 1, 0, 0, 0);
2294
2295         state[0] = self->wmstate;
2296         state[1] = None;
2297         OBT_PROP_SETA32(self->window, WM_STATE, WM_STATE, state, 2);
2298     }
2299 }
2300
2301 static void client_change_state(ObClient *self)
2302 {
2303     gulong netstate[13];
2304     guint num;
2305
2306     num = 0;
2307     if (self->modal)
2308         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MODAL);
2309     if (self->shaded)
2310         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SHADED);
2311     if (self->iconic)
2312         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_HIDDEN);
2313     if (self->skip_taskbar)
2314         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR);
2315     if (self->skip_pager)
2316         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER);
2317     if (self->fullscreen)
2318         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN);
2319     if (self->max_vert)
2320         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT);
2321     if (self->max_horz)
2322         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ);
2323     if (self->above)
2324         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_ABOVE);
2325     if (self->below)
2326         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_BELOW);
2327     if (self->demands_attention)
2328         netstate[num++] = OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION);
2329     if (self->undecorated)
2330         netstate[num++] = OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED);
2331     if (self->locked)
2332         netstate[num++] = OBT_PROP_ATOM(OB_WM_STATE_LOCKED);
2333     OBT_PROP_SETA32(self->window, NET_WM_STATE, ATOM, netstate, num);
2334
2335     if (self->frame)
2336         frame_adjust_state(self->frame);
2337 }
2338
2339 ObClient *client_search_focus_tree(ObClient *self)
2340 {
2341     GSList *it;
2342     ObClient *ret;
2343
2344     for (it = self->transients; it; it = g_slist_next(it)) {
2345         if (client_focused(it->data)) return it->data;
2346         if ((ret = client_search_focus_tree(it->data))) return ret;
2347     }
2348     return NULL;
2349 }
2350
2351 ObClient *client_search_focus_tree_full(ObClient *self)
2352 {
2353     if (self->parents) {
2354         GSList *it;
2355
2356         for (it = self->parents; it; it = g_slist_next(it)) {
2357             ObClient *c = it->data;
2358             if ((c = client_search_focus_tree_full(it->data))) return c;
2359         }
2360
2361         return NULL;
2362     }
2363     else {
2364         /* this function checks the whole tree, the client_search_focus_tree
2365            does not, so we need to check this window */
2366         if (client_focused(self))
2367             return self;
2368         return client_search_focus_tree(self);
2369     }
2370 }
2371
2372 ObClient *client_search_focus_group_full(ObClient *self)
2373 {
2374     GSList *it;
2375
2376     if (self->group) {
2377         for (it = self->group->members; it; it = g_slist_next(it)) {
2378             ObClient *c = it->data;
2379
2380             if (client_focused(c)) return c;
2381             if ((c = client_search_focus_tree(it->data))) return c;
2382         }
2383     } else
2384         if (client_focused(self)) return self;
2385     return NULL;
2386 }
2387
2388 gboolean client_has_parent(ObClient *self)
2389 {
2390     return self->parents != NULL;
2391 }
2392
2393 static ObStackingLayer calc_layer(ObClient *self)
2394 {
2395     ObStackingLayer l;
2396     Rect *monitor;
2397
2398     monitor = screen_physical_area_monitor(client_monitor(self));
2399
2400     if (self->type == OB_CLIENT_TYPE_DESKTOP)
2401         l = OB_STACKING_LAYER_DESKTOP;
2402     else if (self->type == OB_CLIENT_TYPE_DOCK) {
2403         if (self->below) l = OB_STACKING_LAYER_NORMAL;
2404         else l = OB_STACKING_LAYER_ABOVE;
2405     }
2406     else if ((self->fullscreen ||
2407               /* No decorations and fills the monitor = oldskool fullscreen.
2408                  But not for maximized windows.
2409               */
2410               (self->decorations == 0 &&
2411                !(self->max_horz && self->max_vert) &&
2412                RECT_EQUAL(self->area, *monitor))) &&
2413              /* you are fullscreen while you or your children are focused.. */
2414              (client_focused(self) || client_search_focus_tree(self) ||
2415               /* you can be fullscreen if you're on another desktop */
2416               (self->desktop != screen_desktop &&
2417                self->desktop != DESKTOP_ALL) ||
2418               /* and you can also be fullscreen if the focused client is on
2419                  another monitor, or nothing else is focused */
2420               (!focus_client ||
2421                client_monitor(focus_client) != client_monitor(self))))
2422         l = OB_STACKING_LAYER_FULLSCREEN;
2423     else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2424     else if (self->below) l = OB_STACKING_LAYER_BELOW;
2425     else l = OB_STACKING_LAYER_NORMAL;
2426
2427     g_free(monitor);
2428
2429     return l;
2430 }
2431
2432 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2433                                         ObStackingLayer min)
2434 {
2435     ObStackingLayer old, own;
2436     GSList *it;
2437
2438     old = self->layer;
2439     own = calc_layer(self);
2440     self->layer = MAX(own, min);
2441
2442     if (self->layer != old) {
2443         stacking_remove(CLIENT_AS_WINDOW(self));
2444         stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
2445     }
2446
2447     /* we've been restacked */
2448     self->visited = TRUE;
2449
2450     for (it = self->transients; it; it = g_slist_next(it))
2451         client_calc_layer_recursive(it->data, orig,
2452                                     self->layer);
2453 }
2454
2455 static void client_calc_layer_internal(ObClient *self)
2456 {
2457     GSList *sit;
2458
2459     /* transients take on the layer of their parents */
2460     sit = client_search_all_top_parents(self);
2461
2462     for (; sit; sit = g_slist_next(sit))
2463         client_calc_layer_recursive(sit->data, self, 0);
2464 }
2465
2466 void client_calc_layer(ObClient *self)
2467 {
2468     GList *it;
2469
2470     /* skip over stuff above fullscreen layer */
2471     for (it = stacking_list; it; it = g_list_next(it))
2472         if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
2473
2474     /* find the windows in the fullscreen layer, and mark them not-visited */
2475     for (; it; it = g_list_next(it)) {
2476         if (window_layer(it->data) < OB_STACKING_LAYER_FULLSCREEN) break;
2477         else if (WINDOW_IS_CLIENT(it->data))
2478             WINDOW_AS_CLIENT(it->data)->visited = FALSE;
2479     }
2480
2481     client_calc_layer_internal(self);
2482
2483     /* skip over stuff above fullscreen layer */
2484     for (it = stacking_list; it; it = g_list_next(it))
2485         if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
2486
2487     /* now recalc any windows in the fullscreen layer which have not
2488        had their layer recalced already */
2489     for (; it; it = g_list_next(it)) {
2490         if (window_layer(it->data) < OB_STACKING_LAYER_FULLSCREEN) break;
2491         else if (WINDOW_IS_CLIENT(it->data) &&
2492                  !WINDOW_AS_CLIENT(it->data)->visited)
2493             client_calc_layer_internal(it->data);
2494     }
2495 }
2496
2497 gboolean client_should_show(ObClient *self)
2498 {
2499     if (self->iconic)
2500         return FALSE;
2501     if (client_normal(self) && screen_showing_desktop)
2502         return FALSE;
2503     if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2504         return TRUE;
2505
2506     return FALSE;
2507 }
2508
2509 gboolean client_show(ObClient *self)
2510 {
2511     gboolean show = FALSE;
2512
2513     if (client_should_show(self)) {
2514         /* replay pending pointer event before showing the window, in case it
2515            should be going to something under the window */
2516         mouse_replay_pointer();
2517
2518         frame_show(self->frame);
2519         show = TRUE;
2520
2521         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2522            it needs to be in IconicState. This includes when it is on another
2523            desktop!
2524         */
2525         client_change_wm_state(self);
2526     }
2527     return show;
2528 }
2529
2530 gboolean client_hide(ObClient *self)
2531 {
2532     gboolean hide = FALSE;
2533
2534     if (!client_should_show(self)) {
2535         if (self == focus_client) {
2536             event_cancel_all_key_grabs();
2537         }
2538
2539         /* We don't need to ignore enter events here.
2540            The window can hide/iconify in 3 different ways:
2541            1 - through an x message. in this case we ignore all enter events
2542                caused by responding to the x message (unless underMouse)
2543            2 - by a keyboard action. in this case we ignore all enter events
2544                caused by the action
2545            3 - by a mouse action. in this case they are doing stuff with the
2546                mouse and focus _should_ move.
2547
2548            Also in action_end, we simulate an enter event that can't be ignored
2549            so trying to ignore them is futile in case 3 anyways
2550         */
2551
2552         /* replay pending pointer event before hiding the window, in case it
2553            should be going to the window */
2554         mouse_replay_pointer();
2555
2556         frame_hide(self->frame);
2557         hide = TRUE;
2558
2559         /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2560            it needs to be in IconicState. This includes when it is on another
2561            desktop!
2562         */
2563         client_change_wm_state(self);
2564     }
2565     return hide;
2566 }
2567
2568 void client_showhide(ObClient *self)
2569 {
2570     if (!client_show(self))
2571         client_hide(self);
2572 }
2573
2574 gboolean client_normal(ObClient *self) {
2575     return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2576               self->type == OB_CLIENT_TYPE_DOCK ||
2577               self->type == OB_CLIENT_TYPE_SPLASH);
2578 }
2579
2580 gboolean client_helper(ObClient *self)
2581 {
2582     return (self->type == OB_CLIENT_TYPE_UTILITY ||
2583             self->type == OB_CLIENT_TYPE_MENU ||
2584             self->type == OB_CLIENT_TYPE_TOOLBAR);
2585 }
2586
2587 gboolean client_mouse_focusable(ObClient *self)
2588 {
2589     return !(self->type == OB_CLIENT_TYPE_MENU ||
2590              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2591              self->type == OB_CLIENT_TYPE_SPLASH ||
2592              self->type == OB_CLIENT_TYPE_DOCK);
2593 }
2594
2595 gboolean client_enter_focusable(ObClient *self)
2596 {
2597     /* you can focus desktops but it shouldn't on enter */
2598     return (client_mouse_focusable(self) &&
2599             self->type != OB_CLIENT_TYPE_DESKTOP);
2600 }
2601
2602
2603 static void client_apply_startup_state(ObClient *self,
2604                                        gint x, gint y, gint w, gint h)
2605 {
2606     /* save the states that we are going to apply */
2607     gboolean iconic = self->iconic;
2608     gboolean fullscreen = self->fullscreen;
2609     gboolean undecorated = self->undecorated;
2610     gboolean shaded = self->shaded;
2611     gboolean demands_attention = self->demands_attention;
2612     gboolean max_horz = self->max_horz;
2613     gboolean max_vert = self->max_vert;
2614     Rect oldarea;
2615     gint l;
2616
2617     /* turn them all off in the client, so they won't affect the window
2618        being placed */
2619     self->iconic = self->fullscreen = self->undecorated = self->shaded =
2620         self->demands_attention = self->max_horz = self->max_vert = FALSE;
2621
2622     /* move the client to its placed position, or it it's already there,
2623        generate a ConfigureNotify telling the client where it is.
2624
2625        do this after adjusting the frame. otherwise it gets all weird and
2626        clients don't work right
2627
2628        do this before applying the states so they have the correct
2629        pre-max/pre-fullscreen values
2630     */
2631     client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE);
2632     ob_debug("placed window 0x%x at %d, %d with size %d x %d",
2633              self->window, x, y, w, h);
2634     /* save the area, and make it where it should be for the premax stuff */
2635     oldarea = self->area;
2636     RECT_SET(self->area, x, y, w, h);
2637
2638     /* apply the states. these are in a carefully crafted order.. */
2639
2640     if (iconic)
2641         client_iconify(self, TRUE, FALSE, TRUE);
2642     if (fullscreen)
2643         client_fullscreen(self, TRUE);
2644     if (undecorated)
2645         client_set_undecorated(self, TRUE);
2646     if (shaded)
2647         client_shade(self, TRUE);
2648     if (demands_attention)
2649         client_hilite(self, TRUE);
2650
2651     if (max_vert && max_horz)
2652         client_maximize(self, TRUE, 0);
2653     else if (max_vert)
2654         client_maximize(self, TRUE, 2);
2655     else if (max_horz)
2656         client_maximize(self, TRUE, 1);
2657
2658     /* if the window hasn't been configured yet, then do so now, in fact the
2659        x,y,w,h may _not_ be the same as the area rect, which can end up
2660        meaning that the client isn't properly moved/resized by the fullscreen
2661        function
2662        pho can cause this because it maps at size of the screen but not 0,0
2663        so openbox moves it on screen to 0,0 (thus x,y=0,0 and area.x,y don't).
2664        then fullscreen'ing makes it go to 0,0 which it thinks it already is at
2665        cuz thats where the pre-fullscreen will be. however the actual area is
2666        not, so this needs to be called even if we have fullscreened/maxed
2667     */
2668     self->area = oldarea;
2669     client_configure(self, x, y, w, h, FALSE, TRUE, FALSE);
2670
2671     /* set the desktop hint, to make sure that it always exists */
2672     OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, self->desktop);
2673
2674     /* nothing to do for the other states:
2675        skip_taskbar
2676        skip_pager
2677        modal
2678        above
2679        below
2680     */
2681 }
2682
2683 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2684 {
2685     /* these should be the current values. this is for when you're not moving,
2686        just resizing */
2687     g_assert(*x == self->area.x);
2688     g_assert(oldw == self->area.width);
2689
2690     /* horizontal */
2691     switch (self->gravity) {
2692     default:
2693     case NorthWestGravity:
2694     case WestGravity:
2695     case SouthWestGravity:
2696     case StaticGravity:
2697     case ForgetGravity:
2698         break;
2699     case NorthGravity:
2700     case CenterGravity:
2701     case SouthGravity:
2702         *x -= (neww - oldw) / 2;
2703         break;
2704     case NorthEastGravity:
2705     case EastGravity:
2706     case SouthEastGravity:
2707         *x -= neww - oldw;
2708         break;
2709     }
2710 }
2711
2712 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2713 {
2714     /* these should be the current values. this is for when you're not moving,
2715        just resizing */
2716     g_assert(*y == self->area.y);
2717     g_assert(oldh == self->area.height);
2718
2719     /* vertical */
2720     switch (self->gravity) {
2721     default:
2722     case NorthWestGravity:
2723     case NorthGravity:
2724     case NorthEastGravity:
2725     case StaticGravity:
2726     case ForgetGravity:
2727         break;
2728     case WestGravity:
2729     case CenterGravity:
2730     case EastGravity:
2731         *y -= (newh - oldh) / 2;
2732         break;
2733     case SouthWestGravity:
2734     case SouthGravity:
2735     case SouthEastGravity:
2736         *y -= newh - oldh;
2737         break;
2738     }
2739 }
2740
2741 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2742                           gint *logicalw, gint *logicalh,
2743                           gboolean user)
2744 {
2745     Rect desired = {*x, *y, *w, *h};
2746     frame_rect_to_frame(self->frame, &desired);
2747
2748     /* make the frame recalculate its dimensions n shit without changing
2749        anything visible for real, this way the constraints below can work with
2750        the updated frame dimensions. */
2751     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
2752
2753     /* gets the frame's position */
2754     frame_client_gravity(self->frame, x, y);
2755
2756     /* these positions are frame positions, not client positions */
2757
2758     /* set the size and position if fullscreen */
2759     if (self->fullscreen) {
2760         Rect *a;
2761         guint i;
2762
2763         i = screen_find_monitor(&desired);
2764         a = screen_physical_area_monitor(i);
2765
2766         *x = a->x;
2767         *y = a->y;
2768         *w = a->width;
2769         *h = a->height;
2770
2771         user = FALSE; /* ignore if the client can't be moved/resized when it
2772                          is fullscreening */
2773
2774         g_free(a);
2775     } else if (self->max_horz || self->max_vert) {
2776         Rect *a;
2777         guint i;
2778
2779         /* use all possible struts when maximizing to the full screen */
2780         i = screen_find_monitor(&desired);
2781         a = screen_area(self->desktop, i,
2782                         (self->max_horz && self->max_vert ? NULL : &desired));
2783
2784         /* set the size and position if maximized */
2785         if (self->max_horz) {
2786             *x = a->x;
2787             *w = a->width - self->frame->size.left - self->frame->size.right;
2788         }
2789         if (self->max_vert) {
2790             *y = a->y;
2791             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2792         }
2793
2794         user = FALSE; /* ignore if the client can't be moved/resized when it
2795                          is maximizing */
2796
2797         g_free(a);
2798     }
2799
2800     /* gets the client's position */
2801     frame_frame_gravity(self->frame, x, y);
2802
2803     /* work within the preferred sizes given by the window */
2804     if (!(*w == self->area.width && *h == self->area.height)) {
2805         gint basew, baseh, minw, minh;
2806         gint incw, inch;
2807         gfloat minratio, maxratio;
2808
2809         incw = self->fullscreen || self->max_horz ? 1 : self->size_inc.width;
2810         inch = self->fullscreen || self->max_vert ? 1 : self->size_inc.height;
2811         minratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2812             0 : self->min_ratio;
2813         maxratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2814             0 : self->max_ratio;
2815
2816         /* base size is substituted with min size if not specified */
2817         if (self->base_size.width >= 0 || self->base_size.height >= 0) {
2818             basew = self->base_size.width;
2819             baseh = self->base_size.height;
2820         } else {
2821             basew = self->min_size.width;
2822             baseh = self->min_size.height;
2823         }
2824         /* min size is substituted with base size if not specified */
2825         if (self->min_size.width || self->min_size.height) {
2826             minw = self->min_size.width;
2827             minh = self->min_size.height;
2828         } else {
2829             minw = self->base_size.width;
2830             minh = self->base_size.height;
2831         }
2832
2833         /* This comment is no longer true */
2834         /* if this is a user-requested resize, then check against min/max
2835            sizes */
2836
2837         /* smaller than min size or bigger than max size? */
2838         if (*w > self->max_size.width) *w = self->max_size.width;
2839         if (*w < minw) *w = minw;
2840         if (*h > self->max_size.height) *h = self->max_size.height;
2841         if (*h < minh) *h = minh;
2842
2843         *w -= basew;
2844         *h -= baseh;
2845
2846         /* keep to the increments */
2847         *w /= incw;
2848         *h /= inch;
2849
2850         /* you cannot resize to nothing */
2851         if (basew + *w < 1) *w = 1 - basew;
2852         if (baseh + *h < 1) *h = 1 - baseh;
2853
2854         /* save the logical size */
2855         *logicalw = incw > 1 ? *w : *w + basew;
2856         *logicalh = inch > 1 ? *h : *h + baseh;
2857
2858         *w *= incw;
2859         *h *= inch;
2860
2861         *w += basew;
2862         *h += baseh;
2863
2864         /* adjust the height to match the width for the aspect ratios.
2865            for this, min size is not substituted for base size ever. */
2866         *w -= self->base_size.width;
2867         *h -= self->base_size.height;
2868
2869         if (minratio)
2870             if (*h * minratio > *w) {
2871                 *h = (gint)(*w / minratio);
2872
2873                 /* you cannot resize to nothing */
2874                 if (*h < 1) {
2875                     *h = 1;
2876                     *w = (gint)(*h * minratio);
2877                 }
2878             }
2879         if (maxratio)
2880             if (*h * maxratio < *w) {
2881                 *h = (gint)(*w / maxratio);
2882
2883                 /* you cannot resize to nothing */
2884                 if (*h < 1) {
2885                     *h = 1;
2886                     *w = (gint)(*h * minratio);
2887                 }
2888             }
2889
2890         *w += self->base_size.width;
2891         *h += self->base_size.height;
2892     }
2893
2894     /* these override the above states! if you cant move you can't move! */
2895     if (user) {
2896         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2897             *x = self->area.x;
2898             *y = self->area.y;
2899         }
2900         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2901             *w = self->area.width;
2902             *h = self->area.height;
2903         }
2904     }
2905
2906     g_assert(*w > 0);
2907     g_assert(*h > 0);
2908 }
2909
2910
2911 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
2912                       gboolean user, gboolean final, gboolean force_reply)
2913 {
2914     Rect oldframe;
2915     gint oldw, oldh;
2916     gboolean send_resize_client;
2917     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
2918     gboolean fmoved, fresized;
2919     guint fdecor = self->frame->decorations;
2920     gboolean fhorz = self->frame->max_horz;
2921     gboolean fvert = self->frame->max_vert;
2922     gint logicalw, logicalh;
2923
2924     /* find the new x, y, width, and height (and logical size) */
2925     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2926
2927     /* set the logical size if things changed */
2928     if (!(w == self->area.width && h == self->area.height))
2929         SIZE_SET(self->logical_size, logicalw, logicalh);
2930
2931     /* figure out if we moved or resized or what */
2932     moved = (x != self->area.x || y != self->area.y);
2933     resized = (w != self->area.width || h != self->area.height);
2934
2935     oldw = self->area.width;
2936     oldh = self->area.height;
2937     oldframe = self->frame->area;
2938     RECT_SET(self->area, x, y, w, h);
2939
2940     /* for app-requested resizes, always resize if 'resized' is true.
2941        for user-requested ones, only resize if final is true, or when
2942        resizing in redraw mode */
2943     send_resize_client = ((!user && resized) ||
2944                           (user && (final ||
2945                                     (resized && config_resize_redraw))));
2946
2947     /* if the client is enlarging, then resize the client before the frame */
2948     if (send_resize_client && (w > oldw || h > oldh)) {
2949         XMoveResizeWindow(obt_display, self->window,
2950                           self->frame->size.left, self->frame->size.top,
2951                           MAX(w, oldw), MAX(h, oldh));
2952         frame_adjust_client_area(self->frame);
2953     }
2954
2955     /* find the frame's dimensions and move/resize it */
2956     fmoved = moved;
2957     fresized = resized;
2958
2959     /* if decorations changed, then readjust everything for the frame */
2960     if (self->decorations != fdecor ||
2961         self->max_horz != fhorz || self->max_vert != fvert)
2962     {
2963         fmoved = fresized = TRUE;
2964     }
2965
2966     /* adjust the frame */
2967     if (fmoved || fresized) {
2968         gulong ignore_start;
2969         if (!user)
2970             ignore_start = event_start_ignore_all_enters();
2971
2972         /* replay pending pointer event before move the window, in case it
2973            would change what window gets the event */
2974         mouse_replay_pointer();
2975
2976         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
2977
2978         if (!user)
2979             event_end_ignore_all_enters(ignore_start);
2980     }
2981
2982     if (!user || final) {
2983         gint oldrx = self->root_pos.x;
2984         gint oldry = self->root_pos.y;
2985         /* we have reset the client to 0 border width, so don't include
2986            it in these coords */
2987         POINT_SET(self->root_pos,
2988                   self->frame->area.x + self->frame->size.left -
2989                   self->border_width,
2990                   self->frame->area.y + self->frame->size.top -
2991                   self->border_width);
2992         if (self->root_pos.x != oldrx || self->root_pos.y != oldry)
2993             rootmoved = TRUE;
2994     }
2995
2996     /* This is kinda tricky and should not be changed.. let me explain!
2997
2998        When user = FALSE, then the request is coming from the application
2999        itself, and we are more strict about when to send a synthetic
3000        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
3001        in this case (if force_reply is true)
3002
3003        When user = TRUE, then the request is coming from "us", like when we
3004        maximize a window or something.  In this case we are more lenient.  We
3005        used to follow the same rules as above, but _Java_ Swing can't handle
3006        this. So just to appease Swing, when user = TRUE, we always send
3007        a synthetic ConfigureNotify to give the window its root coordinates.
3008     */
3009     if ((!user && !resized && (rootmoved || force_reply)) ||
3010         (user && final && rootmoved))
3011     {
3012         XEvent event;
3013
3014         event.type = ConfigureNotify;
3015         event.xconfigure.display = obt_display;
3016         event.xconfigure.event = self->window;
3017         event.xconfigure.window = self->window;
3018
3019         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d",
3020                  self->title, self->root_pos.x, self->root_pos.y, w, h);
3021
3022         /* root window real coords */
3023         event.xconfigure.x = self->root_pos.x;
3024         event.xconfigure.y = self->root_pos.y;
3025         event.xconfigure.width = w;
3026         event.xconfigure.height = h;
3027         event.xconfigure.border_width = self->border_width;
3028         event.xconfigure.above = None;
3029         event.xconfigure.override_redirect = FALSE;
3030         XSendEvent(event.xconfigure.display, event.xconfigure.window,
3031                    FALSE, StructureNotifyMask, &event);
3032     }
3033
3034     /* if the client is shrinking, then resize the frame before the client.
3035
3036        both of these resize sections may run, because the top one only resizes
3037        in the direction that is growing
3038      */
3039     if (send_resize_client && (w <= oldw || h <= oldh)) {
3040         frame_adjust_client_area(self->frame);
3041         XMoveResizeWindow(obt_display, self->window,
3042                           self->frame->size.left, self->frame->size.top, w, h);
3043     }
3044
3045     XFlush(obt_display);
3046
3047     /* if it moved between monitors, then this can affect the stacking
3048        layer of this window or others - for fullscreen windows */
3049     if (screen_find_monitor(&self->frame->area) !=
3050         screen_find_monitor(&oldframe))
3051     {
3052         client_calc_layer(self);
3053     }
3054 }
3055
3056 void client_fullscreen(ObClient *self, gboolean fs)
3057 {
3058     gint x, y, w, h;
3059
3060     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
3061         self->fullscreen == fs) return;                   /* already done */
3062
3063     self->fullscreen = fs;
3064     client_change_state(self); /* change the state hints on the client */
3065
3066     if (fs) {
3067         self->pre_fullscreen_area = self->area;
3068         /* if the window is maximized, its area isn't all that meaningful.
3069            save its premax area instead. */
3070         if (self->max_horz) {
3071             self->pre_fullscreen_area.x = self->pre_max_area.x;
3072             self->pre_fullscreen_area.width = self->pre_max_area.width;
3073         }
3074         if (self->max_vert) {
3075             self->pre_fullscreen_area.y = self->pre_max_area.y;
3076             self->pre_fullscreen_area.height = self->pre_max_area.height;
3077         }
3078
3079         /* these will help configure_full figure out where to fullscreen
3080            the window */
3081         x = self->area.x;
3082         y = self->area.y;
3083         w = self->area.width;
3084         h = self->area.height;
3085     } else {
3086         g_assert(self->pre_fullscreen_area.width > 0 &&
3087                  self->pre_fullscreen_area.height > 0);
3088
3089         x = self->pre_fullscreen_area.x;
3090         y = self->pre_fullscreen_area.y;
3091         w = self->pre_fullscreen_area.width;
3092         h = self->pre_fullscreen_area.height;
3093         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3094     }
3095
3096     ob_debug("Window %s going fullscreen (%d)",
3097              self->title, self->fullscreen);
3098
3099     client_setup_decor_and_functions(self, FALSE);
3100     client_move_resize(self, x, y, w, h);
3101
3102     /* and adjust our layer/stacking. do this after resizing the window,
3103        and applying decorations, because windows which fill the screen are
3104        considered "fullscreen" and it affects their layer */
3105     client_calc_layer(self);
3106
3107     if (fs) {
3108         /* try focus us when we go into fullscreen mode */
3109         client_focus(self);
3110     }
3111 }
3112
3113 static void client_iconify_recursive(ObClient *self,
3114                                      gboolean iconic, gboolean curdesk,
3115                                      gboolean hide_animation)
3116 {
3117     GSList *it;
3118     gboolean changed = FALSE;
3119
3120     if (self->iconic != iconic) {
3121         ob_debug("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
3122                  self->window);
3123
3124         if (iconic) {
3125             /* don't let non-normal windows iconify along with their parents
3126                or whatever */
3127             if (client_normal(self)) {
3128                 self->iconic = iconic;
3129
3130                 /* update the focus lists.. iconic windows go to the bottom of
3131                    the list */
3132                 focus_order_to_bottom(self);
3133
3134                 changed = TRUE;
3135             }
3136         } else {
3137             self->iconic = iconic;
3138
3139             if (curdesk && self->desktop != screen_desktop &&
3140                 self->desktop != DESKTOP_ALL)
3141                 client_set_desktop(self, screen_desktop, FALSE, FALSE);
3142
3143             /* this puts it after the current focused window */
3144             focus_order_remove(self);
3145             focus_order_add_new(self);
3146
3147             changed = TRUE;
3148         }
3149     }
3150
3151     if (changed) {
3152         client_change_state(self);
3153         if (config_animate_iconify && !hide_animation)
3154             frame_begin_iconify_animation(self->frame, iconic);
3155         /* do this after starting the animation so it doesn't flash */
3156         client_showhide(self);
3157     }
3158
3159     /* iconify all direct transients, and deiconify all transients
3160        (non-direct too) */
3161     for (it = self->transients; it; it = g_slist_next(it))
3162         if (it->data != self)
3163             if (client_is_direct_child(self, it->data) || !iconic)
3164                 client_iconify_recursive(it->data, iconic, curdesk,
3165                                          hide_animation);
3166 }
3167
3168 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3169                     gboolean hide_animation)
3170 {
3171     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3172         /* move up the transient chain as far as possible first */
3173         self = client_search_top_direct_parent(self);
3174         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3175     }
3176 }
3177
3178 void client_maximize(ObClient *self, gboolean max, gint dir)
3179 {
3180     gint x, y, w, h;
3181
3182     g_assert(dir == 0 || dir == 1 || dir == 2);
3183     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
3184
3185     /* check if already done */
3186     if (max) {
3187         if (dir == 0 && self->max_horz && self->max_vert) return;
3188         if (dir == 1 && self->max_horz) return;
3189         if (dir == 2 && self->max_vert) return;
3190     } else {
3191         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3192         if (dir == 1 && !self->max_horz) return;
3193         if (dir == 2 && !self->max_vert) return;
3194     }
3195
3196     /* these will help configure_full figure out which screen to fill with
3197        the window */
3198     x = self->area.x;
3199     y = self->area.y;
3200     w = self->area.width;
3201     h = self->area.height;
3202
3203     if (max) {
3204         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3205             RECT_SET(self->pre_max_area,
3206                      self->area.x, self->pre_max_area.y,
3207                      self->area.width, self->pre_max_area.height);
3208         }
3209         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3210             RECT_SET(self->pre_max_area,
3211                      self->pre_max_area.x, self->area.y,
3212                      self->pre_max_area.width, self->area.height);
3213         }
3214     } else {
3215         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3216             g_assert(self->pre_max_area.width > 0);
3217
3218             x = self->pre_max_area.x;
3219             w = self->pre_max_area.width;
3220
3221             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3222                      0, self->pre_max_area.height);
3223         }
3224         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3225             g_assert(self->pre_max_area.height > 0);
3226
3227             y = self->pre_max_area.y;
3228             h = self->pre_max_area.height;
3229
3230             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3231                      self->pre_max_area.width, 0);
3232         }
3233     }
3234
3235     if (dir == 0 || dir == 1) /* horz */
3236         self->max_horz = max;
3237     if (dir == 0 || dir == 2) /* vert */
3238         self->max_vert = max;
3239
3240     client_change_state(self); /* change the state hints on the client */
3241
3242     client_setup_decor_and_functions(self, FALSE);
3243     client_move_resize(self, x, y, w, h);
3244 }
3245
3246 void client_shade(ObClient *self, gboolean shade)
3247 {
3248     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3249          shade) ||                         /* can't shade */
3250         self->shaded == shade) return;     /* already done */
3251
3252     self->shaded = shade;
3253     client_change_state(self);
3254     client_change_wm_state(self); /* the window is being hidden/shown */
3255     /* resize the frame to just the titlebar */
3256     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
3257 }
3258
3259 static void client_ping_event(ObClient *self, gboolean dead)
3260 {
3261     if (self->not_responding != dead) {
3262         self->not_responding = dead;
3263         client_update_title(self);
3264
3265         if (dead)
3266             /* the client isn't responding, so ask to kill it */
3267             client_prompt_kill(self);
3268         else {
3269             /* it came back to life ! */
3270
3271             if (self->kill_prompt) {
3272                 prompt_unref(self->kill_prompt);
3273                 self->kill_prompt = NULL;
3274             }
3275
3276             self->kill_level = 0;
3277         }
3278     }
3279 }
3280
3281 void client_close(ObClient *self)
3282 {
3283     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3284
3285     /* if closing an internal obprompt, that is just cancelling it */
3286     if (self->prompt) {
3287         prompt_cancel(self->prompt);
3288         return;
3289     }
3290
3291     /* in the case that the client provides no means to requesting that it
3292        close, we just kill it */
3293     if (!self->delete_window)
3294         /* don't use client_kill(), we should only kill based on PID in
3295            response to a lack of PING replies */
3296         XKillClient(obt_display, self->window);
3297     else {
3298         /* request the client to close with WM_DELETE_WINDOW */
3299         OBT_PROP_MSG_TO(self->window, self->window, WM_PROTOCOLS,
3300                         OBT_PROP_ATOM(WM_DELETE_WINDOW), event_curtime,
3301                         0, 0, 0, NoEventMask);
3302
3303         /* we're trying to close the window, so see if it is responding. if it
3304            is not, then we will let them kill the window */
3305         if (self->ping)
3306             ping_start(self, client_ping_event);
3307
3308         /* if we already know the window isn't responding (maybe they clicked
3309            no in the kill dialog but it hasn't come back to life), then show
3310            the kill dialog */
3311         if (self->not_responding)
3312             client_prompt_kill(self);
3313     }
3314 }
3315
3316 #define OB_KILL_RESULT_NO 0
3317 #define OB_KILL_RESULT_YES 1
3318
3319 static void client_kill_requested(ObPrompt *p, gint result, gpointer data)
3320 {
3321     ObClient *self = data;
3322
3323     if (result == OB_KILL_RESULT_YES)
3324         client_kill(self);
3325
3326     prompt_unref(self->kill_prompt);
3327     self->kill_prompt = NULL;
3328 }
3329
3330 static void client_prompt_kill(ObClient *self)
3331 {
3332     /* check if we're already prompting */
3333     if (!self->kill_prompt) {
3334         ObPromptAnswer answers[] = {
3335             { 0, OB_KILL_RESULT_NO },
3336             { 0, OB_KILL_RESULT_YES }
3337         };
3338         gchar *m;
3339         const gchar *y;
3340
3341         if (client_on_localhost(self)) {
3342             const gchar *sig;
3343
3344             if (self->kill_level == 0)
3345                 sig = "terminate";
3346             else
3347                 sig = "kill";
3348
3349             m = g_strdup_printf
3350                 (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"),
3351                  self->original_title, sig);
3352             y = _("End Process");
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?"),
3357                  self->original_title);
3358             y = _("Disconnect");
3359         }
3360         /* set the dialog buttons' text */
3361         answers[0].text = _("Cancel");  /* "no" */
3362         answers[1].text = y;            /* "yes" */
3363
3364         self->kill_prompt = prompt_new(m, answers,
3365                                        sizeof(answers)/sizeof(answers[0]),
3366                                        OB_KILL_RESULT_NO, /* default = no */
3367                                        OB_KILL_RESULT_NO, /* cancel = no */
3368                                        client_kill_requested, self);
3369         g_free(m);
3370     }
3371
3372     prompt_show(self->kill_prompt, self, TRUE);
3373 }
3374
3375 void client_kill(ObClient *self)
3376 {
3377     /* don't kill our own windows */
3378     if (self->prompt) return;
3379
3380     if (client_on_localhost(self) && self->pid) {
3381         /* running on the local host */
3382         if (self->kill_level == 0) {
3383             ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
3384                      self->window, self->pid);
3385             kill(self->pid, SIGTERM);
3386             ++self->kill_level;
3387
3388             /* show that we're trying to kill it */
3389             client_update_title(self);
3390         }
3391         else {
3392             ob_debug("killing window 0x%x with pid %lu, with SIGKILL",
3393                      self->window, self->pid);
3394             kill(self->pid, SIGKILL); /* kill -9 */
3395         }
3396     }
3397     else {
3398         /* running on a remote host */
3399         XKillClient(obt_display, self->window);
3400     }
3401 }
3402
3403 void client_hilite(ObClient *self, gboolean hilite)
3404 {
3405     if (self->demands_attention == hilite)
3406         return; /* no change */
3407
3408     /* don't allow focused windows to hilite */
3409     self->demands_attention = hilite && !client_focused(self);
3410     if (self->frame != NULL) { /* if we're mapping, just set the state */
3411         if (self->demands_attention)
3412             frame_flash_start(self->frame);
3413         else
3414             frame_flash_stop(self->frame);
3415         client_change_state(self);
3416     }
3417 }
3418
3419 static void client_set_desktop_recursive(ObClient *self,
3420                                          guint target,
3421                                          gboolean donthide,
3422                                          gboolean dontraise)
3423 {
3424     guint old;
3425     GSList *it;
3426
3427     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3428
3429         ob_debug("Setting desktop %u", target+1);
3430
3431         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3432
3433         old = self->desktop;
3434         self->desktop = target;
3435         OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, target);
3436         /* the frame can display the current desktop state */
3437         frame_adjust_state(self->frame);
3438         /* 'move' the window to the new desktop */
3439         if (!donthide)
3440             client_hide(self);
3441         client_show(self);
3442         /* raise if it was not already on the desktop */
3443         if (old != DESKTOP_ALL && !dontraise)
3444             stacking_raise(CLIENT_AS_WINDOW(self));
3445         if (STRUT_EXISTS(self->strut))
3446             screen_update_areas();
3447         else
3448             /* the new desktop's geometry may be different, so we may need to
3449                resize, for example if we are maximized */
3450             client_reconfigure(self, FALSE);
3451     }
3452
3453     /* move all transients */
3454     for (it = self->transients; it; it = g_slist_next(it))
3455         if (it->data != self)
3456             if (client_is_direct_child(self, it->data))
3457                 client_set_desktop_recursive(it->data, target,
3458                                              donthide, dontraise);
3459 }
3460
3461 void client_set_desktop(ObClient *self, guint target,
3462                         gboolean donthide, gboolean dontraise)
3463 {
3464     self = client_search_top_direct_parent(self);
3465     client_set_desktop_recursive(self, target, donthide, dontraise);
3466 }
3467
3468 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3469 {
3470     while (child != parent && (child = client_direct_parent(child)));
3471     return child == parent;
3472 }
3473
3474 ObClient *client_search_modal_child(ObClient *self)
3475 {
3476     GSList *it;
3477     ObClient *ret;
3478
3479     for (it = self->transients; it; it = g_slist_next(it)) {
3480         ObClient *c = it->data;
3481         if ((ret = client_search_modal_child(c))) return ret;
3482         if (c->modal) return c;
3483     }
3484     return NULL;
3485 }
3486
3487 gboolean client_validate(ObClient *self)
3488 {
3489     XEvent e;
3490
3491     XSync(obt_display, FALSE); /* get all events on the server */
3492
3493     if (XCheckTypedWindowEvent(obt_display, self->window, DestroyNotify, &e) ||
3494         XCheckTypedWindowEvent(obt_display, self->window, UnmapNotify, &e))
3495     {
3496         XPutBackEvent(obt_display, &e);
3497         return FALSE;
3498     }
3499
3500     return TRUE;
3501 }
3502
3503 void client_set_wm_state(ObClient *self, glong state)
3504 {
3505     if (state == self->wmstate) return; /* no change */
3506
3507     switch (state) {
3508     case IconicState:
3509         client_iconify(self, TRUE, TRUE, FALSE);
3510         break;
3511     case NormalState:
3512         client_iconify(self, FALSE, TRUE, FALSE);
3513         break;
3514     }
3515 }
3516
3517 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3518 {
3519     gboolean shaded = self->shaded;
3520     gboolean fullscreen = self->fullscreen;
3521     gboolean undecorated = self->undecorated;
3522     gboolean locked = self->locked;
3523     gboolean max_horz = self->max_horz;
3524     gboolean max_vert = self->max_vert;
3525     gboolean modal = self->modal;
3526     gboolean iconic = self->iconic;
3527     gboolean demands_attention = self->demands_attention;
3528     gboolean above = self->above;
3529     gboolean below = self->below;
3530     gint i;
3531
3532     if (!(action == OBT_PROP_ATOM(NET_WM_STATE_ADD) ||
3533           action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) ||
3534           action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)))
3535         /* an invalid action was passed to the client message, ignore it */
3536         return;
3537
3538     for (i = 0; i < 2; ++i) {
3539         Atom state = i == 0 ? data1 : data2;
3540
3541         if (!state) continue;
3542
3543         /* if toggling, then pick whether we're adding or removing */
3544         if (action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)) {
3545             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
3546                 action = modal ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3547                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3548             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT))
3549                 action = self->max_vert ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3550                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3551             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ))
3552                 action = self->max_horz ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3553                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3554             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
3555                 action = shaded ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3556                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3557             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
3558                 action = self->skip_taskbar ?
3559                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3560                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3561             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
3562                 action = self->skip_pager ?
3563                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3564                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3565             else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
3566                 action = self->iconic ?
3567                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3568                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3569             else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
3570                 action = fullscreen ?
3571                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3572                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3573             else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
3574                 action = self->above ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3575                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3576             else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
3577                 action = self->below ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3578                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3579             else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
3580                 action = self->demands_attention ?
3581                     OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3582                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3583             else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
3584                 action = undecorated ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3585                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3586             else if (state == OBT_PROP_ATOM(OB_WM_STATE_LOCKED))
3587                 action = locked ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3588                     OBT_PROP_ATOM(NET_WM_STATE_ADD);
3589         }
3590
3591         if (action == OBT_PROP_ATOM(NET_WM_STATE_ADD)) {
3592             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3593                 modal = TRUE;
3594             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3595                 max_vert = TRUE;
3596             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3597                 max_horz = TRUE;
3598             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3599                 shaded = TRUE;
3600             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3601                 self->skip_taskbar = TRUE;
3602             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3603                 self->skip_pager = TRUE;
3604             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3605                 iconic = TRUE;
3606             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3607                 fullscreen = TRUE;
3608             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3609                 above = TRUE;
3610                 below = FALSE;
3611             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3612                 above = FALSE;
3613                 below = TRUE;
3614             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3615                 demands_attention = TRUE;
3616             } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3617                 undecorated = TRUE;
3618             } else if (state == OBT_PROP_ATOM(OB_WM_STATE_LOCKED)) {
3619                 locked = TRUE;
3620             }
3621
3622         } else { /* action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) */
3623             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3624                 modal = FALSE;
3625             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3626                 max_vert = FALSE;
3627             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3628                 max_horz = FALSE;
3629             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3630                 shaded = FALSE;
3631             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3632                 self->skip_taskbar = FALSE;
3633             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3634                 self->skip_pager = FALSE;
3635             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3636                 iconic = FALSE;
3637             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3638                 fullscreen = FALSE;
3639             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3640                 above = FALSE;
3641             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3642                 below = FALSE;
3643             } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3644                 demands_attention = FALSE;
3645             } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3646                 undecorated = FALSE;
3647             } else if (state == OBT_PROP_ATOM(OB_WM_STATE_LOCKED)) {
3648                 locked = FALSE;
3649             }
3650         }
3651     }
3652
3653     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3654         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3655             /* toggling both */
3656             if (max_horz == max_vert) { /* both going the same way */
3657                 client_maximize(self, max_horz, 0);
3658             } else {
3659                 client_maximize(self, max_horz, 1);
3660                 client_maximize(self, max_vert, 2);
3661             }
3662         } else {
3663             /* toggling one */
3664             if (max_horz != self->max_horz)
3665                 client_maximize(self, max_horz, 1);
3666             else
3667                 client_maximize(self, max_vert, 2);
3668         }
3669     }
3670     /* change fullscreen state before shading, as it will affect if the window
3671        can shade or not */
3672     if (fullscreen != self->fullscreen)
3673         client_fullscreen(self, fullscreen);
3674     if (shaded != self->shaded)
3675         client_shade(self, shaded);
3676     if (undecorated != self->undecorated)
3677         client_set_undecorated(self, undecorated);
3678     if (locked != self->locked)
3679         client_set_locked(self, locked);
3680     if (above != self->above || below != self->below) {
3681         self->above = above;
3682         self->below = below;
3683         client_calc_layer(self);
3684     }
3685
3686     if (modal != self->modal) {
3687         self->modal = modal;
3688         /* when a window changes modality, then its stacking order with its
3689            transients needs to change */
3690         stacking_raise(CLIENT_AS_WINDOW(self));
3691
3692         /* it also may get focused. if something is focused that shouldn't
3693            be focused anymore, then move the focus */
3694         if (focus_client && client_focus_target(focus_client) != focus_client)
3695             client_focus(focus_client);
3696     }
3697
3698     if (iconic != self->iconic)
3699         client_iconify(self, iconic, FALSE, FALSE);
3700
3701     if (demands_attention != self->demands_attention)
3702         client_hilite(self, demands_attention);
3703
3704     client_change_state(self); /* change the hint to reflect these changes */
3705 }
3706
3707 ObClient *client_focus_target(ObClient *self)
3708 {
3709     ObClient *child = NULL;
3710
3711     child = client_search_modal_child(self);
3712     if (child) return child;
3713     return self;
3714 }
3715
3716 gboolean client_can_focus(ObClient *self)
3717 {
3718     /* choose the correct target */
3719     self = client_focus_target(self);
3720
3721     if (!self->frame->visible)
3722         return FALSE;
3723
3724     if (!(self->can_focus || self->focus_notify))
3725         return FALSE;
3726
3727     return TRUE;
3728 }
3729
3730 gboolean client_focus(ObClient *self)
3731 {
3732     /* we might not focus this window, so if we have modal children which would
3733        be focused instead, bring them to this desktop */
3734     client_bring_modal_windows(self);
3735
3736     /* choose the correct target */
3737     self = client_focus_target(self);
3738
3739     if (!client_can_focus(self)) {
3740         ob_debug_type(OB_DEBUG_FOCUS,
3741                       "Client %s can't be focused", self->title);
3742         return FALSE;
3743     }
3744
3745     ob_debug_type(OB_DEBUG_FOCUS,
3746                   "Focusing client \"%s\" (0x%x) at time %u",
3747                   self->title, self->window, event_curtime);
3748
3749     /* if using focus_delay, stop the timer now so that focus doesn't
3750        go moving on us */
3751     event_halt_focus_delay();
3752
3753     event_cancel_all_key_grabs();
3754
3755     obt_display_ignore_errors(TRUE);
3756
3757     if (self->can_focus) {
3758         /* This can cause a BadMatch error with CurrentTime, or if an app
3759            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3760         XSetInputFocus(obt_display, self->window, RevertToPointerRoot,
3761                        event_curtime);
3762     }
3763
3764     if (self->focus_notify) {
3765         XEvent ce;
3766         ce.xclient.type = ClientMessage;
3767         ce.xclient.message_type = OBT_PROP_ATOM(WM_PROTOCOLS);
3768         ce.xclient.display = obt_display;
3769         ce.xclient.window = self->window;
3770         ce.xclient.format = 32;
3771         ce.xclient.data.l[0] = OBT_PROP_ATOM(WM_TAKE_FOCUS);
3772         ce.xclient.data.l[1] = event_curtime;
3773         ce.xclient.data.l[2] = 0l;
3774         ce.xclient.data.l[3] = 0l;
3775         ce.xclient.data.l[4] = 0l;
3776         XSendEvent(obt_display, self->window, FALSE, NoEventMask, &ce);
3777     }
3778
3779     obt_display_ignore_errors(FALSE);
3780
3781     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d",
3782                   obt_display_error_occured);
3783     return !obt_display_error_occured;
3784 }
3785
3786 static void client_present(ObClient *self, gboolean here, gboolean raise,
3787                            gboolean unshade)
3788 {
3789     if (client_normal(self) && screen_showing_desktop)
3790         screen_show_desktop(FALSE, self);
3791     if (self->iconic)
3792         client_iconify(self, FALSE, here, FALSE);
3793     if (self->desktop != DESKTOP_ALL &&
3794         self->desktop != screen_desktop)
3795     {
3796         if (here)
3797             client_set_desktop(self, screen_desktop, FALSE, TRUE);
3798         else
3799             screen_set_desktop(self->desktop, FALSE);
3800     } else if (!self->frame->visible)
3801         /* if its not visible for other reasons, then don't mess
3802            with it */
3803         return;
3804     if (self->shaded && unshade)
3805         client_shade(self, FALSE);
3806     if (raise)
3807         stacking_raise(CLIENT_AS_WINDOW(self));
3808
3809     client_focus(self);
3810 }
3811
3812 /* this function exists to map to the client_activate message in the ewmh,
3813    the user arg is unused because nobody uses it correctly anyway. */
3814 void client_activate(ObClient *self, gboolean here, gboolean raise,
3815                      gboolean unshade, gboolean user)
3816 {
3817     client_present(self, here, raise, unshade);
3818 }
3819
3820 static void client_bring_windows_recursive(ObClient *self,
3821                                            guint desktop,
3822                                            gboolean helpers,
3823                                            gboolean modals,
3824                                            gboolean iconic)
3825 {
3826     GSList *it;
3827
3828     for (it = self->transients; it; it = g_slist_next(it))
3829         client_bring_windows_recursive(it->data, desktop,
3830                                        helpers, modals, iconic);
3831
3832     if (((helpers && client_helper(self)) ||
3833          (modals && self->modal)) &&
3834         ((self->desktop != desktop && self->desktop != DESKTOP_ALL) ||
3835          (iconic && self->iconic)))
3836     {
3837         if (iconic && self->iconic)
3838             client_iconify(self, FALSE, TRUE, FALSE);
3839         else
3840             client_set_desktop(self, desktop, FALSE, FALSE);
3841     }
3842 }
3843
3844 void client_bring_helper_windows(ObClient *self)
3845 {
3846     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
3847 }
3848
3849 void client_bring_modal_windows(ObClient *self)
3850 {
3851     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
3852 }
3853
3854 gboolean client_focused(ObClient *self)
3855 {
3856     return self == focus_client;
3857 }
3858
3859 RrImage* client_icon(ObClient *self)
3860 {
3861     RrImage *ret = NULL;
3862
3863     if (self->icon_set)
3864         ret = self->icon_set;
3865     else if (self->parents) {
3866         GSList *it;
3867         for (it = self->parents; it && !ret; it = g_slist_next(it))
3868             ret = client_icon(it->data);
3869     }
3870     if (!ret)
3871         ret = client_default_icon;
3872     return ret;
3873 }
3874
3875 void client_set_layer(ObClient *self, gint layer)
3876 {
3877     if (layer < 0) {
3878         self->below = TRUE;
3879         self->above = FALSE;
3880     } else if (layer == 0) {
3881         self->below = self->above = FALSE;
3882     } else {
3883         self->below = FALSE;
3884         self->above = TRUE;
3885     }
3886     client_calc_layer(self);
3887     client_change_state(self); /* reflect this in the state hints */
3888 }
3889
3890 void client_set_locked(ObClient *self, gboolean locked)
3891 {
3892     if (self->locked != locked) {
3893         self->locked = locked;
3894         client_change_state(self);
3895     }
3896 }
3897
3898 void client_set_undecorated(ObClient *self, gboolean undecorated)
3899 {
3900     if (self->undecorated != undecorated &&
3901         /* don't let it undecorate if the function is missing, but let
3902            it redecorate */
3903         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3904     {
3905         self->undecorated = undecorated;
3906         client_setup_decor_and_functions(self, TRUE);
3907         client_change_state(self); /* reflect this in the state hints */
3908     }
3909 }
3910
3911 guint client_monitor(ObClient *self)
3912 {
3913     return screen_find_monitor(&self->frame->area);
3914 }
3915
3916 ObClient *client_direct_parent(ObClient *self)
3917 {
3918     if (!self->parents) return NULL;
3919     if (self->transient_for_group) return NULL;
3920     return self->parents->data;
3921 }
3922
3923 ObClient *client_search_top_direct_parent(ObClient *self)
3924 {
3925     ObClient *p;
3926     while ((p = client_direct_parent(self))) self = p;
3927     return self;
3928 }
3929
3930 static GSList *client_search_all_top_parents_internal(ObClient *self,
3931                                                       gboolean bylayer,
3932                                                       ObStackingLayer layer)
3933 {
3934     GSList *ret;
3935     ObClient *p;
3936
3937     /* move up the direct transient chain as far as possible */
3938     while ((p = client_direct_parent(self)) &&
3939            (!bylayer || p->layer == layer))
3940         self = p;
3941
3942     if (!self->parents)
3943         ret = g_slist_prepend(NULL, self);
3944     else
3945         ret = g_slist_copy(self->parents);
3946
3947     return ret;
3948 }
3949
3950 GSList *client_search_all_top_parents(ObClient *self)
3951 {
3952     return client_search_all_top_parents_internal(self, FALSE, 0);
3953 }
3954
3955 GSList *client_search_all_top_parents_layer(ObClient *self)
3956 {
3957     return client_search_all_top_parents_internal(self, TRUE, self->layer);
3958 }
3959
3960 ObClient *client_search_focus_parent(ObClient *self)
3961 {
3962     GSList *it;
3963
3964     for (it = self->parents; it; it = g_slist_next(it))
3965         if (client_focused(it->data)) return it->data;
3966
3967     return NULL;
3968 }
3969
3970 ObClient *client_search_parent(ObClient *self, ObClient *search)
3971 {
3972     GSList *it;
3973
3974     for (it = self->parents; it; it = g_slist_next(it))
3975         if (it->data == search) return search;
3976
3977     return NULL;
3978 }
3979
3980 ObClient *client_search_transient(ObClient *self, ObClient *search)
3981 {
3982     GSList *sit;
3983
3984     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3985         if (sit->data == search)
3986             return search;
3987         if (client_search_transient(sit->data, search))
3988             return search;
3989     }
3990     return NULL;
3991 }
3992
3993 static void detect_edge(Rect area, ObDirection dir,
3994                         gint my_head, gint my_size,
3995                         gint my_edge_start, gint my_edge_size,
3996                         gint *dest, gboolean *near_edge)
3997 {
3998     gint edge_start, edge_size, head, tail;
3999     gboolean skip_head = FALSE, skip_tail = FALSE;
4000
4001     switch (dir) {
4002         case OB_DIRECTION_NORTH:
4003         case OB_DIRECTION_SOUTH:
4004             edge_start = area.x;
4005             edge_size = area.width;
4006             break;
4007         case OB_DIRECTION_EAST:
4008         case OB_DIRECTION_WEST:
4009             edge_start = area.y;
4010             edge_size = area.height;
4011             break;
4012         default:
4013             g_assert_not_reached();
4014     }
4015
4016     /* do we collide with this window? */
4017     if (!RANGES_INTERSECT(my_edge_start, my_edge_size,
4018                 edge_start, edge_size))
4019         return;
4020
4021     switch (dir) {
4022         case OB_DIRECTION_NORTH:
4023             head = RECT_BOTTOM(area);
4024             tail = RECT_TOP(area);
4025             break;
4026         case OB_DIRECTION_SOUTH:
4027             head = RECT_TOP(area);
4028             tail = RECT_BOTTOM(area);
4029             break;
4030         case OB_DIRECTION_WEST:
4031             head = RECT_RIGHT(area);
4032             tail = RECT_LEFT(area);
4033             break;
4034         case OB_DIRECTION_EAST:
4035             head = RECT_LEFT(area);
4036             tail = RECT_RIGHT(area);
4037             break;
4038         default:
4039             g_assert_not_reached();
4040     }
4041     switch (dir) {
4042         case OB_DIRECTION_NORTH:
4043         case OB_DIRECTION_WEST:
4044             /* check if our window is past the head of this window */
4045             if (my_head <= head + 1)
4046                 skip_head = TRUE;
4047             /* check if our window's tail is past the tail of this window */
4048             if (my_head + my_size - 1 <= tail)
4049                 skip_tail = TRUE;
4050             /* check if the head of this window is closer than the previously
4051                chosen edge (take into account that the previously chosen
4052                edge might have been a tail, not a head) */
4053             if (head + (*near_edge ? 0 : my_size) < *dest)
4054                 skip_head = TRUE;
4055             /* check if the tail of this window is closer than the previously
4056                chosen edge (take into account that the previously chosen
4057                edge might have been a head, not a tail) */
4058             if (tail - (!*near_edge ? 0 : my_size) < *dest)
4059                 skip_tail = TRUE;
4060             break;
4061         case OB_DIRECTION_SOUTH:
4062         case OB_DIRECTION_EAST:
4063             /* check if our window is past the head of this window */
4064             if (my_head >= head - 1)
4065                 skip_head = TRUE;
4066             /* check if our window's tail is past the tail of this window */
4067             if (my_head - my_size + 1 >= tail)
4068                 skip_tail = TRUE;
4069             /* check if the head of this window is closer than the previously
4070                chosen edge (take into account that the previously chosen
4071                edge might have been a tail, not a head) */
4072             if (head - (*near_edge ? 0 : my_size) > *dest)
4073                 skip_head = TRUE;
4074             /* check if the tail of this window is closer than the previously
4075                chosen edge (take into account that the previously chosen
4076                edge might have been a head, not a tail) */
4077             if (tail + (!*near_edge ? 0 : my_size) > *dest)
4078                 skip_tail = TRUE;
4079             break;
4080         default:
4081             g_assert_not_reached();
4082     }
4083
4084     ob_debug("my head %d size %d", my_head, my_size);
4085     ob_debug("head %d tail %d deest %d", head, tail, *dest);
4086     if (!skip_head) {
4087         ob_debug("using near edge %d", head);
4088         *dest = head;
4089         *near_edge = TRUE;
4090     }
4091     else if (!skip_tail) {
4092         ob_debug("using far edge %d", tail);
4093         *dest = tail;
4094         *near_edge = FALSE;
4095     }
4096 }
4097
4098 void client_find_edge_directional(ObClient *self, ObDirection dir,
4099                                   gint my_head, gint my_size,
4100                                   gint my_edge_start, gint my_edge_size,
4101                                   gint *dest, gboolean *near_edge)
4102 {
4103     GList *it;
4104     Rect *a, *mon;
4105     Rect dock_area;
4106     gint edge;
4107
4108     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
4109                     &self->frame->area);
4110     mon = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR,
4111                       &self->frame->area);
4112
4113     switch (dir) {
4114     case OB_DIRECTION_NORTH:
4115         if (my_head >= RECT_TOP(*mon) + 1)
4116             edge = RECT_TOP(*mon) - 1;
4117         else
4118             edge = RECT_TOP(*a) - 1;
4119         break;
4120     case OB_DIRECTION_SOUTH:
4121         if (my_head <= RECT_BOTTOM(*mon) - 1)
4122             edge = RECT_BOTTOM(*mon) + 1;
4123         else
4124             edge = RECT_BOTTOM(*a) + 1;
4125         break;
4126     case OB_DIRECTION_EAST:
4127         if (my_head <= RECT_RIGHT(*mon) - 1)
4128             edge = RECT_RIGHT(*mon) + 1;
4129         else
4130             edge = RECT_RIGHT(*a) + 1;
4131         break;
4132     case OB_DIRECTION_WEST:
4133         if (my_head >= RECT_LEFT(*mon) + 1)
4134             edge = RECT_LEFT(*mon) - 1;
4135         else
4136             edge = RECT_LEFT(*a) - 1;
4137         break;
4138     default:
4139         g_assert_not_reached();
4140     }
4141     /* default to the far edge, then narrow it down */
4142     *dest = edge;
4143     *near_edge = TRUE;
4144
4145     for (it = client_list; it; it = g_list_next(it)) {
4146         ObClient *cur = it->data;
4147
4148         /* skip windows to not bump into */
4149         if (cur == self)
4150             continue;
4151         if (cur->iconic)
4152             continue;
4153         if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&
4154             cur->desktop != screen_desktop)
4155             continue;
4156
4157         ob_debug("trying window %s", cur->title);
4158
4159         detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start,
4160                     my_edge_size, dest, near_edge);
4161     }
4162     dock_get_area(&dock_area);
4163     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
4164                 my_edge_size, dest, near_edge);
4165     g_free(a);
4166     g_free(mon);
4167 }
4168
4169 void client_find_move_directional(ObClient *self, ObDirection dir,
4170                                   gint *x, gint *y)
4171 {
4172     gint head, size;
4173     gint e, e_start, e_size;
4174     gboolean near;
4175
4176     switch (dir) {
4177     case OB_DIRECTION_EAST:
4178         head = RECT_RIGHT(self->frame->area);
4179         size = self->frame->area.width;
4180         e_start = RECT_TOP(self->frame->area);
4181         e_size = self->frame->area.height;
4182         break;
4183     case OB_DIRECTION_WEST:
4184         head = RECT_LEFT(self->frame->area);
4185         size = self->frame->area.width;
4186         e_start = RECT_TOP(self->frame->area);
4187         e_size = self->frame->area.height;
4188         break;
4189     case OB_DIRECTION_NORTH:
4190         head = RECT_TOP(self->frame->area);
4191         size = self->frame->area.height;
4192         e_start = RECT_LEFT(self->frame->area);
4193         e_size = self->frame->area.width;
4194         break;
4195     case OB_DIRECTION_SOUTH:
4196         head = RECT_BOTTOM(self->frame->area);
4197         size = self->frame->area.height;
4198         e_start = RECT_LEFT(self->frame->area);
4199         e_size = self->frame->area.width;
4200         break;
4201     default:
4202         g_assert_not_reached();
4203     }
4204
4205     client_find_edge_directional(self, dir, head, size,
4206                                  e_start, e_size, &e, &near);
4207     *x = self->frame->area.x;
4208     *y = self->frame->area.y;
4209     switch (dir) {
4210     case OB_DIRECTION_EAST:
4211         if (near) e -= self->frame->area.width;
4212         else      e++;
4213         *x = e;
4214         break;
4215     case OB_DIRECTION_WEST:
4216         if (near) e++;
4217         else      e -= self->frame->area.width;
4218         *x = e;
4219         break;
4220     case OB_DIRECTION_NORTH:
4221         if (near) e++;
4222         else      e -= self->frame->area.height;
4223         *y = e;
4224         break;
4225     case OB_DIRECTION_SOUTH:
4226         if (near) e -= self->frame->area.height;
4227         else      e++;
4228         *y = e;
4229         break;
4230     default:
4231         g_assert_not_reached();
4232     }
4233     frame_frame_gravity(self->frame, x, y);
4234 }
4235
4236 void client_find_resize_directional(ObClient *self, ObDirection side,
4237                                     gboolean grow,
4238                                     gint *x, gint *y, gint *w, gint *h)
4239 {
4240     gint head;
4241     gint e, e_start, e_size, delta;
4242     gboolean near;
4243     ObDirection dir;
4244
4245     switch (side) {
4246     case OB_DIRECTION_EAST:
4247         head = RECT_RIGHT(self->frame->area) +
4248             (self->size_inc.width - 1) * (grow ? 1 : -1);
4249         e_start = RECT_TOP(self->frame->area);
4250         e_size = self->frame->area.height;
4251         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
4252         break;
4253     case OB_DIRECTION_WEST:
4254         head = RECT_LEFT(self->frame->area) -
4255             (self->size_inc.width - 1) * (grow ? 1 : -1);
4256         e_start = RECT_TOP(self->frame->area);
4257         e_size = self->frame->area.height;
4258         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
4259         break;
4260     case OB_DIRECTION_NORTH:
4261         head = RECT_TOP(self->frame->area) -
4262             (self->size_inc.height - 1) * (grow ? 1 : -1);
4263         e_start = RECT_LEFT(self->frame->area);
4264         e_size = self->frame->area.width;
4265         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
4266         break;
4267     case OB_DIRECTION_SOUTH:
4268         head = RECT_BOTTOM(self->frame->area) +
4269             (self->size_inc.height - 1) * (grow ? 1 : -1);
4270         e_start = RECT_LEFT(self->frame->area);
4271         e_size = self->frame->area.width;
4272         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
4273         break;
4274     default:
4275         g_assert_not_reached();
4276     }
4277
4278     ob_debug("head %d dir %d", head, dir);
4279     client_find_edge_directional(self, dir, head, 1,
4280                                  e_start, e_size, &e, &near);
4281     ob_debug("edge %d", e);
4282     *x = self->frame->area.x;
4283     *y = self->frame->area.y;
4284     *w = self->frame->area.width;
4285     *h = self->frame->area.height;
4286     switch (side) {
4287     case OB_DIRECTION_EAST:
4288         if (grow == near) --e;
4289         delta = e - RECT_RIGHT(self->frame->area);
4290         *w += delta;
4291         break;
4292     case OB_DIRECTION_WEST:
4293         if (grow == near) ++e;
4294         delta = RECT_LEFT(self->frame->area) - e;
4295         *x -= delta;
4296         *w += delta;
4297         break;
4298     case OB_DIRECTION_NORTH:
4299         if (grow == near) ++e;
4300         delta = RECT_TOP(self->frame->area) - e;
4301         *y -= delta;
4302         *h += delta;
4303         break;
4304     case OB_DIRECTION_SOUTH:
4305         if (grow == near) --e;
4306         delta = e - RECT_BOTTOM(self->frame->area);
4307         *h += delta;
4308         break;
4309     default:
4310         g_assert_not_reached();
4311     }
4312     frame_frame_gravity(self->frame, x, y);
4313     *w -= self->frame->size.left + self->frame->size.right;
4314     *h -= self->frame->size.top + self->frame->size.bottom;
4315 }
4316
4317 ObClient* client_under_pointer(void)
4318 {
4319     gint x, y;
4320     GList *it;
4321     ObClient *ret = NULL;
4322
4323     if (screen_pointer_pos(&x, &y)) {
4324         for (it = stacking_list; it; it = g_list_next(it)) {
4325             if (WINDOW_IS_CLIENT(it->data)) {
4326                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4327                 if (c->frame->visible &&
4328                     /* check the desktop, this is done during desktop
4329                        switching and windows are shown/hidden status is not
4330                        reliable */
4331                     (c->desktop == screen_desktop ||
4332                      c->desktop == DESKTOP_ALL) &&
4333                     /* ignore all animating windows */
4334                     !frame_iconify_animating(c->frame) &&
4335                     RECT_CONTAINS(c->frame->area, x, y))
4336                 {
4337                     ret = c;
4338                     break;
4339                 }
4340             }
4341         }
4342     }
4343     return ret;
4344 }
4345
4346 gboolean client_has_group_siblings(ObClient *self)
4347 {
4348     return self->group && self->group->members->next;
4349 }
4350
4351 /*! Returns TRUE if the client is running on the same machine as Openbox */
4352 gboolean client_on_localhost(ObClient *self)
4353 {
4354     return self->client_machine == NULL;
4355 }