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