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