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