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