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