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