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