Merge branch 'backport' into work
[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_mwm_hints(ObClient *self);
84 static void client_get_colormap(ObClient *self);
85 static void client_set_desktop_recursive(ObClient *self,
86                                          guint target,
87                                          gboolean donthide,
88                                          gboolean dontraise);
89 static void client_change_allowed_actions(ObClient *self);
90 static void client_change_state(ObClient *self);
91 static void client_change_wm_state(ObClient *self);
92 static void client_apply_startup_state(ObClient *self,
93                                        gint x, gint y, gint w, gint h);
94 static void client_restore_session_state(ObClient *self);
95 static gboolean client_restore_session_stacking(ObClient *self);
96 static ObAppSettings *client_get_settings_state(ObClient *self);
97 static void client_update_transient_tree(ObClient *self,
98                                          ObGroup *oldgroup, ObGroup *newgroup,
99                                          gboolean oldgtran, gboolean newgtran,
100                                          ObClient* oldparent,
101                                          ObClient *newparent);
102 static void client_present(ObClient *self, gboolean here, gboolean raise,
103                            gboolean unshade);
104 static GSList *client_search_all_top_parents_internal(ObClient *self,
105                                                       gboolean bylayer,
106                                                       ObStackingLayer layer);
107 static void client_call_notifies(ObClient *self, GSList *list);
108 static void client_ping_event(ObClient *self, gboolean dead);
109 static void client_prompt_kill(ObClient *self);
110
111 void client_startup(gboolean reconfig)
112 {
113     if ((client_default_icon = RrImageCacheFind(ob_rr_icons,
114                                                 ob_rr_theme->def_win_icon,
115                                                 ob_rr_theme->def_win_icon_w,
116                                                 ob_rr_theme->def_win_icon_h)))
117         RrImageRef(client_default_icon);
118     else {
119         client_default_icon = RrImageNew(ob_rr_icons);
120         RrImageAddPicture(client_default_icon,
121                           ob_rr_theme->def_win_icon,
122                           ob_rr_theme->def_win_icon_w,
123                           ob_rr_theme->def_win_icon_h);
124     }
125
126     if (reconfig) return;
127
128     client_set_list();
129 }
130
131 void client_shutdown(gboolean reconfig)
132 {
133     RrImageUnref(client_default_icon);
134     client_default_icon = NULL;
135
136     if (reconfig) return;
137 }
138
139 static void client_call_notifies(ObClient *self, GSList *list)
140 {
141     GSList *it;
142
143     for (it = list; it; it = g_slist_next(it)) {
144         ClientCallback *d = it->data;
145         d->func(self, d->data);
146     }
147 }
148
149 void client_add_destroy_notify(ObClientCallback func, gpointer data)
150 {
151     ClientCallback *d = g_new(ClientCallback, 1);
152     d->func = func;
153     d->data = data;
154     client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d);
155 }
156
157 void client_remove_destroy_notify(ObClientCallback func)
158 {
159     GSList *it;
160
161     for (it = client_destroy_notifies; it; it = g_slist_next(it)) {
162         ClientCallback *d = it->data;
163         if (d->func == func) {
164             g_free(d);
165             client_destroy_notifies =
166                 g_slist_delete_link(client_destroy_notifies, it);
167             break;
168         }
169     }
170 }
171
172 void client_set_list(void)
173 {
174     Window *windows, *win_it;
175     GList *it;
176     guint size = g_list_length(client_list);
177
178     /* create an array of the window ids */
179     if (size > 0) {
180         windows = g_new(Window, size);
181         win_it = windows;
182         for (it = client_list; it; it = g_list_next(it), ++win_it)
183             *win_it = ((ObClient*)it->data)->window;
184     } else
185         windows = NULL;
186
187     OBT_PROP_SETA32(obt_root(ob_screen), NET_CLIENT_LIST, WINDOW,
188                     (gulong*)windows, size);
189
190     if (windows)
191         g_free(windows);
192
193     stacking_set_list();
194 }
195
196 void client_manage(Window window, ObPrompt *prompt)
197 {
198     ObClient *self;
199     XSetWindowAttributes attrib_set;
200     gboolean activate = FALSE;
201     ObAppSettings *settings;
202     gboolean transient = FALSE;
203     Rect place, *monitor;
204     Time launch_time, map_time;
205     guint32 user_time;
206
207     ob_debug("Managing window: 0x%lx", window);
208
209     map_time = event_get_server_time();
210
211     /* choose the events we want to receive on the CLIENT window
212        (ObPrompt windows can request events too) */
213     attrib_set.event_mask = CLIENT_EVENTMASK |
214         (prompt ? prompt->event_mask : 0);
215     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
216     XChangeWindowAttributes(obt_display, window,
217                             CWEventMask|CWDontPropagate, &attrib_set);
218
219     /* create the ObClient struct, and populate it from the hints on the
220        window */
221     self = g_new0(ObClient, 1);
222     self->obwin.type = OB_WINDOW_CLASS_CLIENT;
223     self->window = window;
224     self->prompt = prompt;
225
226     /* non-zero defaults */
227     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
228     self->gravity = NorthWestGravity;
229     self->desktop = screen_num_desktops; /* always an invalid value */
230
231     /* get all the stuff off the window */
232     client_get_all(self, TRUE);
233
234     ob_debug("Window type: %d", self->type);
235     ob_debug("Window group: 0x%x", self->group?self->group->leader:0);
236     ob_debug("Window name: %s class: %s", self->name, self->class);
237
238     /* now we have all of the window's information so we can set this up.
239        do this before creating the frame, so it can tell that we are still
240        mapping and doesn't go applying things right away */
241     client_setup_decor_and_functions(self, FALSE);
242
243     /* specify that if we exit, the window should not be destroyed and
244        should be reparented back to root automatically, unless we are managing
245        an internal ObPrompt window  */
246     if (!self->prompt)
247         XChangeSaveSet(obt_display, window, SetModeInsert);
248
249     /* create the decoration frame for the client window */
250     self->frame = frame_new(self);
251
252     frame_grab_client(self->frame);
253
254     /* we've grabbed everything and set everything that we need to at mapping
255        time now */
256     grab_server(FALSE);
257
258     /* per-app settings override stuff from client_get_all, and return the
259        settings for other uses too. the returned settings is a shallow copy,
260        that needs to be freed with g_free(). */
261     settings = client_get_settings_state(self);
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 focuesed: %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 static 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(it->data))) 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 gboolean client_validate(ObClient *self)
3542 {
3543     XEvent e;
3544
3545     XSync(obt_display, FALSE); /* get all events on the server */
3546
3547     if (XCheckTypedWindowEvent(obt_display, self->window, DestroyNotify, &e) ||
3548         XCheckTypedWindowEvent(obt_display, self->window, UnmapNotify, &e))
3549     {
3550         XPutBackEvent(obt_display, &e);
3551         return FALSE;
3552     }
3553
3554     return TRUE;
3555 }
3556
3557 void client_set_wm_state(ObClient *self, glong state)
3558 {
3559     if (state == self->wmstate) return; /* no change */
3560
3561     switch (state) {
3562     case IconicState:
3563         client_iconify(self, TRUE, TRUE, FALSE);
3564         break;
3565     case NormalState:
3566         client_iconify(self, FALSE, TRUE, FALSE);
3567         break;
3568     }
3569 }
3570
3571 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3572 {
3573     gboolean shaded = self->shaded;
3574     gboolean fullscreen = self->fullscreen;
3575     gboolean undecorated = self->undecorated;
3576     gboolean max_horz = self->max_horz;
3577     gboolean max_vert = self->max_vert;
3578     gboolean modal = self->modal;
3579     gboolean iconic = self->iconic;
3580     gboolean demands_attention = self->demands_attention;
3581     gboolean above = self->above;
3582     gboolean below = self->below;
3583     gint i;
3584     gboolean value;
3585
3586     if (!(action == OBT_PROP_ATOM(NET_WM_STATE_ADD) ||
3587           action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) ||
3588           action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)))
3589         /* an invalid action was passed to the client message, ignore it */
3590         return;
3591
3592     for (i = 0; i < 2; ++i) {
3593         Atom state = i == 0 ? data1 : data2;
3594
3595         if (!state) continue;
3596
3597         /* if toggling, then pick whether we're adding or removing */
3598         if (action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)) {
3599             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
3600                 value = modal;
3601             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT))
3602                 value = self->max_vert;
3603             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ))
3604                 value = self->max_horz;
3605             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
3606                 value = shaded;
3607             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
3608                 value = self->skip_taskbar;
3609             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
3610                 value = self->skip_pager;
3611             else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
3612                 value = self->iconic;
3613             else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
3614                 value = fullscreen;
3615             else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
3616                 value = self->above;
3617             else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
3618                 value = self->below;
3619             else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
3620                 value = self->demands_attention;
3621             else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
3622                 value = undecorated;
3623             action = value ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3624                              OBT_PROP_ATOM(NET_WM_STATE_ADD);
3625         }
3626
3627         value = action == OBT_PROP_ATOM(NET_WM_STATE_ADD);
3628         if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3629             modal = value;
3630         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3631             max_vert = value;
3632         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3633             max_horz = value;
3634         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3635             shaded = value;
3636         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3637             self->skip_taskbar = value;
3638         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3639             self->skip_pager = value;
3640         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3641             iconic = value;
3642         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3643             fullscreen = value;
3644         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3645             above = value;
3646             /* only unset below when setting above, otherwise you can't get to
3647                the normal layer */
3648             if (value)
3649                 below = FALSE;
3650         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3651             /* and vice versa */
3652             if (value)
3653                 above = FALSE;
3654             below = value;
3655         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3656             demands_attention = value;
3657         } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3658             undecorated = value;
3659         }
3660     }
3661
3662     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3663         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3664             /* toggling both */
3665             if (max_horz == max_vert) { /* both going the same way */
3666                 client_maximize(self, max_horz, 0);
3667             } else {
3668                 client_maximize(self, max_horz, 1);
3669                 client_maximize(self, max_vert, 2);
3670             }
3671         } else {
3672             /* toggling one */
3673             if (max_horz != self->max_horz)
3674                 client_maximize(self, max_horz, 1);
3675             else
3676                 client_maximize(self, max_vert, 2);
3677         }
3678     }
3679     /* change fullscreen state before shading, as it will affect if the window
3680        can shade or not */
3681     if (fullscreen != self->fullscreen)
3682         client_fullscreen(self, fullscreen);
3683     if (shaded != self->shaded)
3684         client_shade(self, shaded);
3685     if (undecorated != self->undecorated)
3686         client_set_undecorated(self, undecorated);
3687     if (above != self->above || below != self->below) {
3688         self->above = above;
3689         self->below = below;
3690         client_calc_layer(self);
3691     }
3692
3693     if (modal != self->modal) {
3694         self->modal = modal;
3695         /* when a window changes modality, then its stacking order with its
3696            transients needs to change */
3697         stacking_raise(CLIENT_AS_WINDOW(self));
3698
3699         /* it also may get focused. if something is focused that shouldn't
3700            be focused anymore, then move the focus */
3701         if (focus_client && client_focus_target(focus_client) != focus_client)
3702             client_focus(focus_client);
3703     }
3704
3705     if (iconic != self->iconic)
3706         client_iconify(self, iconic, FALSE, FALSE);
3707
3708     if (demands_attention != self->demands_attention)
3709         client_hilite(self, demands_attention);
3710
3711     client_change_state(self); /* change the hint to reflect these changes */
3712 }
3713
3714 ObClient *client_focus_target(ObClient *self)
3715 {
3716     ObClient *child = NULL;
3717
3718     child = client_search_modal_child(self);
3719     if (child) return child;
3720     return self;
3721 }
3722
3723 gboolean client_can_focus(ObClient *self)
3724 {
3725     /* choose the correct target */
3726     self = client_focus_target(self);
3727
3728     if (!self->frame->visible)
3729         return FALSE;
3730
3731     if (!(self->can_focus || self->focus_notify))
3732         return FALSE;
3733
3734     return TRUE;
3735 }
3736
3737 gboolean client_focus(ObClient *self)
3738 {
3739     /* we might not focus this window, so if we have modal children which would
3740        be focused instead, bring them to this desktop */
3741     client_bring_modal_windows(self);
3742
3743     /* choose the correct target */
3744     self = client_focus_target(self);
3745
3746     if (!client_can_focus(self)) {
3747         ob_debug_type(OB_DEBUG_FOCUS,
3748                       "Client %s can't be focused", self->title);
3749         return FALSE;
3750     }
3751
3752     ob_debug_type(OB_DEBUG_FOCUS,
3753                   "Focusing client \"%s\" (0x%x) at time %u",
3754                   self->title, self->window, event_curtime);
3755
3756     /* if using focus_delay, stop the timer now so that focus doesn't
3757        go moving on us */
3758     event_halt_focus_delay();
3759
3760     event_cancel_all_key_grabs();
3761
3762     obt_display_ignore_errors(TRUE);
3763
3764     if (self->can_focus) {
3765         /* This can cause a BadMatch error with CurrentTime, or if an app
3766            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3767         XSetInputFocus(obt_display, self->window, RevertToPointerRoot,
3768                        event_curtime);
3769     }
3770
3771     if (self->focus_notify) {
3772         XEvent ce;
3773         ce.xclient.type = ClientMessage;
3774         ce.xclient.message_type = OBT_PROP_ATOM(WM_PROTOCOLS);
3775         ce.xclient.display = obt_display;
3776         ce.xclient.window = self->window;
3777         ce.xclient.format = 32;
3778         ce.xclient.data.l[0] = OBT_PROP_ATOM(WM_TAKE_FOCUS);
3779         ce.xclient.data.l[1] = event_curtime;
3780         ce.xclient.data.l[2] = 0l;
3781         ce.xclient.data.l[3] = 0l;
3782         ce.xclient.data.l[4] = 0l;
3783         XSendEvent(obt_display, self->window, FALSE, NoEventMask, &ce);
3784     }
3785
3786     obt_display_ignore_errors(FALSE);
3787
3788     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d",
3789                   obt_display_error_occured);
3790     return !obt_display_error_occured;
3791 }
3792
3793 static void client_present(ObClient *self, gboolean here, gboolean raise,
3794                            gboolean unshade)
3795 {
3796     if (client_normal(self) && screen_showing_desktop)
3797         screen_show_desktop(FALSE, self);
3798     if (self->iconic)
3799         client_iconify(self, FALSE, here, FALSE);
3800     if (self->desktop != DESKTOP_ALL &&
3801         self->desktop != screen_desktop)
3802     {
3803         if (here)
3804             client_set_desktop(self, screen_desktop, FALSE, TRUE);
3805         else
3806             screen_set_desktop(self->desktop, FALSE);
3807     } else if (!self->frame->visible)
3808         /* if its not visible for other reasons, then don't mess
3809            with it */
3810         return;
3811     if (self->shaded && unshade)
3812         client_shade(self, FALSE);
3813     if (raise)
3814         stacking_raise(CLIENT_AS_WINDOW(self));
3815
3816     client_focus(self);
3817 }
3818
3819 /* this function exists to map to the net_active_window message in the ewmh */
3820 void client_activate(ObClient *self, gboolean here, gboolean raise,
3821                      gboolean unshade, gboolean user)
3822 {
3823     if (user || (self->desktop == DESKTOP_ALL ||
3824                  self->desktop == screen_desktop))
3825         client_present(self, here, raise, unshade);
3826     else
3827         client_hilite(self, TRUE);
3828 }
3829
3830 static void client_bring_windows_recursive(ObClient *self,
3831                                            guint desktop,
3832                                            gboolean helpers,
3833                                            gboolean modals,
3834                                            gboolean iconic)
3835 {
3836     GSList *it;
3837
3838     for (it = self->transients; it; it = g_slist_next(it))
3839         client_bring_windows_recursive(it->data, desktop,
3840                                        helpers, modals, iconic);
3841
3842     if (((helpers && client_helper(self)) ||
3843          (modals && self->modal)) &&
3844         ((self->desktop != desktop && self->desktop != DESKTOP_ALL) ||
3845          (iconic && self->iconic)))
3846     {
3847         if (iconic && self->iconic)
3848             client_iconify(self, FALSE, TRUE, FALSE);
3849         else
3850             client_set_desktop(self, desktop, FALSE, FALSE);
3851     }
3852 }
3853
3854 void client_bring_helper_windows(ObClient *self)
3855 {
3856     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
3857 }
3858
3859 void client_bring_modal_windows(ObClient *self)
3860 {
3861     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
3862 }
3863
3864 gboolean client_focused(ObClient *self)
3865 {
3866     return self == focus_client;
3867 }
3868
3869 RrImage* client_icon(ObClient *self)
3870 {
3871     RrImage *ret = NULL;
3872
3873     if (self->icon_set)
3874         ret = self->icon_set;
3875     else if (self->parents) {
3876         GSList *it;
3877         for (it = self->parents; it && !ret; it = g_slist_next(it))
3878             ret = client_icon(it->data);
3879     }
3880     if (!ret)
3881         ret = client_default_icon;
3882     return ret;
3883 }
3884
3885 void client_set_layer(ObClient *self, gint layer)
3886 {
3887     if (layer < 0) {
3888         self->below = TRUE;
3889         self->above = FALSE;
3890     } else if (layer == 0) {
3891         self->below = self->above = FALSE;
3892     } else {
3893         self->below = FALSE;
3894         self->above = TRUE;
3895     }
3896     client_calc_layer(self);
3897     client_change_state(self); /* reflect this in the state hints */
3898 }
3899
3900 void client_set_undecorated(ObClient *self, gboolean undecorated)
3901 {
3902     if (self->undecorated != undecorated &&
3903         /* don't let it undecorate if the function is missing, but let
3904            it redecorate */
3905         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3906     {
3907         self->undecorated = undecorated;
3908         client_setup_decor_and_functions(self, TRUE);
3909         client_change_state(self); /* reflect this in the state hints */
3910
3911         hooks_queue((undecorated ?
3912                      OB_HOOK_WIN_UNDECORATED : OB_HOOK_WIN_DECORATED), self);
3913     }
3914 }
3915
3916 guint client_monitor(ObClient *self)
3917 {
3918     return screen_find_monitor(&self->frame->area);
3919 }
3920
3921 ObClient *client_direct_parent(ObClient *self)
3922 {
3923     if (!self->parents) return NULL;
3924     if (self->transient_for_group) return NULL;
3925     return self->parents->data;
3926 }
3927
3928 ObClient *client_search_top_direct_parent(ObClient *self)
3929 {
3930     ObClient *p;
3931     while ((p = client_direct_parent(self))) self = p;
3932     return self;
3933 }
3934
3935 static GSList *client_search_all_top_parents_internal(ObClient *self,
3936                                                       gboolean bylayer,
3937                                                       ObStackingLayer layer)
3938 {
3939     GSList *ret;
3940     ObClient *p;
3941
3942     /* move up the direct transient chain as far as possible */
3943     while ((p = client_direct_parent(self)) &&
3944            (!bylayer || p->layer == layer))
3945         self = p;
3946
3947     if (!self->parents)
3948         ret = g_slist_prepend(NULL, self);
3949     else
3950         ret = g_slist_copy(self->parents);
3951
3952     return ret;
3953 }
3954
3955 GSList *client_search_all_top_parents(ObClient *self)
3956 {
3957     return client_search_all_top_parents_internal(self, FALSE, 0);
3958 }
3959
3960 GSList *client_search_all_top_parents_layer(ObClient *self)
3961 {
3962     return client_search_all_top_parents_internal(self, TRUE, self->layer);
3963 }
3964
3965 ObClient *client_search_focus_parent(ObClient *self)
3966 {
3967     GSList *it;
3968
3969     for (it = self->parents; it; it = g_slist_next(it))
3970         if (client_focused(it->data)) return it->data;
3971
3972     return NULL;
3973 }
3974
3975 ObClient *client_search_focus_parent_full(ObClient *self)
3976 {
3977     GSList *it;
3978     ObClient *ret = NULL;
3979
3980     for (it = self->parents; it; it = g_slist_next(it)) {
3981         if (client_focused(it->data))
3982             ret = it->data;
3983         else
3984             ret = client_search_focus_parent_full(it->data);
3985         if (ret) break;
3986     }
3987     return ret;
3988 }
3989
3990 ObClient *client_search_parent(ObClient *self, ObClient *search)
3991 {
3992     GSList *it;
3993
3994     for (it = self->parents; it; it = g_slist_next(it))
3995         if (it->data == search) return search;
3996
3997     return NULL;
3998 }
3999
4000 ObClient *client_search_transient(ObClient *self, ObClient *search)
4001 {
4002     GSList *sit;
4003
4004     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
4005         if (sit->data == search)
4006             return search;
4007         if (client_search_transient(sit->data, search))
4008             return search;
4009     }
4010     return NULL;
4011 }
4012
4013 static void detect_edge(Rect area, ObDirection dir,
4014                         gint my_head, gint my_size,
4015                         gint my_edge_start, gint my_edge_size,
4016                         gint *dest, gboolean *near_edge)
4017 {
4018     gint edge_start, edge_size, head, tail;
4019     gboolean skip_head = FALSE, skip_tail = FALSE;
4020
4021     switch (dir) {
4022         case OB_DIRECTION_NORTH:
4023         case OB_DIRECTION_SOUTH:
4024             edge_start = area.x;
4025             edge_size = area.width;
4026             break;
4027         case OB_DIRECTION_EAST:
4028         case OB_DIRECTION_WEST:
4029             edge_start = area.y;
4030             edge_size = area.height;
4031             break;
4032         default:
4033             g_assert_not_reached();
4034     }
4035
4036     /* do we collide with this window? */
4037     if (!RANGES_INTERSECT(my_edge_start, my_edge_size,
4038                 edge_start, edge_size))
4039         return;
4040
4041     switch (dir) {
4042         case OB_DIRECTION_NORTH:
4043             head = RECT_BOTTOM(area);
4044             tail = RECT_TOP(area);
4045             break;
4046         case OB_DIRECTION_SOUTH:
4047             head = RECT_TOP(area);
4048             tail = RECT_BOTTOM(area);
4049             break;
4050         case OB_DIRECTION_WEST:
4051             head = RECT_RIGHT(area);
4052             tail = RECT_LEFT(area);
4053             break;
4054         case OB_DIRECTION_EAST:
4055             head = RECT_LEFT(area);
4056             tail = RECT_RIGHT(area);
4057             break;
4058         default:
4059             g_assert_not_reached();
4060     }
4061     switch (dir) {
4062         case OB_DIRECTION_NORTH:
4063         case OB_DIRECTION_WEST:
4064             /* check if our window is past the head of this window */
4065             if (my_head <= head + 1)
4066                 skip_head = TRUE;
4067             /* check if our window's tail is past the tail of this window */
4068             if (my_head + my_size - 1 <= tail)
4069                 skip_tail = TRUE;
4070             /* check if the head of this window is closer than the previously
4071                chosen edge (take into account that the previously chosen
4072                edge might have been a tail, not a head) */
4073             if (head + (*near_edge ? 0 : my_size) <= *dest)
4074                 skip_head = TRUE;
4075             /* check if the tail of this window is closer than the previously
4076                chosen edge (take into account that the previously chosen
4077                edge might have been a head, not a tail) */
4078             if (tail - (!*near_edge ? 0 : my_size) <= *dest)
4079                 skip_tail = TRUE;
4080             break;
4081         case OB_DIRECTION_SOUTH:
4082         case OB_DIRECTION_EAST:
4083             /* check if our window is past the head of this window */
4084             if (my_head >= head - 1)
4085                 skip_head = TRUE;
4086             /* check if our window's tail is past the tail of this window */
4087             if (my_head - my_size + 1 >= tail)
4088                 skip_tail = TRUE;
4089             /* check if the head of this window is closer than the previously
4090                chosen edge (take into account that the previously chosen
4091                edge might have been a tail, not a head) */
4092             if (head - (*near_edge ? 0 : my_size) >= *dest)
4093                 skip_head = TRUE;
4094             /* check if the tail of this window is closer than the previously
4095                chosen edge (take into account that the previously chosen
4096                edge might have been a head, not a tail) */
4097             if (tail + (!*near_edge ? 0 : my_size) >= *dest)
4098                 skip_tail = TRUE;
4099             break;
4100         default:
4101             g_assert_not_reached();
4102     }
4103
4104     ob_debug("my head %d size %d", my_head, my_size);
4105     ob_debug("head %d tail %d dest %d", head, tail, *dest);
4106     if (!skip_head) {
4107         ob_debug("using near edge %d", head);
4108         *dest = head;
4109         *near_edge = TRUE;
4110     }
4111     else if (!skip_tail) {
4112         ob_debug("using far edge %d", tail);
4113         *dest = tail;
4114         *near_edge = FALSE;
4115     }
4116 }
4117
4118 void client_find_edge_directional(ObClient *self, ObDirection dir,
4119                                   gint my_head, gint my_size,
4120                                   gint my_edge_start, gint my_edge_size,
4121                                   gint *dest, gboolean *near_edge)
4122 {
4123     GList *it;
4124     Rect *a, *mon;
4125     Rect dock_area;
4126     gint edge;
4127
4128     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
4129                     &self->frame->area);
4130     mon = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR,
4131                       &self->frame->area);
4132
4133     switch (dir) {
4134     case OB_DIRECTION_NORTH:
4135         if (my_head >= RECT_TOP(*mon) + 1)
4136             edge = RECT_TOP(*mon) - 1;
4137         else
4138             edge = RECT_TOP(*a) - 1;
4139         break;
4140     case OB_DIRECTION_SOUTH:
4141         if (my_head <= RECT_BOTTOM(*mon) - 1)
4142             edge = RECT_BOTTOM(*mon) + 1;
4143         else
4144             edge = RECT_BOTTOM(*a) + 1;
4145         break;
4146     case OB_DIRECTION_EAST:
4147         if (my_head <= RECT_RIGHT(*mon) - 1)
4148             edge = RECT_RIGHT(*mon) + 1;
4149         else
4150             edge = RECT_RIGHT(*a) + 1;
4151         break;
4152     case OB_DIRECTION_WEST:
4153         if (my_head >= RECT_LEFT(*mon) + 1)
4154             edge = RECT_LEFT(*mon) - 1;
4155         else
4156             edge = RECT_LEFT(*a) - 1;
4157         break;
4158     default:
4159         g_assert_not_reached();
4160     }
4161     /* default to the far edge, then narrow it down */
4162     *dest = edge;
4163     *near_edge = TRUE;
4164
4165     for (it = client_list; it; it = g_list_next(it)) {
4166         ObClient *cur = it->data;
4167
4168         /* skip windows to not bump into */
4169         if (cur == self)
4170             continue;
4171         if (cur->iconic)
4172             continue;
4173         if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&
4174             cur->desktop != screen_desktop)
4175             continue;
4176
4177         ob_debug("trying window %s", cur->title);
4178
4179         detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start,
4180                     my_edge_size, dest, near_edge);
4181     }
4182     dock_get_area(&dock_area);
4183     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
4184                 my_edge_size, dest, near_edge);
4185     g_free(a);
4186     g_free(mon);
4187 }
4188
4189 void client_find_move_directional(ObClient *self, ObDirection dir,
4190                                   gint *x, gint *y)
4191 {
4192     gint head, size;
4193     gint e, e_start, e_size;
4194     gboolean near;
4195
4196     switch (dir) {
4197     case OB_DIRECTION_EAST:
4198         head = RECT_RIGHT(self->frame->area);
4199         size = self->frame->area.width;
4200         e_start = RECT_TOP(self->frame->area);
4201         e_size = self->frame->area.height;
4202         break;
4203     case OB_DIRECTION_WEST:
4204         head = RECT_LEFT(self->frame->area);
4205         size = self->frame->area.width;
4206         e_start = RECT_TOP(self->frame->area);
4207         e_size = self->frame->area.height;
4208         break;
4209     case OB_DIRECTION_NORTH:
4210         head = RECT_TOP(self->frame->area);
4211         size = self->frame->area.height;
4212         e_start = RECT_LEFT(self->frame->area);
4213         e_size = self->frame->area.width;
4214         break;
4215     case OB_DIRECTION_SOUTH:
4216         head = RECT_BOTTOM(self->frame->area);
4217         size = self->frame->area.height;
4218         e_start = RECT_LEFT(self->frame->area);
4219         e_size = self->frame->area.width;
4220         break;
4221     default:
4222         g_assert_not_reached();
4223     }
4224
4225     client_find_edge_directional(self, dir, head, size,
4226                                  e_start, e_size, &e, &near);
4227     *x = self->frame->area.x;
4228     *y = self->frame->area.y;
4229     switch (dir) {
4230     case OB_DIRECTION_EAST:
4231         if (near) e -= self->frame->area.width;
4232         else      e++;
4233         *x = e;
4234         break;
4235     case OB_DIRECTION_WEST:
4236         if (near) e++;
4237         else      e -= self->frame->area.width;
4238         *x = e;
4239         break;
4240     case OB_DIRECTION_NORTH:
4241         if (near) e++;
4242         else      e -= self->frame->area.height;
4243         *y = e;
4244         break;
4245     case OB_DIRECTION_SOUTH:
4246         if (near) e -= self->frame->area.height;
4247         else      e++;
4248         *y = e;
4249         break;
4250     default:
4251         g_assert_not_reached();
4252     }
4253     frame_frame_gravity(self->frame, x, y);
4254 }
4255
4256 void client_find_resize_directional(ObClient *self, ObDirection side,
4257                                     gboolean grow,
4258                                     gint *x, gint *y, gint *w, gint *h)
4259 {
4260     gint head;
4261     gint e, e_start, e_size, delta;
4262     gboolean near;
4263     ObDirection dir;
4264
4265     switch (side) {
4266     case OB_DIRECTION_EAST:
4267         head = RECT_RIGHT(self->frame->area) +
4268             (self->size_inc.width - 1) * (grow ? 1 : 0);
4269         e_start = RECT_TOP(self->frame->area);
4270         e_size = self->frame->area.height;
4271         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
4272         break;
4273     case OB_DIRECTION_WEST:
4274         head = RECT_LEFT(self->frame->area) -
4275             (self->size_inc.width - 1) * (grow ? 1 : 0);
4276         e_start = RECT_TOP(self->frame->area);
4277         e_size = self->frame->area.height;
4278         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
4279         break;
4280     case OB_DIRECTION_NORTH:
4281         head = RECT_TOP(self->frame->area) -
4282             (self->size_inc.height - 1) * (grow ? 1 : 0);
4283         e_start = RECT_LEFT(self->frame->area);
4284         e_size = self->frame->area.width;
4285         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
4286         break;
4287     case OB_DIRECTION_SOUTH:
4288         head = RECT_BOTTOM(self->frame->area) +
4289             (self->size_inc.height - 1) * (grow ? 1 : 0);
4290         e_start = RECT_LEFT(self->frame->area);
4291         e_size = self->frame->area.width;
4292         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
4293         break;
4294     default:
4295         g_assert_not_reached();
4296     }
4297
4298     ob_debug("head %d dir %d", head, dir);
4299     client_find_edge_directional(self, dir, head, 1,
4300                                  e_start, e_size, &e, &near);
4301     ob_debug("edge %d", e);
4302     *x = self->frame->area.x;
4303     *y = self->frame->area.y;
4304     *w = self->frame->area.width;
4305     *h = self->frame->area.height;
4306     switch (side) {
4307     case OB_DIRECTION_EAST:
4308         if (grow == near) --e;
4309         delta = e - RECT_RIGHT(self->frame->area);
4310         *w += delta;
4311         break;
4312     case OB_DIRECTION_WEST:
4313         if (grow == near) ++e;
4314         delta = RECT_LEFT(self->frame->area) - e;
4315         *x -= delta;
4316         *w += delta;
4317         break;
4318     case OB_DIRECTION_NORTH:
4319         if (grow == near) ++e;
4320         delta = RECT_TOP(self->frame->area) - e;
4321         *y -= delta;
4322         *h += delta;
4323         break;
4324     case OB_DIRECTION_SOUTH:
4325         if (grow == near) --e;
4326         delta = e - RECT_BOTTOM(self->frame->area);
4327         *h += delta;
4328         break;
4329     default:
4330         g_assert_not_reached();
4331     }
4332     frame_frame_gravity(self->frame, x, y);
4333     *w -= self->frame->size.left + self->frame->size.right;
4334     *h -= self->frame->size.top + self->frame->size.bottom;
4335 }
4336
4337 ObClient* client_under_pointer(void)
4338 {
4339     gint x, y;
4340     GList *it;
4341     ObClient *ret = NULL;
4342
4343     if (screen_pointer_pos(&x, &y)) {
4344         for (it = stacking_list; it; it = g_list_next(it)) {
4345             if (WINDOW_IS_CLIENT(it->data)) {
4346                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4347                 if (c->frame->visible &&
4348                     /* check the desktop, this is done during desktop
4349                        switching and windows are shown/hidden status is not
4350                        reliable */
4351                     (c->desktop == screen_desktop ||
4352                      c->desktop == DESKTOP_ALL) &&
4353                     /* ignore all animating windows */
4354                     !frame_iconify_animating(c->frame) &&
4355                     RECT_CONTAINS(c->frame->area, x, y))
4356                 {
4357                     ret = c;
4358                     break;
4359                 }
4360             }
4361         }
4362     }
4363     return ret;
4364 }
4365
4366 gboolean client_has_group_siblings(ObClient *self)
4367 {
4368     return self->group && self->group->members->next;
4369 }
4370
4371 /*! Returns TRUE if the client is running on the same machine as Openbox */
4372 gboolean client_on_localhost(ObClient *self)
4373 {
4374     return self->client_machine == NULL;
4375 }