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