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