Use the KeyCode to directly find the modifier mask. (Fix bug 5173)
[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              (gint)G_MAXUSHORT
2934              - self->frame->size.left - self->frame->size.right);
2935     *h = MIN(*h,
2936              (gint)G_MAXUSHORT
2937              - self->frame->size.top - self->frame->size.bottom);
2938
2939     /* gets the frame's position */
2940     frame_client_gravity(self->frame, x, y);
2941
2942     /* these positions are frame positions, not client positions */
2943
2944     /* set the size and position if fullscreen */
2945     if (self->fullscreen) {
2946         const Rect *a;
2947         guint i;
2948
2949         i = screen_find_monitor(&desired);
2950         a = screen_physical_area_monitor(i);
2951
2952         *x = a->x;
2953         *y = a->y;
2954         *w = a->width;
2955         *h = a->height;
2956
2957         user = FALSE; /* ignore if the client can't be moved/resized when it
2958                          is fullscreening */
2959     } else if (self->max_horz || self->max_vert) {
2960         Rect *a;
2961         guint i;
2962
2963         /* use all possible struts when maximizing to the full screen */
2964         i = screen_find_monitor(&desired);
2965         a = screen_area(self->desktop, i,
2966                         (self->max_horz && self->max_vert ? NULL : &desired));
2967
2968         /* set the size and position if maximized */
2969         if (self->max_horz) {
2970             *x = a->x;
2971             *w = a->width - self->frame->size.left - self->frame->size.right;
2972         }
2973         if (self->max_vert) {
2974             *y = a->y;
2975             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2976         }
2977
2978         user = FALSE; /* ignore if the client can't be moved/resized when it
2979                          is maximizing */
2980
2981         g_slice_free(Rect, a);
2982     }
2983
2984     /* gets the client's position */
2985     frame_frame_gravity(self->frame, x, y);
2986
2987     /* work within the preferred sizes given by the window, these may have
2988        changed rather than it's requested width and height, so always run
2989        through this code */
2990     {
2991         gint basew, baseh, minw, minh;
2992         gint incw, inch, maxw, maxh;
2993         gfloat minratio, maxratio;
2994
2995         incw = self->size_inc.width;
2996         inch = self->size_inc.height;
2997         minratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2998             0 : self->min_ratio;
2999         maxratio = self->fullscreen || (self->max_horz && self->max_vert) ?
3000             0 : self->max_ratio;
3001
3002         /* base size is substituted with min size if not specified */
3003         if (self->base_size.width >= 0 || self->base_size.height >= 0) {
3004             basew = self->base_size.width;
3005             baseh = self->base_size.height;
3006         } else {
3007             basew = self->min_size.width;
3008             baseh = self->min_size.height;
3009         }
3010         /* min size is substituted with base size if not specified */
3011         if (self->min_size.width || self->min_size.height) {
3012             minw = self->min_size.width;
3013             minh = self->min_size.height;
3014         } else {
3015             minw = self->base_size.width;
3016             minh = self->base_size.height;
3017         }
3018
3019         /* This comment is no longer true */
3020         /* if this is a user-requested resize, then check against min/max
3021            sizes */
3022
3023         /* smaller than min size or bigger than max size? */
3024         if (*w > self->max_size.width) *w = self->max_size.width;
3025         if (*w < minw) *w = minw;
3026         if (*h > self->max_size.height) *h = self->max_size.height;
3027         if (*h < minh) *h = minh;
3028
3029         *w -= basew;
3030         *h -= baseh;
3031
3032         /* the sizes to used for maximized */
3033         maxw = *w;
3034         maxh = *h;
3035
3036         /* keep to the increments */
3037         *w /= incw;
3038         *h /= inch;
3039
3040         /* you cannot resize to nothing */
3041         if (basew + *w < 1) *w = 1 - basew;
3042         if (baseh + *h < 1) *h = 1 - baseh;
3043
3044         /* save the logical size */
3045         *logicalw = incw > 1 ? *w : *w + basew;
3046         *logicalh = inch > 1 ? *h : *h + baseh;
3047
3048         *w *= incw;
3049         *h *= inch;
3050
3051         /* if maximized/fs then don't use the size increments */
3052         if (self->fullscreen || self->max_horz) *w = maxw;
3053         if (self->fullscreen || self->max_vert) *h = maxh;
3054
3055         *w += basew;
3056         *h += baseh;
3057
3058         /* adjust the height to match the width for the aspect ratios.
3059            for this, min size is not substituted for base size ever. */
3060         *w -= self->base_size.width;
3061         *h -= self->base_size.height;
3062
3063         if (minratio)
3064             if (*h * minratio > *w) {
3065                 *h = (gint)(*w / minratio);
3066
3067                 /* you cannot resize to nothing */
3068                 if (*h < 1) {
3069                     *h = 1;
3070                     *w = (gint)(*h * minratio);
3071                 }
3072             }
3073         if (maxratio)
3074             if (*h * maxratio < *w) {
3075                 *h = (gint)(*w / maxratio);
3076
3077                 /* you cannot resize to nothing */
3078                 if (*h < 1) {
3079                     *h = 1;
3080                     *w = (gint)(*h * minratio);
3081                 }
3082             }
3083
3084         *w += self->base_size.width;
3085         *h += self->base_size.height;
3086     }
3087
3088     /* these override the above states! if you cant move you can't move! */
3089     if (user) {
3090         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
3091             *x = self->area.x;
3092             *y = self->area.y;
3093         }
3094         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
3095             *w = self->area.width;
3096             *h = self->area.height;
3097         }
3098     }
3099
3100     g_assert(*w > 0);
3101     g_assert(*h > 0);
3102 }
3103
3104 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
3105                       gboolean user, gboolean final, gboolean force_reply)
3106 {
3107     Rect oldframe, oldclient;
3108     gboolean send_resize_client;
3109     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
3110     gboolean fmoved, fresized;
3111     guint fdecor = self->frame->decorations;
3112     gboolean fhorz = self->frame->max_horz;
3113     gboolean fvert = self->frame->max_vert;
3114     gint logicalw, logicalh;
3115
3116     /* find the new x, y, width, and height (and logical size) */
3117     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
3118
3119     /* set the logical size if things changed */
3120     if (!(w == self->area.width && h == self->area.height))
3121         SIZE_SET(self->logical_size, logicalw, logicalh);
3122
3123     /* figure out if we moved or resized or what */
3124     moved = (x != self->area.x || y != self->area.y);
3125     resized = (w != self->area.width || h != self->area.height);
3126
3127     oldframe = self->frame->area;
3128     oldclient = self->area;
3129     RECT_SET(self->area, x, y, w, h);
3130
3131     /* for app-requested resizes, always resize if 'resized' is true.
3132        for user-requested ones, only resize if final is true, or when
3133        resizing in redraw mode */
3134     send_resize_client = ((!user && resized) ||
3135                           (user && (final ||
3136                                     (resized && config_resize_redraw))));
3137
3138     /* if the client is enlarging, then resize the client before the frame */
3139     if (send_resize_client && (w > oldclient.width || h > oldclient.height)) {
3140         XMoveResizeWindow(obt_display, self->window,
3141                           self->frame->size.left, self->frame->size.top,
3142                           MAX(w, oldclient.width), MAX(h, oldclient.height));
3143         frame_adjust_client_area(self->frame);
3144     }
3145
3146     /* find the frame's dimensions and move/resize it */
3147     fmoved = moved;
3148     fresized = resized;
3149
3150     /* if decorations changed, then readjust everything for the frame */
3151     if (self->decorations != fdecor ||
3152         self->max_horz != fhorz || self->max_vert != fvert)
3153     {
3154         fmoved = fresized = TRUE;
3155     }
3156
3157     /* adjust the frame */
3158     if (fmoved || fresized) {
3159         gulong ignore_start;
3160         if (!user)
3161             ignore_start = event_start_ignore_all_enters();
3162
3163         /* replay pending pointer event before move the window, in case it
3164            would change what window gets the event */
3165         mouse_replay_pointer();
3166
3167         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
3168
3169         if (!user)
3170             event_end_ignore_all_enters(ignore_start);
3171     }
3172
3173     if (!user || final) {
3174         gint oldrx = self->root_pos.x;
3175         gint oldry = self->root_pos.y;
3176         /* we have reset the client to 0 border width, so don't include
3177            it in these coords */
3178         POINT_SET(self->root_pos,
3179                   self->frame->area.x + self->frame->size.left -
3180                   self->border_width,
3181                   self->frame->area.y + self->frame->size.top -
3182                   self->border_width);
3183         if (self->root_pos.x != oldrx || self->root_pos.y != oldry)
3184             rootmoved = TRUE;
3185     }
3186
3187     /* This is kinda tricky and should not be changed.. let me explain!
3188
3189        When user = FALSE, then the request is coming from the application
3190        itself, and we are more strict about when to send a synthetic
3191        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
3192        in this case (or send one if force_reply is true)
3193
3194        When user = TRUE, then the request is coming from "us", like when we
3195        maximize a window or something.  In this case we are more lenient.  We
3196        used to follow the same rules as above, but _Java_ Swing can't handle
3197        this. So just to appease Swing, when user = TRUE, we always send
3198        a synthetic ConfigureNotify to give the window its root coordinates.
3199        Lastly, if force_reply is TRUE, we always send a
3200        ConfigureNotify, which is needed during a resize with XSYNCronization.
3201     */
3202     if ((!user && !resized && (rootmoved || force_reply)) ||
3203         (user && ((!resized && force_reply) || (final && rootmoved))))
3204     {
3205         XEvent event;
3206
3207         event.type = ConfigureNotify;
3208         event.xconfigure.display = obt_display;
3209         event.xconfigure.event = self->window;
3210         event.xconfigure.window = self->window;
3211
3212         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d",
3213                  self->title, self->root_pos.x, self->root_pos.y, w, h);
3214
3215         /* root window real coords */
3216         event.xconfigure.x = self->root_pos.x;
3217         event.xconfigure.y = self->root_pos.y;
3218         event.xconfigure.width = w;
3219         event.xconfigure.height = h;
3220         event.xconfigure.border_width = self->border_width;
3221         event.xconfigure.above = None;
3222         event.xconfigure.override_redirect = FALSE;
3223         XSendEvent(event.xconfigure.display, event.xconfigure.window,
3224                    FALSE, StructureNotifyMask, &event);
3225     }
3226
3227     /* if the client is shrinking, then resize the frame before the client.
3228
3229        both of these resize sections may run, because the top one only resizes
3230        in the direction that is growing
3231      */
3232     if (send_resize_client && (w <= oldclient.width || h <= oldclient.height))
3233     {
3234         frame_adjust_client_area(self->frame);
3235         XMoveResizeWindow(obt_display, self->window,
3236                           self->frame->size.left, self->frame->size.top, w, h);
3237     }
3238
3239     XFlush(obt_display);
3240
3241     /* if it moved between monitors, then this can affect the stacking
3242        layer of this window or others - for fullscreen windows.
3243        also if it changed to/from oldschool fullscreen then its layer may
3244        change
3245
3246        watch out tho, don't try change stacking stuff if the window is no
3247        longer being managed !
3248     */
3249     if (self->managed &&
3250         (screen_find_monitor(&self->frame->area) !=
3251          screen_find_monitor(&oldframe) ||
3252          (final && (client_is_oldfullscreen(self, &oldclient) !=
3253                     client_is_oldfullscreen(self, &self->area)))))
3254     {
3255         client_calc_layer(self);
3256     }
3257 }
3258
3259 void client_fullscreen(ObClient *self, gboolean fs)
3260 {
3261     gint x, y, w, h;
3262
3263     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
3264         self->fullscreen == fs) return;                   /* already done */
3265
3266     self->fullscreen = fs;
3267     client_change_state(self); /* change the state hints on the client */
3268
3269     if (fs) {
3270         self->pre_fullscreen_area = self->area;
3271         self->pre_fullscreen_max_horz = self->max_horz;
3272         self->pre_fullscreen_max_vert = self->max_vert;
3273
3274         /* if the window is maximized, its area isn't all that meaningful.
3275            save its premax area instead. */
3276         if (self->max_horz) {
3277             self->pre_fullscreen_area.x = self->pre_max_area.x;
3278             self->pre_fullscreen_area.width = self->pre_max_area.width;
3279         }
3280         if (self->max_vert) {
3281             self->pre_fullscreen_area.y = self->pre_max_area.y;
3282             self->pre_fullscreen_area.height = self->pre_max_area.height;
3283         }
3284
3285         /* these will help configure_full figure out where to fullscreen
3286            the window */
3287         x = self->area.x;
3288         y = self->area.y;
3289         w = self->area.width;
3290         h = self->area.height;
3291     } else {
3292         g_assert(self->pre_fullscreen_area.width > 0 &&
3293                  self->pre_fullscreen_area.height > 0);
3294
3295         self->max_horz = self->pre_fullscreen_max_horz;
3296         self->max_vert = self->pre_fullscreen_max_vert;
3297         if (self->max_horz) {
3298             self->pre_max_area.x = self->pre_fullscreen_area.x;
3299             self->pre_max_area.width = self->pre_fullscreen_area.width;
3300         }
3301         if (self->max_vert) {
3302             self->pre_max_area.y = self->pre_fullscreen_area.y;
3303             self->pre_max_area.height = self->pre_fullscreen_area.height;
3304         }
3305
3306         x = self->pre_fullscreen_area.x;
3307         y = self->pre_fullscreen_area.y;
3308         w = self->pre_fullscreen_area.width;
3309         h = self->pre_fullscreen_area.height;
3310         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3311     }
3312
3313     ob_debug("Window %s going fullscreen (%d)",
3314              self->title, self->fullscreen);
3315
3316     if (fs) {
3317         /* make sure the window is on some monitor */
3318         client_find_onscreen(self, &x, &y, w, h, FALSE);
3319     }
3320
3321     client_setup_decor_and_functions(self, FALSE);
3322     client_move_resize(self, x, y, w, h);
3323
3324     /* and adjust our layer/stacking. do this after resizing the window,
3325        and applying decorations, because windows which fill the screen are
3326        considered "fullscreen" and it affects their layer */
3327     client_calc_layer(self);
3328
3329     if (fs) {
3330         /* try focus us when we go into fullscreen mode */
3331         client_focus(self);
3332     }
3333 }
3334
3335 static void client_iconify_recursive(ObClient *self,
3336                                      gboolean iconic, gboolean curdesk,
3337                                      gboolean hide_animation)
3338 {
3339     GSList *it;
3340     gboolean changed = FALSE;
3341
3342     if (self->iconic != iconic) {
3343         ob_debug("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
3344                  self->window);
3345
3346         if (iconic) {
3347             /* don't let non-normal windows iconify along with their parents
3348                or whatever */
3349             if (client_normal(self)) {
3350                 self->iconic = iconic;
3351
3352                 /* update the focus lists.. iconic windows go to the bottom of
3353                    the list. this will also call focus_cycle_addremove(). */
3354                 focus_order_to_bottom(self);
3355
3356                 changed = TRUE;
3357             }
3358         } else {
3359             self->iconic = iconic;
3360
3361             if (curdesk && self->desktop != screen_desktop &&
3362                 self->desktop != DESKTOP_ALL)
3363                 client_set_desktop(self, screen_desktop, FALSE, FALSE);
3364
3365             /* this puts it after the current focused window, this will
3366                also cause focus_cycle_addremove() to be called for the
3367                client */
3368             focus_order_like_new(self);
3369
3370             changed = TRUE;
3371         }
3372     }
3373
3374     if (changed) {
3375         client_change_state(self);
3376         if (config_animate_iconify && !hide_animation)
3377             frame_begin_iconify_animation(self->frame, iconic);
3378         /* do this after starting the animation so it doesn't flash */
3379         client_showhide(self);
3380     }
3381
3382     /* iconify all direct transients, and deiconify all transients
3383        (non-direct too) */
3384     for (it = self->transients; it; it = g_slist_next(it))
3385         if (it->data != self)
3386             if (client_is_direct_child(self, it->data) || !iconic)
3387                 client_iconify_recursive(it->data, iconic, curdesk,
3388                                          hide_animation);
3389 }
3390
3391 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3392                     gboolean hide_animation)
3393 {
3394     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3395         /* move up the transient chain as far as possible first */
3396         self = client_search_top_direct_parent(self);
3397         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3398     }
3399 }
3400
3401 void client_maximize(ObClient *self, gboolean max, gint dir)
3402 {
3403     gint x, y, w, h;
3404
3405     g_assert(dir == 0 || dir == 1 || dir == 2);
3406     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && max) return;/* can't */
3407
3408     /* check if already done */
3409     if (max) {
3410         if (dir == 0 && self->max_horz && self->max_vert) return;
3411         if (dir == 1 && self->max_horz) return;
3412         if (dir == 2 && self->max_vert) return;
3413     } else {
3414         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3415         if (dir == 1 && !self->max_horz) return;
3416         if (dir == 2 && !self->max_vert) return;
3417     }
3418
3419     /* these will help configure_full figure out which screen to fill with
3420        the window */
3421     x = self->area.x;
3422     y = self->area.y;
3423     w = self->area.width;
3424     h = self->area.height;
3425
3426     if (max) {
3427         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3428             RECT_SET(self->pre_max_area,
3429                      self->area.x, self->pre_max_area.y,
3430                      self->area.width, self->pre_max_area.height);
3431         }
3432         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3433             RECT_SET(self->pre_max_area,
3434                      self->pre_max_area.x, self->area.y,
3435                      self->pre_max_area.width, self->area.height);
3436         }
3437     } else {
3438         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3439             g_assert(self->pre_max_area.width > 0);
3440
3441             x = self->pre_max_area.x;
3442             w = self->pre_max_area.width;
3443
3444             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3445                      0, self->pre_max_area.height);
3446         }
3447         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3448             g_assert(self->pre_max_area.height > 0);
3449
3450             y = self->pre_max_area.y;
3451             h = self->pre_max_area.height;
3452
3453             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3454                      self->pre_max_area.width, 0);
3455         }
3456     }
3457
3458     if (dir == 0 || dir == 1) /* horz */
3459         self->max_horz = max;
3460     if (dir == 0 || dir == 2) /* vert */
3461         self->max_vert = max;
3462
3463     if (max) {
3464         /* make sure the window is on some monitor */
3465         client_find_onscreen(self, &x, &y, w, h, FALSE);
3466     }
3467
3468     client_change_state(self); /* change the state hints on the client */
3469
3470     client_setup_decor_and_functions(self, FALSE);
3471     client_move_resize(self, x, y, w, h);
3472 }
3473
3474 void client_shade(ObClient *self, gboolean shade)
3475 {
3476     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3477          shade) ||                         /* can't shade */
3478         self->shaded == shade) return;     /* already done */
3479
3480     self->shaded = shade;
3481     client_change_state(self);
3482     client_change_wm_state(self); /* the window is being hidden/shown */
3483     /* resize the frame to just the titlebar */
3484     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
3485 }
3486
3487 static void client_ping_event(ObClient *self, gboolean dead)
3488 {
3489     if (self->not_responding != dead) {
3490         self->not_responding = dead;
3491         client_update_title(self);
3492
3493         if (dead)
3494             /* the client isn't responding, so ask to kill it */
3495             client_prompt_kill(self);
3496         else {
3497             /* it came back to life ! */
3498
3499             if (self->kill_prompt) {
3500                 prompt_unref(self->kill_prompt);
3501                 self->kill_prompt = NULL;
3502             }
3503
3504             self->kill_level = 0;
3505         }
3506     }
3507 }
3508
3509 void client_close(ObClient *self)
3510 {
3511     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3512
3513     /* if closing an internal obprompt, that is just cancelling it */
3514     if (self->prompt) {
3515         prompt_cancel(self->prompt);
3516         return;
3517     }
3518
3519     /* in the case that the client provides no means to requesting that it
3520        close, we just kill it */
3521     if (!self->delete_window)
3522         /* don't use client_kill(), we should only kill based on PID in
3523            response to a lack of PING replies */
3524         XKillClient(obt_display, self->window);
3525     else {
3526         /* request the client to close with WM_DELETE_WINDOW */
3527         OBT_PROP_MSG_TO(self->window, self->window, WM_PROTOCOLS,
3528                         OBT_PROP_ATOM(WM_DELETE_WINDOW), event_time(),
3529                         0, 0, 0, NoEventMask);
3530
3531         /* we're trying to close the window, so see if it is responding. if it
3532            is not, then we will let them kill the window */
3533         if (self->ping)
3534             ping_start(self, client_ping_event);
3535
3536         /* if we already know the window isn't responding (maybe they clicked
3537            no in the kill dialog but it hasn't come back to life), then show
3538            the kill dialog */
3539         if (self->not_responding)
3540             client_prompt_kill(self);
3541     }
3542 }
3543
3544 #define OB_KILL_RESULT_NO 0
3545 #define OB_KILL_RESULT_YES 1
3546
3547 static gboolean client_kill_requested(ObPrompt *p, gint result, gpointer data)
3548 {
3549     ObClient *self = data;
3550
3551     if (result == OB_KILL_RESULT_YES)
3552         client_kill(self);
3553     return TRUE; /* call the cleanup func */
3554 }
3555
3556 static void client_kill_cleanup(ObPrompt *p, gpointer data)
3557 {
3558     ObClient *self = data;
3559
3560     g_assert(p == self->kill_prompt);
3561
3562     prompt_unref(self->kill_prompt);
3563     self->kill_prompt = NULL;
3564 }
3565
3566 static void client_prompt_kill(ObClient *self)
3567 {
3568     /* check if we're already prompting */
3569     if (!self->kill_prompt) {
3570         ObPromptAnswer answers[] = {
3571             { 0, OB_KILL_RESULT_NO },
3572             { 0, OB_KILL_RESULT_YES }
3573         };
3574         gchar *m;
3575         const gchar *y, *title;
3576
3577         title = self->original_title;
3578         if (title[0] == '\0') {
3579             /* empty string, so use its parent */
3580             ObClient *p = client_search_top_direct_parent(self);
3581             if (p) title = p->original_title;
3582         }
3583
3584         if (client_on_localhost(self)) {
3585             const gchar *sig;
3586
3587             if (self->kill_level == 0)
3588                 sig = "terminate";
3589             else
3590                 sig = "kill";
3591
3592             m = g_strdup_printf
3593                 (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"),
3594                  title, sig);
3595             y = _("End Process");
3596         }
3597         else {
3598             m = g_strdup_printf
3599                 (_("The window \"%s\" does not seem to be responding.  Do you want to disconnect it from the X server?"),
3600                  title);
3601             y = _("Disconnect");
3602         }
3603         /* set the dialog buttons' text */
3604         answers[0].text = _("Cancel");  /* "no" */
3605         answers[1].text = y;            /* "yes" */
3606
3607         self->kill_prompt = prompt_new(m, NULL, answers,
3608                                        sizeof(answers)/sizeof(answers[0]),
3609                                        OB_KILL_RESULT_NO, /* default = no */
3610                                        OB_KILL_RESULT_NO, /* cancel = no */
3611                                        client_kill_requested,
3612                                        client_kill_cleanup,
3613                                        self);
3614         g_free(m);
3615     }
3616
3617     prompt_show(self->kill_prompt, self, TRUE);
3618 }
3619
3620 void client_kill(ObClient *self)
3621 {
3622     /* don't kill our own windows */
3623     if (self->prompt) return;
3624
3625     if (client_on_localhost(self) && self->pid) {
3626         /* running on the local host */
3627         if (self->kill_level == 0) {
3628             ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
3629                      self->window, self->pid);
3630             kill(self->pid, SIGTERM);
3631             ++self->kill_level;
3632
3633             /* show that we're trying to kill it */
3634             client_update_title(self);
3635         }
3636         else {
3637             ob_debug("killing window 0x%x with pid %lu, with SIGKILL",
3638                      self->window, self->pid);
3639             kill(self->pid, SIGKILL); /* kill -9 */
3640         }
3641     }
3642     else {
3643         /* running on a remote host */
3644         XKillClient(obt_display, self->window);
3645     }
3646 }
3647
3648 void client_hilite(ObClient *self, gboolean hilite)
3649 {
3650     if (self->demands_attention == hilite)
3651         return; /* no change */
3652
3653     /* don't allow focused windows to hilite */
3654     self->demands_attention = hilite && !client_focused(self);
3655     if (self->frame != NULL) { /* if we're mapping, just set the state */
3656         if (self->demands_attention) {
3657             frame_flash_start(self->frame);
3658
3659             /* if the window is on another desktop then raise it and make it
3660                the most recently used window */
3661             if (self->desktop != screen_desktop &&
3662                 self->desktop != DESKTOP_ALL)
3663             {
3664                 stacking_raise(CLIENT_AS_WINDOW(self));
3665                 focus_order_to_top(self);
3666             }
3667         }
3668         else
3669             frame_flash_stop(self->frame);
3670         client_change_state(self);
3671     }
3672 }
3673
3674 static void client_set_desktop_recursive(ObClient *self,
3675                                          guint target,
3676                                          gboolean donthide,
3677                                          gboolean dontraise)
3678 {
3679     guint old;
3680     GSList *it;
3681
3682     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3683
3684         ob_debug("Setting desktop %u", target+1);
3685
3686         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3687
3688         old = self->desktop;
3689         self->desktop = target;
3690         OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, target);
3691         /* the frame can display the current desktop state */
3692         frame_adjust_state(self->frame);
3693         /* 'move' the window to the new desktop */
3694         if (!donthide)
3695             client_hide(self);
3696         client_show(self);
3697         /* raise if it was not already on the desktop */
3698         if (old != DESKTOP_ALL && !dontraise)
3699             stacking_raise(CLIENT_AS_WINDOW(self));
3700         if (STRUT_EXISTS(self->strut))
3701             screen_update_areas();
3702         else
3703             /* the new desktop's geometry may be different, so we may need to
3704                resize, for example if we are maximized */
3705             client_reconfigure(self, FALSE);
3706
3707         focus_cycle_addremove(self, FALSE);
3708     }
3709
3710     /* move all transients */
3711     for (it = self->transients; it; it = g_slist_next(it))
3712         if (it->data != self)
3713             if (client_is_direct_child(self, it->data))
3714                 client_set_desktop_recursive(it->data, target,
3715                                              donthide, dontraise);
3716 }
3717
3718 void client_set_desktop(ObClient *self, guint target,
3719                         gboolean donthide, gboolean dontraise)
3720 {
3721     self = client_search_top_direct_parent(self);
3722     client_set_desktop_recursive(self, target, donthide, dontraise);
3723
3724     focus_cycle_addremove(NULL, TRUE);
3725 }
3726
3727 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3728 {
3729     while (child != parent && (child = client_direct_parent(child)));
3730     return child == parent;
3731 }
3732
3733 ObClient *client_search_modal_child(ObClient *self)
3734 {
3735     GSList *it;
3736     ObClient *ret;
3737
3738     for (it = self->transients; it; it = g_slist_next(it)) {
3739         ObClient *c = it->data;
3740         if ((ret = client_search_modal_child(c))) return ret;
3741         if (c->modal) return c;
3742     }
3743     return NULL;
3744 }
3745
3746 struct ObClientFindDestroyUnmap {
3747     Window window;
3748     gint ignore_unmaps;
3749 };
3750
3751 static gboolean find_destroy_unmap(XEvent *e, gpointer data)
3752 {
3753     struct ObClientFindDestroyUnmap *find = data;
3754     if (e->type == DestroyNotify)
3755         return e->xdestroywindow.window == find->window;
3756     if (e->type == UnmapNotify && e->xunmap.window == find->window)
3757         /* ignore the first $find->ignore_unmaps$ many unmap events */
3758         return --find->ignore_unmaps < 0;
3759     return FALSE;
3760 }
3761
3762 gboolean client_validate(ObClient *self)
3763 {
3764     struct ObClientFindDestroyUnmap find;
3765
3766     XSync(obt_display, FALSE); /* get all events on the server */
3767
3768     find.window = self->window;
3769     find.ignore_unmaps = self->ignore_unmaps;
3770     if (xqueue_exists_local(find_destroy_unmap, &find))
3771         return FALSE;
3772
3773     return TRUE;
3774 }
3775
3776 void client_set_wm_state(ObClient *self, glong state)
3777 {
3778     if (state == self->wmstate) return; /* no change */
3779
3780     switch (state) {
3781     case IconicState:
3782         client_iconify(self, TRUE, TRUE, FALSE);
3783         break;
3784     case NormalState:
3785         client_iconify(self, FALSE, TRUE, FALSE);
3786         break;
3787     }
3788 }
3789
3790 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3791 {
3792     gboolean shaded = self->shaded;
3793     gboolean fullscreen = self->fullscreen;
3794     gboolean undecorated = self->undecorated;
3795     gboolean max_horz = self->max_horz;
3796     gboolean max_vert = self->max_vert;
3797     gboolean modal = self->modal;
3798     gboolean iconic = self->iconic;
3799     gboolean demands_attention = self->demands_attention;
3800     gboolean above = self->above;
3801     gboolean below = self->below;
3802     gint i;
3803     gboolean value;
3804
3805     if (!(action == OBT_PROP_ATOM(NET_WM_STATE_ADD) ||
3806           action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) ||
3807           action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)))
3808         /* an invalid action was passed to the client message, ignore it */
3809         return;
3810
3811     for (i = 0; i < 2; ++i) {
3812         Atom state = i == 0 ? data1 : data2;
3813
3814         if (!state) continue;
3815
3816         /* if toggling, then pick whether we're adding or removing */
3817         if (action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)) {
3818             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
3819                 value = modal;
3820             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT))
3821                 value = self->max_vert;
3822             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ))
3823                 value = self->max_horz;
3824             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
3825                 value = shaded;
3826             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
3827                 value = self->skip_taskbar;
3828             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
3829                 value = self->skip_pager;
3830             else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
3831                 value = self->iconic;
3832             else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
3833                 value = fullscreen;
3834             else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
3835                 value = self->above;
3836             else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
3837                 value = self->below;
3838             else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
3839                 value = self->demands_attention;
3840             else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
3841                 value = undecorated;
3842             else
3843                 g_assert_not_reached();
3844             action = value ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3845                              OBT_PROP_ATOM(NET_WM_STATE_ADD);
3846         }
3847
3848         value = action == OBT_PROP_ATOM(NET_WM_STATE_ADD);
3849         if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3850             modal = value;
3851         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3852             max_vert = value;
3853         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3854             max_horz = value;
3855         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3856             shaded = value;
3857         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3858             self->skip_taskbar = value;
3859         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3860             self->skip_pager = value;
3861         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3862             iconic = value;
3863         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3864             fullscreen = value;
3865         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3866             above = value;
3867             /* only unset below when setting above, otherwise you can't get to
3868                the normal layer */
3869             if (value)
3870                 below = FALSE;
3871         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3872             /* and vice versa */
3873             if (value)
3874                 above = FALSE;
3875             below = value;
3876         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3877             demands_attention = value;
3878         } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3879             undecorated = value;
3880         }
3881     }
3882
3883     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3884         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3885             /* toggling both */
3886             if (max_horz == max_vert) { /* both going the same way */
3887                 client_maximize(self, max_horz, 0);
3888             } else {
3889                 client_maximize(self, max_horz, 1);
3890                 client_maximize(self, max_vert, 2);
3891             }
3892         } else {
3893             /* toggling one */
3894             if (max_horz != self->max_horz)
3895                 client_maximize(self, max_horz, 1);
3896             else
3897                 client_maximize(self, max_vert, 2);
3898         }
3899     }
3900     /* change fullscreen state before shading, as it will affect if the window
3901        can shade or not */
3902     if (fullscreen != self->fullscreen)
3903         client_fullscreen(self, fullscreen);
3904     if (shaded != self->shaded)
3905         client_shade(self, shaded);
3906     if (undecorated != self->undecorated)
3907         client_set_undecorated(self, undecorated);
3908     if (above != self->above || below != self->below) {
3909         self->above = above;
3910         self->below = below;
3911         client_calc_layer(self);
3912     }
3913
3914     if (modal != self->modal) {
3915         self->modal = modal;
3916         /* when a window changes modality, then its stacking order with its
3917            transients needs to change */
3918         stacking_raise(CLIENT_AS_WINDOW(self));
3919
3920         /* it also may get focused. if something is focused that shouldn't
3921            be focused anymore, then move the focus */
3922         if (focus_client && client_focus_target(focus_client) != focus_client)
3923             client_focus(focus_client);
3924     }
3925
3926     if (iconic != self->iconic)
3927         client_iconify(self, iconic, FALSE, FALSE);
3928
3929     if (demands_attention != self->demands_attention)
3930         client_hilite(self, demands_attention);
3931
3932     client_change_state(self); /* change the hint to reflect these changes */
3933
3934     focus_cycle_addremove(self, TRUE);
3935 }
3936
3937 ObClient *client_focus_target(ObClient *self)
3938 {
3939     ObClient *child = NULL;
3940
3941     child = client_search_modal_child(self);
3942     if (child) return child;
3943     return self;
3944 }
3945
3946 gboolean client_can_focus(ObClient *self)
3947 {
3948     /* choose the correct target */
3949     self = client_focus_target(self);
3950
3951     if (!self->frame->visible)
3952         return FALSE;
3953
3954     if (!(self->can_focus || self->focus_notify))
3955         return FALSE;
3956
3957     return TRUE;
3958 }
3959
3960 gboolean client_focus(ObClient *self)
3961 {
3962     if (!client_validate(self)) return FALSE;
3963
3964     /* we might not focus this window, so if we have modal children which would
3965        be focused instead, bring them to this desktop */
3966     client_bring_modal_windows(self);
3967
3968     /* choose the correct target */
3969     self = client_focus_target(self);
3970
3971     if (!client_can_focus(self)) {
3972         ob_debug_type(OB_DEBUG_FOCUS,
3973                       "Client %s can't be focused", self->title);
3974         return FALSE;
3975     }
3976
3977     /* if we have helper windows they should be there with the window */
3978     client_bring_helper_windows(self);
3979
3980     ob_debug_type(OB_DEBUG_FOCUS,
3981                   "Focusing client \"%s\" (0x%x) at time %u",
3982                   self->title, self->window, event_time());
3983
3984     /* if using focus_delay, stop the timer now so that focus doesn't
3985        go moving on us */
3986     event_halt_focus_delay();
3987
3988     obt_display_ignore_errors(TRUE);
3989
3990     if (self->can_focus) {
3991         /* This can cause a BadMatch error with CurrentTime, or if an app
3992            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3993         XSetInputFocus(obt_display, self->window, RevertToPointerRoot,
3994                        event_time());
3995     }
3996
3997     if (self->focus_notify) {
3998         XEvent ce;
3999         ce.xclient.type = ClientMessage;
4000         ce.xclient.message_type = OBT_PROP_ATOM(WM_PROTOCOLS);
4001         ce.xclient.display = obt_display;
4002         ce.xclient.window = self->window;
4003         ce.xclient.format = 32;
4004         ce.xclient.data.l[0] = OBT_PROP_ATOM(WM_TAKE_FOCUS);
4005         ce.xclient.data.l[1] = event_time();
4006         ce.xclient.data.l[2] = 0l;
4007         ce.xclient.data.l[3] = 0l;
4008         ce.xclient.data.l[4] = 0l;
4009         XSendEvent(obt_display, self->window, FALSE, NoEventMask, &ce);
4010     }
4011
4012     obt_display_ignore_errors(FALSE);
4013
4014     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d",
4015                   obt_display_error_occured);
4016     return !obt_display_error_occured;
4017 }
4018
4019 static void client_present(ObClient *self, gboolean here, gboolean raise,
4020                            gboolean unshade)
4021 {
4022     if (client_normal(self) && screen_showing_desktop)
4023         screen_show_desktop(FALSE, self);
4024     if (self->iconic)
4025         client_iconify(self, FALSE, here, FALSE);
4026     if (self->desktop != DESKTOP_ALL &&
4027         self->desktop != screen_desktop)
4028     {
4029         if (here)
4030             client_set_desktop(self, screen_desktop, FALSE, TRUE);
4031         else
4032             screen_set_desktop(self->desktop, FALSE);
4033     } else if (!self->frame->visible)
4034         /* if its not visible for other reasons, then don't mess
4035            with it */
4036         return;
4037     if (self->shaded && unshade)
4038         client_shade(self, FALSE);
4039     if (raise)
4040         stacking_raise(CLIENT_AS_WINDOW(self));
4041
4042     client_focus(self);
4043 }
4044
4045 /* this function exists to map to the net_active_window message in the ewmh */
4046 void client_activate(ObClient *self, gboolean desktop,
4047                      gboolean here, gboolean raise,
4048                      gboolean unshade, gboolean user)
4049 {
4050     self = client_focus_target(self);
4051
4052     if (client_can_steal_focus(self, desktop, user, event_time(), CurrentTime))
4053         client_present(self, here, raise, unshade);
4054     else
4055         client_hilite(self, TRUE);
4056 }
4057
4058 static void client_bring_windows_recursive(ObClient *self,
4059                                            guint desktop,
4060                                            gboolean helpers,
4061                                            gboolean modals,
4062                                            gboolean iconic)
4063 {
4064     GSList *it;
4065
4066     for (it = self->transients; it; it = g_slist_next(it))
4067         client_bring_windows_recursive(it->data, desktop,
4068                                        helpers, modals, iconic);
4069
4070     if (((helpers && client_helper(self)) ||
4071          (modals && self->modal)) &&
4072         (!screen_compare_desktops(self->desktop, desktop) ||
4073          (iconic && self->iconic)))
4074     {
4075         if (iconic && self->iconic)
4076             client_iconify(self, FALSE, TRUE, FALSE);
4077         else
4078             client_set_desktop(self, desktop, FALSE, FALSE);
4079     }
4080 }
4081
4082 void client_bring_helper_windows(ObClient *self)
4083 {
4084     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
4085 }
4086
4087 void client_bring_modal_windows(ObClient *self)
4088 {
4089     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
4090 }
4091
4092 gboolean client_focused(ObClient *self)
4093 {
4094     return self == focus_client;
4095 }
4096
4097 RrImage* client_icon(ObClient *self)
4098 {
4099     RrImage *ret = NULL;
4100
4101     if (self->icon_set)
4102         ret = self->icon_set;
4103     else if (self->parents) {
4104         GSList *it;
4105         for (it = self->parents; it && !ret; it = g_slist_next(it))
4106             ret = client_icon(it->data);
4107     }
4108     if (!ret)
4109         ret = client_default_icon;
4110     return ret;
4111 }
4112
4113 void client_set_layer(ObClient *self, gint layer)
4114 {
4115     if (layer < 0) {
4116         self->below = TRUE;
4117         self->above = FALSE;
4118     } else if (layer == 0) {
4119         self->below = self->above = FALSE;
4120     } else {
4121         self->below = FALSE;
4122         self->above = TRUE;
4123     }
4124     client_calc_layer(self);
4125     client_change_state(self); /* reflect this in the state hints */
4126 }
4127
4128 void client_set_undecorated(ObClient *self, gboolean undecorated)
4129 {
4130     if (self->undecorated != undecorated &&
4131         /* don't let it undecorate if the function is missing, but let
4132            it redecorate */
4133         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
4134     {
4135         self->undecorated = undecorated;
4136         client_setup_decor_and_functions(self, TRUE);
4137         client_change_state(self); /* reflect this in the state hints */
4138     }
4139 }
4140
4141 guint client_monitor(ObClient *self)
4142 {
4143     return screen_find_monitor(&self->frame->area);
4144 }
4145
4146 ObClient *client_direct_parent(ObClient *self)
4147 {
4148     if (!self->parents) return NULL;
4149     if (self->transient_for_group) return NULL;
4150     return self->parents->data;
4151 }
4152
4153 ObClient *client_search_top_direct_parent(ObClient *self)
4154 {
4155     ObClient *p;
4156     while ((p = client_direct_parent(self))) self = p;
4157     return self;
4158 }
4159
4160 static GSList *client_search_all_top_parents_internal(ObClient *self,
4161                                                       gboolean bylayer,
4162                                                       ObStackingLayer layer)
4163 {
4164     GSList *ret;
4165     ObClient *p;
4166
4167     /* move up the direct transient chain as far as possible */
4168     while ((p = client_direct_parent(self)) &&
4169            (!bylayer || p->layer == layer))
4170         self = p;
4171
4172     if (!self->parents)
4173         ret = g_slist_prepend(NULL, self);
4174     else
4175         ret = g_slist_copy(self->parents);
4176
4177     return ret;
4178 }
4179
4180 GSList *client_search_all_top_parents(ObClient *self)
4181 {
4182     return client_search_all_top_parents_internal(self, FALSE, 0);
4183 }
4184
4185 GSList *client_search_all_top_parents_layer(ObClient *self)
4186 {
4187     return client_search_all_top_parents_internal(self, TRUE, self->layer);
4188 }
4189
4190 ObClient *client_search_focus_parent(ObClient *self)
4191 {
4192     GSList *it;
4193
4194     for (it = self->parents; it; it = g_slist_next(it))
4195         if (client_focused(it->data)) return it->data;
4196
4197     return NULL;
4198 }
4199
4200 ObClient *client_search_focus_parent_full(ObClient *self)
4201 {
4202     GSList *it;
4203     ObClient *ret = NULL;
4204
4205     for (it = self->parents; it; it = g_slist_next(it)) {
4206         if (client_focused(it->data))
4207             ret = it->data;
4208         else
4209             ret = client_search_focus_parent_full(it->data);
4210         if (ret) break;
4211     }
4212     return ret;
4213 }
4214
4215 ObClient *client_search_parent(ObClient *self, ObClient *search)
4216 {
4217     GSList *it;
4218
4219     for (it = self->parents; it; it = g_slist_next(it))
4220         if (it->data == search) return search;
4221
4222     return NULL;
4223 }
4224
4225 ObClient *client_search_transient(ObClient *self, ObClient *search)
4226 {
4227     GSList *sit;
4228
4229     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
4230         if (sit->data == search)
4231             return search;
4232         if (client_search_transient(sit->data, search))
4233             return search;
4234     }
4235     return NULL;
4236 }
4237
4238 static void detect_edge(Rect area, ObDirection dir,
4239                         gint my_head, gint my_size,
4240                         gint my_edge_start, gint my_edge_size,
4241                         gint *dest, gboolean *near_edge)
4242 {
4243     gint edge_start, edge_size, head, tail;
4244     gboolean skip_head = FALSE, skip_tail = FALSE;
4245
4246     switch (dir) {
4247         case OB_DIRECTION_NORTH:
4248         case OB_DIRECTION_SOUTH:
4249             edge_start = area.x;
4250             edge_size = area.width;
4251             break;
4252         case OB_DIRECTION_EAST:
4253         case OB_DIRECTION_WEST:
4254             edge_start = area.y;
4255             edge_size = area.height;
4256             break;
4257         default:
4258             g_assert_not_reached();
4259     }
4260
4261     /* do we collide with this window? */
4262     if (!RANGES_INTERSECT(my_edge_start, my_edge_size,
4263                 edge_start, edge_size))
4264         return;
4265
4266     switch (dir) {
4267         case OB_DIRECTION_NORTH:
4268             head = RECT_BOTTOM(area);
4269             tail = RECT_TOP(area);
4270             break;
4271         case OB_DIRECTION_SOUTH:
4272             head = RECT_TOP(area);
4273             tail = RECT_BOTTOM(area);
4274             break;
4275         case OB_DIRECTION_WEST:
4276             head = RECT_RIGHT(area);
4277             tail = RECT_LEFT(area);
4278             break;
4279         case OB_DIRECTION_EAST:
4280             head = RECT_LEFT(area);
4281             tail = RECT_RIGHT(area);
4282             break;
4283         default:
4284             g_assert_not_reached();
4285     }
4286     switch (dir) {
4287         case OB_DIRECTION_NORTH:
4288         case OB_DIRECTION_WEST:
4289             /* check if our window is past the head of this window */
4290             if (my_head <= head + 1)
4291                 skip_head = TRUE;
4292             /* check if our window's tail is past the tail of this window */
4293             if (my_head + my_size - 1 <= tail)
4294                 skip_tail = TRUE;
4295             /* check if the head of this window is closer than the previously
4296                chosen edge (take into account that the previously chosen
4297                edge might have been a tail, not a head) */
4298             if (head + (*near_edge ? 0 : my_size) <= *dest)
4299                 skip_head = TRUE;
4300             /* check if the tail of this window is closer than the previously
4301                chosen edge (take into account that the previously chosen
4302                edge might have been a head, not a tail) */
4303             if (tail - (!*near_edge ? 0 : my_size) <= *dest)
4304                 skip_tail = TRUE;
4305             break;
4306         case OB_DIRECTION_SOUTH:
4307         case OB_DIRECTION_EAST:
4308             /* check if our window is past the head of this window */
4309             if (my_head >= head - 1)
4310                 skip_head = TRUE;
4311             /* check if our window's tail is past the tail of this window */
4312             if (my_head - my_size + 1 >= tail)
4313                 skip_tail = TRUE;
4314             /* check if the head of this window is closer than the previously
4315                chosen edge (take into account that the previously chosen
4316                edge might have been a tail, not a head) */
4317             if (head - (*near_edge ? 0 : my_size) >= *dest)
4318                 skip_head = TRUE;
4319             /* check if the tail of this window is closer than the previously
4320                chosen edge (take into account that the previously chosen
4321                edge might have been a head, not a tail) */
4322             if (tail + (!*near_edge ? 0 : my_size) >= *dest)
4323                 skip_tail = TRUE;
4324             break;
4325         default:
4326             g_assert_not_reached();
4327     }
4328
4329     ob_debug("my head %d size %d", my_head, my_size);
4330     ob_debug("head %d tail %d dest %d", head, tail, *dest);
4331     if (!skip_head) {
4332         ob_debug("using near edge %d", head);
4333         *dest = head;
4334         *near_edge = TRUE;
4335     }
4336     else if (!skip_tail) {
4337         ob_debug("using far edge %d", tail);
4338         *dest = tail;
4339         *near_edge = FALSE;
4340     }
4341 }
4342
4343 void client_find_edge_directional(ObClient *self, ObDirection dir,
4344                                   gint my_head, gint my_size,
4345                                   gint my_edge_start, gint my_edge_size,
4346                                   gint *dest, gboolean *near_edge)
4347 {
4348     GList *it;
4349     Rect *a;
4350     Rect dock_area;
4351     gint edge;
4352     guint i;
4353
4354     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
4355                     &self->frame->area);
4356
4357     switch (dir) {
4358     case OB_DIRECTION_NORTH:
4359         edge = RECT_TOP(*a) - 1;
4360         break;
4361     case OB_DIRECTION_SOUTH:
4362         edge = RECT_BOTTOM(*a) + 1;
4363         break;
4364     case OB_DIRECTION_EAST:
4365         edge = RECT_RIGHT(*a) + 1;
4366         break;
4367     case OB_DIRECTION_WEST:
4368         edge = RECT_LEFT(*a) - 1;
4369         break;
4370     default:
4371         g_assert_not_reached();
4372     }
4373     /* default to the far edge, then narrow it down */
4374     *dest = edge;
4375     *near_edge = TRUE;
4376
4377     /* search for edges of monitors */
4378     for (i = 0; i < screen_num_monitors; ++i) {
4379         Rect *area = screen_area(self->desktop, i, NULL);
4380         detect_edge(*area, dir, my_head, my_size, my_edge_start,
4381                     my_edge_size, dest, near_edge);
4382         g_slice_free(Rect, area);
4383     }
4384
4385     /* search for edges of clients */
4386     for (it = client_list; it; it = g_list_next(it)) {
4387         ObClient *cur = it->data;
4388
4389         /* skip windows to not bump into */
4390         if (cur == self)
4391             continue;
4392         if (cur->iconic)
4393             continue;
4394         if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&
4395             cur->desktop != screen_desktop)
4396             continue;
4397
4398         ob_debug("trying window %s", cur->title);
4399
4400         detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start,
4401                     my_edge_size, dest, near_edge);
4402     }
4403     dock_get_area(&dock_area);
4404     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
4405                 my_edge_size, dest, near_edge);
4406
4407     g_slice_free(Rect, a);
4408 }
4409
4410 void client_find_move_directional(ObClient *self, ObDirection dir,
4411                                   gint *x, gint *y)
4412 {
4413     gint head, size;
4414     gint e, e_start, e_size;
4415     gboolean near;
4416
4417     switch (dir) {
4418     case OB_DIRECTION_EAST:
4419         head = RECT_RIGHT(self->frame->area);
4420         size = self->frame->area.width;
4421         e_start = RECT_TOP(self->frame->area);
4422         e_size = self->frame->area.height;
4423         break;
4424     case OB_DIRECTION_WEST:
4425         head = RECT_LEFT(self->frame->area);
4426         size = self->frame->area.width;
4427         e_start = RECT_TOP(self->frame->area);
4428         e_size = self->frame->area.height;
4429         break;
4430     case OB_DIRECTION_NORTH:
4431         head = RECT_TOP(self->frame->area);
4432         size = self->frame->area.height;
4433         e_start = RECT_LEFT(self->frame->area);
4434         e_size = self->frame->area.width;
4435         break;
4436     case OB_DIRECTION_SOUTH:
4437         head = RECT_BOTTOM(self->frame->area);
4438         size = self->frame->area.height;
4439         e_start = RECT_LEFT(self->frame->area);
4440         e_size = self->frame->area.width;
4441         break;
4442     default:
4443         g_assert_not_reached();
4444     }
4445
4446     client_find_edge_directional(self, dir, head, size,
4447                                  e_start, e_size, &e, &near);
4448     *x = self->frame->area.x;
4449     *y = self->frame->area.y;
4450     switch (dir) {
4451     case OB_DIRECTION_EAST:
4452         if (near) e -= self->frame->area.width;
4453         else      e++;
4454         *x = e;
4455         break;
4456     case OB_DIRECTION_WEST:
4457         if (near) e++;
4458         else      e -= self->frame->area.width;
4459         *x = e;
4460         break;
4461     case OB_DIRECTION_NORTH:
4462         if (near) e++;
4463         else      e -= self->frame->area.height;
4464         *y = e;
4465         break;
4466     case OB_DIRECTION_SOUTH:
4467         if (near) e -= self->frame->area.height;
4468         else      e++;
4469         *y = e;
4470         break;
4471     default:
4472         g_assert_not_reached();
4473     }
4474     frame_frame_gravity(self->frame, x, y);
4475 }
4476
4477 void client_find_resize_directional(ObClient *self, ObDirection side,
4478                                     gboolean grow,
4479                                     gint *x, gint *y, gint *w, gint *h)
4480 {
4481     gint head;
4482     gint e, e_start, e_size, delta;
4483     gboolean near;
4484     ObDirection dir;
4485
4486     switch (side) {
4487     case OB_DIRECTION_EAST:
4488         head = RECT_RIGHT(self->frame->area) +
4489             (self->size_inc.width - 1) * (grow ? 1 : 0);
4490         e_start = RECT_TOP(self->frame->area);
4491         e_size = self->frame->area.height;
4492         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
4493         break;
4494     case OB_DIRECTION_WEST:
4495         head = RECT_LEFT(self->frame->area) -
4496             (self->size_inc.width - 1) * (grow ? 1 : 0);
4497         e_start = RECT_TOP(self->frame->area);
4498         e_size = self->frame->area.height;
4499         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
4500         break;
4501     case OB_DIRECTION_NORTH:
4502         head = RECT_TOP(self->frame->area) -
4503             (self->size_inc.height - 1) * (grow ? 1 : 0);
4504         e_start = RECT_LEFT(self->frame->area);
4505         e_size = self->frame->area.width;
4506         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
4507         break;
4508     case OB_DIRECTION_SOUTH:
4509         head = RECT_BOTTOM(self->frame->area) +
4510             (self->size_inc.height - 1) * (grow ? 1 : 0);
4511         e_start = RECT_LEFT(self->frame->area);
4512         e_size = self->frame->area.width;
4513         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
4514         break;
4515     default:
4516         g_assert_not_reached();
4517     }
4518
4519     ob_debug("head %d dir %d", head, dir);
4520     client_find_edge_directional(self, dir, head, 1,
4521                                  e_start, e_size, &e, &near);
4522     ob_debug("edge %d", e);
4523     *x = self->frame->area.x;
4524     *y = self->frame->area.y;
4525     *w = self->frame->area.width;
4526     *h = self->frame->area.height;
4527     switch (side) {
4528     case OB_DIRECTION_EAST:
4529         if (grow == near) --e;
4530         delta = e - RECT_RIGHT(self->frame->area);
4531         *w += delta;
4532         break;
4533     case OB_DIRECTION_WEST:
4534         if (grow == near) ++e;
4535         delta = RECT_LEFT(self->frame->area) - e;
4536         *x -= delta;
4537         *w += delta;
4538         break;
4539     case OB_DIRECTION_NORTH:
4540         if (grow == near) ++e;
4541         delta = RECT_TOP(self->frame->area) - e;
4542         *y -= delta;
4543         *h += delta;
4544         break;
4545     case OB_DIRECTION_SOUTH:
4546         if (grow == near) --e;
4547         delta = e - RECT_BOTTOM(self->frame->area);
4548         *h += delta;
4549         break;
4550     default:
4551         g_assert_not_reached();
4552     }
4553     frame_frame_gravity(self->frame, x, y);
4554     *w -= self->frame->size.left + self->frame->size.right;
4555     *h -= self->frame->size.top + self->frame->size.bottom;
4556 }
4557
4558 ObClient* client_under_pointer(void)
4559 {
4560     gint x, y;
4561     GList *it;
4562     ObClient *ret = NULL;
4563
4564     if (screen_pointer_pos(&x, &y)) {
4565         for (it = stacking_list; it; it = g_list_next(it)) {
4566             if (WINDOW_IS_CLIENT(it->data)) {
4567                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4568                 if (c->frame->visible &&
4569                     /* check the desktop, this is done during desktop
4570                        switching and windows are shown/hidden status is not
4571                        reliable */
4572                     (c->desktop == screen_desktop ||
4573                      c->desktop == DESKTOP_ALL) &&
4574                     /* ignore all animating windows */
4575                     !frame_iconify_animating(c->frame) &&
4576                     RECT_CONTAINS(c->frame->area, x, y))
4577                 {
4578                     ret = c;
4579                     break;
4580                 }
4581             }
4582         }
4583     }
4584     return ret;
4585 }
4586
4587 gboolean client_has_group_siblings(ObClient *self)
4588 {
4589     return self->group && self->group->members->next;
4590 }
4591
4592 gboolean client_has_relative(ObClient *self)
4593 {
4594     return client_has_parent(self) ||
4595         client_has_group_siblings(self) ||
4596         client_has_children(self);
4597 }
4598
4599 /*! Returns TRUE if the client is running on the same machine as Openbox */
4600 gboolean client_on_localhost(ObClient *self)
4601 {
4602     return self->client_machine == NULL;
4603 }