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