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