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