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