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