tell what signal is going to be sent to the client when killing through the dialog
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "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         const gchar *sig;
3412
3413         if (self->kill_level == 0)
3414             sig = "terminate";
3415         else
3416             sig = "kill";
3417
3418         m = g_strdup_printf
3419             (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"), self->original_title, sig);
3420
3421         self->kill_prompt = prompt_new(m, answers,
3422                                        sizeof(answers)/sizeof(answers[0]),
3423                                        OB_KILL_RESULT_NO, /* default = no */
3424                                        OB_KILL_RESULT_NO, /* cancel = no */
3425                                        client_kill_requested, self);
3426         g_free(m);
3427     }
3428
3429     prompt_show(self->kill_prompt, self);
3430 }
3431
3432 void client_kill(ObClient *self)
3433 {
3434     /* don't kill our own windows */
3435     if (self->prompt) return;
3436
3437     if (!self->client_machine && self->pid) {
3438         /* running on the local host */
3439         if (self->kill_level == 0) {
3440             ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
3441                      self->window, self->pid);
3442             kill(self->pid, SIGTERM);
3443             ++self->kill_level;
3444
3445             /* show that we're trying to kill it */
3446             client_update_title(self);
3447         }
3448         else {
3449             ob_debug("killing window 0x%x with pid %lu, with SIGKILL\n",
3450                      self->window, self->pid);
3451             kill(self->pid, SIGKILL); /* kill -9 */
3452         }
3453     }
3454     else {
3455         /* running on a remote host */
3456         XKillClient(ob_display, self->window);
3457     }
3458 }
3459
3460 void client_hilite(ObClient *self, gboolean hilite)
3461 {
3462     if (self->demands_attention == hilite)
3463         return; /* no change */
3464
3465     /* don't allow focused windows to hilite */
3466     self->demands_attention = hilite && !client_focused(self);
3467     if (self->frame != NULL) { /* if we're mapping, just set the state */
3468         if (self->demands_attention)
3469             frame_flash_start(self->frame);
3470         else
3471             frame_flash_stop(self->frame);
3472         client_change_state(self);
3473     }
3474 }
3475
3476 static void client_set_desktop_recursive(ObClient *self,
3477                                          guint target,
3478                                          gboolean donthide,
3479                                          gboolean dontraise)
3480 {
3481     guint old;
3482     GSList *it;
3483
3484     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3485
3486         ob_debug("Setting desktop %u\n", target+1);
3487
3488         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3489
3490         old = self->desktop;
3491         self->desktop = target;
3492         PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3493         /* the frame can display the current desktop state */
3494         frame_adjust_state(self->frame);
3495         /* 'move' the window to the new desktop */
3496         if (!donthide)
3497             client_hide(self);
3498         client_show(self);
3499         /* raise if it was not already on the desktop */
3500         if (old != DESKTOP_ALL && !dontraise)
3501             stacking_raise(CLIENT_AS_WINDOW(self));
3502         if (STRUT_EXISTS(self->strut))
3503             screen_update_areas();
3504         else
3505             /* the new desktop's geometry may be different, so we may need to
3506                resize, for example if we are maximized */
3507             client_reconfigure(self, FALSE);
3508     }
3509
3510     /* move all transients */
3511     for (it = self->transients; it; it = g_slist_next(it))
3512         if (it->data != self)
3513             if (client_is_direct_child(self, it->data))
3514                 client_set_desktop_recursive(it->data, target,
3515                                              donthide, dontraise);
3516 }
3517
3518 void client_set_desktop(ObClient *self, guint target,
3519                         gboolean donthide, gboolean dontraise)
3520 {
3521     self = client_search_top_direct_parent(self);
3522     client_set_desktop_recursive(self, target, donthide, dontraise);
3523 }
3524
3525 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3526 {
3527     while (child != parent && (child = client_direct_parent(child)));
3528     return child == parent;
3529 }
3530
3531 ObClient *client_search_modal_child(ObClient *self)
3532 {
3533     GSList *it;
3534     ObClient *ret;
3535
3536     for (it = self->transients; it; it = g_slist_next(it)) {
3537         ObClient *c = it->data;
3538         if ((ret = client_search_modal_child(c))) return ret;
3539         if (c->modal) return c;
3540     }
3541     return NULL;
3542 }
3543
3544 gboolean client_validate(ObClient *self)
3545 {
3546     XEvent e;
3547
3548     XSync(ob_display, FALSE); /* get all events on the server */
3549
3550     if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3551         XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3552         XPutBackEvent(ob_display, &e);
3553         return FALSE;
3554     }
3555
3556     return TRUE;
3557 }
3558
3559 void client_set_wm_state(ObClient *self, glong state)
3560 {
3561     if (state == self->wmstate) return; /* no change */
3562
3563     switch (state) {
3564     case IconicState:
3565         client_iconify(self, TRUE, TRUE, FALSE);
3566         break;
3567     case NormalState:
3568         client_iconify(self, FALSE, TRUE, FALSE);
3569         break;
3570     }
3571 }
3572
3573 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3574 {
3575     gboolean shaded = self->shaded;
3576     gboolean fullscreen = self->fullscreen;
3577     gboolean undecorated = self->undecorated;
3578     gboolean max_horz = self->max_horz;
3579     gboolean max_vert = self->max_vert;
3580     gboolean modal = self->modal;
3581     gboolean iconic = self->iconic;
3582     gboolean demands_attention = self->demands_attention;
3583     gboolean above = self->above;
3584     gboolean below = self->below;
3585     gint i;
3586
3587     if (!(action == prop_atoms.net_wm_state_add ||
3588           action == prop_atoms.net_wm_state_remove ||
3589           action == prop_atoms.net_wm_state_toggle))
3590         /* an invalid action was passed to the client message, ignore it */
3591         return;
3592
3593     for (i = 0; i < 2; ++i) {
3594         Atom state = i == 0 ? data1 : data2;
3595
3596         if (!state) continue;
3597
3598         /* if toggling, then pick whether we're adding or removing */
3599         if (action == prop_atoms.net_wm_state_toggle) {
3600             if (state == prop_atoms.net_wm_state_modal)
3601                 action = modal ? prop_atoms.net_wm_state_remove :
3602                     prop_atoms.net_wm_state_add;
3603             else if (state == prop_atoms.net_wm_state_maximized_vert)
3604                 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3605                     prop_atoms.net_wm_state_add;
3606             else if (state == prop_atoms.net_wm_state_maximized_horz)
3607                 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3608                     prop_atoms.net_wm_state_add;
3609             else if (state == prop_atoms.net_wm_state_shaded)
3610                 action = shaded ? prop_atoms.net_wm_state_remove :
3611                     prop_atoms.net_wm_state_add;
3612             else if (state == prop_atoms.net_wm_state_skip_taskbar)
3613                 action = self->skip_taskbar ?
3614                     prop_atoms.net_wm_state_remove :
3615                     prop_atoms.net_wm_state_add;
3616             else if (state == prop_atoms.net_wm_state_skip_pager)
3617                 action = self->skip_pager ?
3618                     prop_atoms.net_wm_state_remove :
3619                     prop_atoms.net_wm_state_add;
3620             else if (state == prop_atoms.net_wm_state_hidden)
3621                 action = self->iconic ?
3622                     prop_atoms.net_wm_state_remove :
3623                     prop_atoms.net_wm_state_add;
3624             else if (state == prop_atoms.net_wm_state_fullscreen)
3625                 action = fullscreen ?
3626                     prop_atoms.net_wm_state_remove :
3627                     prop_atoms.net_wm_state_add;
3628             else if (state == prop_atoms.net_wm_state_above)
3629                 action = self->above ? prop_atoms.net_wm_state_remove :
3630                     prop_atoms.net_wm_state_add;
3631             else if (state == prop_atoms.net_wm_state_below)
3632                 action = self->below ? prop_atoms.net_wm_state_remove :
3633                     prop_atoms.net_wm_state_add;
3634             else if (state == prop_atoms.net_wm_state_demands_attention)
3635                 action = self->demands_attention ?
3636                     prop_atoms.net_wm_state_remove :
3637                     prop_atoms.net_wm_state_add;
3638             else if (state == prop_atoms.ob_wm_state_undecorated)
3639                 action = undecorated ? prop_atoms.net_wm_state_remove :
3640                     prop_atoms.net_wm_state_add;
3641         }
3642
3643         if (action == prop_atoms.net_wm_state_add) {
3644             if (state == prop_atoms.net_wm_state_modal) {
3645                 modal = TRUE;
3646             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3647                 max_vert = TRUE;
3648             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3649                 max_horz = TRUE;
3650             } else if (state == prop_atoms.net_wm_state_shaded) {
3651                 shaded = TRUE;
3652             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3653                 self->skip_taskbar = TRUE;
3654             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3655                 self->skip_pager = TRUE;
3656             } else if (state == prop_atoms.net_wm_state_hidden) {
3657                 iconic = TRUE;
3658             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3659                 fullscreen = TRUE;
3660             } else if (state == prop_atoms.net_wm_state_above) {
3661                 above = TRUE;
3662                 below = FALSE;
3663             } else if (state == prop_atoms.net_wm_state_below) {
3664                 above = FALSE;
3665                 below = TRUE;
3666             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3667                 demands_attention = TRUE;
3668             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3669                 undecorated = TRUE;
3670             }
3671
3672         } else { /* action == prop_atoms.net_wm_state_remove */
3673             if (state == prop_atoms.net_wm_state_modal) {
3674                 modal = FALSE;
3675             } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3676                 max_vert = FALSE;
3677             } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3678                 max_horz = FALSE;
3679             } else if (state == prop_atoms.net_wm_state_shaded) {
3680                 shaded = FALSE;
3681             } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3682                 self->skip_taskbar = FALSE;
3683             } else if (state == prop_atoms.net_wm_state_skip_pager) {
3684                 self->skip_pager = FALSE;
3685             } else if (state == prop_atoms.net_wm_state_hidden) {
3686                 iconic = FALSE;
3687             } else if (state == prop_atoms.net_wm_state_fullscreen) {
3688                 fullscreen = FALSE;
3689             } else if (state == prop_atoms.net_wm_state_above) {
3690                 above = FALSE;
3691             } else if (state == prop_atoms.net_wm_state_below) {
3692                 below = FALSE;
3693             } else if (state == prop_atoms.net_wm_state_demands_attention) {
3694                 demands_attention = FALSE;
3695             } else if (state == prop_atoms.ob_wm_state_undecorated) {
3696                 undecorated = FALSE;
3697             }
3698         }
3699     }
3700
3701     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3702         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3703             /* toggling both */
3704             if (max_horz == max_vert) { /* both going the same way */
3705                 client_maximize(self, max_horz, 0);
3706             } else {
3707                 client_maximize(self, max_horz, 1);
3708                 client_maximize(self, max_vert, 2);
3709             }
3710         } else {
3711             /* toggling one */
3712             if (max_horz != self->max_horz)
3713                 client_maximize(self, max_horz, 1);
3714             else
3715                 client_maximize(self, max_vert, 2);
3716         }
3717     }
3718     /* change fullscreen state before shading, as it will affect if the window
3719        can shade or not */
3720     if (fullscreen != self->fullscreen)
3721         client_fullscreen(self, fullscreen);
3722     if (shaded != self->shaded)
3723         client_shade(self, shaded);
3724     if (undecorated != self->undecorated)
3725         client_set_undecorated(self, undecorated);
3726     if (above != self->above || below != self->below) {
3727         self->above = above;
3728         self->below = below;
3729         client_calc_layer(self);
3730     }
3731
3732     if (modal != self->modal) {
3733         self->modal = modal;
3734         /* when a window changes modality, then its stacking order with its
3735            transients needs to change */
3736         stacking_raise(CLIENT_AS_WINDOW(self));
3737
3738         /* it also may get focused. if something is focused that shouldn't
3739            be focused anymore, then move the focus */
3740         if (focus_client && client_focus_target(focus_client) != focus_client)
3741             client_focus(focus_client);
3742     }
3743
3744     if (iconic != self->iconic)
3745         client_iconify(self, iconic, FALSE, FALSE);
3746
3747     if (demands_attention != self->demands_attention)
3748         client_hilite(self, demands_attention);
3749
3750     client_change_state(self); /* change the hint to reflect these changes */
3751 }
3752
3753 ObClient *client_focus_target(ObClient *self)
3754 {
3755     ObClient *child = NULL;
3756
3757     child = client_search_modal_child(self);
3758     if (child) return child;
3759     return self;
3760 }
3761
3762 gboolean client_can_focus(ObClient *self)
3763 {
3764     /* choose the correct target */
3765     self = client_focus_target(self);
3766
3767     if (!self->frame->visible)
3768         return FALSE;
3769
3770     if (!(self->can_focus || self->focus_notify))
3771         return FALSE;
3772
3773     return TRUE;
3774 }
3775
3776 gboolean client_focus(ObClient *self)
3777 {
3778     /* we might not focus this window, so if we have modal children which would
3779        be focused instead, bring them to this desktop */
3780     client_bring_modal_windows(self);
3781
3782     /* choose the correct target */
3783     self = client_focus_target(self);
3784
3785     if (!client_can_focus(self)) {
3786         ob_debug_type(OB_DEBUG_FOCUS,
3787                       "Client %s can't be focused\n", self->title);
3788         return FALSE;
3789     }
3790
3791     ob_debug_type(OB_DEBUG_FOCUS,
3792                   "Focusing client \"%s\" (0x%x) at time %u\n",
3793                   self->title, self->window, event_curtime);
3794
3795     /* if using focus_delay, stop the timer now so that focus doesn't
3796        go moving on us */
3797     event_halt_focus_delay();
3798
3799     /* if there is a grab going on, then we need to cancel it. if we move
3800        focus during the grab, applications will get NotifyWhileGrabbed events
3801        and ignore them !
3802
3803        actions should not rely on being able to move focus during an
3804        interactive grab.
3805     */
3806     event_cancel_all_key_grabs();
3807
3808     xerror_set_ignore(TRUE);
3809     xerror_occured = FALSE;
3810
3811     if (self->can_focus) {
3812         /* This can cause a BadMatch error with CurrentTime, or if an app
3813            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3814         XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3815                        event_curtime);
3816     }
3817
3818     if (self->focus_notify) {
3819         XEvent ce;
3820         ce.xclient.type = ClientMessage;
3821         ce.xclient.message_type = prop_atoms.wm_protocols;
3822         ce.xclient.display = ob_display;
3823         ce.xclient.window = self->window;
3824         ce.xclient.format = 32;
3825         ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3826         ce.xclient.data.l[1] = event_curtime;
3827         ce.xclient.data.l[2] = 0l;
3828         ce.xclient.data.l[3] = 0l;
3829         ce.xclient.data.l[4] = 0l;
3830         XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3831     }
3832
3833     xerror_set_ignore(FALSE);
3834
3835     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d\n", xerror_occured);
3836     return !xerror_occured;
3837 }
3838
3839 static void client_present(ObClient *self, gboolean here, gboolean raise,
3840                            gboolean unshade)
3841 {
3842     if (client_normal(self) && screen_showing_desktop)
3843         screen_show_desktop(FALSE, self);
3844     if (self->iconic)
3845         client_iconify(self, FALSE, here, FALSE);
3846     if (self->desktop != DESKTOP_ALL &&
3847         self->desktop != screen_desktop)
3848     {
3849         if (here)
3850             client_set_desktop(self, screen_desktop, FALSE, TRUE);
3851         else
3852             screen_set_desktop(self->desktop, FALSE);
3853     } else if (!self->frame->visible)
3854         /* if its not visible for other reasons, then don't mess
3855            with it */
3856         return;
3857     if (self->shaded && unshade)
3858         client_shade(self, FALSE);
3859     if (raise)
3860         stacking_raise(CLIENT_AS_WINDOW(self));
3861
3862     client_focus(self);
3863 }
3864
3865 void client_activate(ObClient *self, gboolean here, gboolean raise,
3866                      gboolean unshade, gboolean user)
3867 {
3868     client_present(self, here, raise, unshade);
3869 }
3870
3871 static void client_bring_windows_recursive(ObClient *self,
3872                                            guint desktop,
3873                                            gboolean helpers,
3874                                            gboolean modals,
3875                                            gboolean iconic)
3876 {
3877     GSList *it;
3878
3879     for (it = self->transients; it; it = g_slist_next(it))
3880         client_bring_windows_recursive(it->data, desktop,
3881                                        helpers, modals, iconic);
3882
3883     if (((helpers && client_helper(self)) ||
3884          (modals && self->modal)) &&
3885         ((self->desktop != desktop && self->desktop != DESKTOP_ALL) ||
3886          (iconic && self->iconic)))
3887     {
3888         if (iconic && self->iconic)
3889             client_iconify(self, FALSE, TRUE, FALSE);
3890         else
3891             client_set_desktop(self, desktop, FALSE, FALSE);
3892     }
3893 }
3894
3895 void client_bring_helper_windows(ObClient *self)
3896 {
3897     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
3898 }
3899
3900 void client_bring_modal_windows(ObClient *self)
3901 {
3902     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
3903 }
3904
3905 gboolean client_focused(ObClient *self)
3906 {
3907     return self == focus_client;
3908 }
3909
3910 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3911 {
3912     guint i;
3913     gulong min_diff, min_i;
3914
3915     if (!self->nicons) {
3916         ObClientIcon *parent = NULL;
3917         GSList *it;
3918
3919         for (it = self->parents; it; it = g_slist_next(it)) {
3920             ObClient *c = it->data;
3921             if ((parent = client_icon_recursive(c, w, h)))
3922                 break;
3923         }
3924
3925         return parent;
3926     }
3927
3928     /* some kind of crappy approximation to find the icon closest in size to
3929        what we requested, but icons are generally all the same ratio as
3930        eachother so it's good enough. */
3931
3932     min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
3933     min_i = 0;
3934
3935     for (i = 1; i < self->nicons; ++i) {
3936         gulong diff;
3937
3938         diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
3939         if (diff < min_diff) {
3940             min_diff = diff;
3941             min_i = i;
3942         }
3943     }
3944     return &self->icons[min_i];
3945 }
3946
3947 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3948 {
3949     ObClientIcon *ret;
3950     static ObClientIcon deficon;
3951
3952     if (!(ret = client_icon_recursive(self, w, h))) {
3953         deficon.width = deficon.height = 48;
3954         deficon.data = ob_rr_theme->def_win_icon;
3955         ret = &deficon;
3956     }
3957     return ret;
3958 }
3959
3960 void client_set_layer(ObClient *self, gint layer)
3961 {
3962     if (layer < 0) {
3963         self->below = TRUE;
3964         self->above = FALSE;
3965     } else if (layer == 0) {
3966         self->below = self->above = FALSE;
3967     } else {
3968         self->below = FALSE;
3969         self->above = TRUE;
3970     }
3971     client_calc_layer(self);
3972     client_change_state(self); /* reflect this in the state hints */
3973 }
3974
3975 void client_set_undecorated(ObClient *self, gboolean undecorated)
3976 {
3977     if (self->undecorated != undecorated &&
3978         /* don't let it undecorate if the function is missing, but let
3979            it redecorate */
3980         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3981     {
3982         self->undecorated = undecorated;
3983         client_setup_decor_and_functions(self, TRUE);
3984         client_change_state(self); /* reflect this in the state hints */
3985     }
3986 }
3987
3988 guint client_monitor(ObClient *self)
3989 {
3990     return screen_find_monitor(&self->frame->area);
3991 }
3992
3993 ObClient *client_direct_parent(ObClient *self)
3994 {
3995     if (!self->parents) return NULL;
3996     if (self->transient_for_group) return NULL;
3997     return self->parents->data;
3998 }
3999
4000 ObClient *client_search_top_direct_parent(ObClient *self)
4001 {
4002     ObClient *p;
4003     while ((p = client_direct_parent(self))) self = p;
4004     return self;
4005 }
4006
4007 static GSList *client_search_all_top_parents_internal(ObClient *self,
4008                                                       gboolean bylayer,
4009                                                       ObStackingLayer layer)
4010 {
4011     GSList *ret;
4012     ObClient *p;
4013
4014     /* move up the direct transient chain as far as possible */
4015     while ((p = client_direct_parent(self)) &&
4016            (!bylayer || p->layer == layer))
4017         self = p;
4018
4019     if (!self->parents)
4020         ret = g_slist_prepend(NULL, self);
4021     else
4022         ret = g_slist_copy(self->parents);
4023
4024     return ret;
4025 }
4026
4027 GSList *client_search_all_top_parents(ObClient *self)
4028 {
4029     return client_search_all_top_parents_internal(self, FALSE, 0);
4030 }
4031
4032 GSList *client_search_all_top_parents_layer(ObClient *self)
4033 {
4034     return client_search_all_top_parents_internal(self, TRUE, self->layer);
4035 }
4036
4037 ObClient *client_search_focus_parent(ObClient *self)
4038 {
4039     GSList *it;
4040
4041     for (it = self->parents; it; it = g_slist_next(it))
4042         if (client_focused(it->data)) return it->data;
4043
4044     return NULL;
4045 }
4046
4047 ObClient *client_search_parent(ObClient *self, ObClient *search)
4048 {
4049     GSList *it;
4050
4051     for (it = self->parents; it; it = g_slist_next(it))
4052         if (it->data == search) return search;
4053
4054     return NULL;
4055 }
4056
4057 ObClient *client_search_transient(ObClient *self, ObClient *search)
4058 {
4059     GSList *sit;
4060
4061     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
4062         if (sit->data == search)
4063             return search;
4064         if (client_search_transient(sit->data, search))
4065             return search;
4066     }
4067     return NULL;
4068 }
4069
4070 static void detect_edge(Rect area, ObDirection dir,
4071                         gint my_head, gint my_size,
4072                         gint my_edge_start, gint my_edge_size,
4073                         gint *dest, gboolean *near_edge)
4074 {
4075     gint edge_start, edge_size, head, tail;
4076     gboolean skip_head = FALSE, skip_tail = FALSE;
4077
4078     switch (dir) {
4079         case OB_DIRECTION_NORTH:
4080         case OB_DIRECTION_SOUTH:
4081             edge_start = area.x;
4082             edge_size = area.width;
4083             break;
4084         case OB_DIRECTION_EAST:
4085         case OB_DIRECTION_WEST:
4086             edge_start = area.y;
4087             edge_size = area.height;
4088             break;
4089         default:
4090             g_assert_not_reached();
4091     }
4092
4093     /* do we collide with this window? */
4094     if (!RANGES_INTERSECT(my_edge_start, my_edge_size,
4095                 edge_start, edge_size))
4096         return;
4097
4098     switch (dir) {
4099         case OB_DIRECTION_NORTH:
4100             head = RECT_BOTTOM(area);
4101             tail = RECT_TOP(area);
4102             break;
4103         case OB_DIRECTION_SOUTH:
4104             head = RECT_TOP(area);
4105             tail = RECT_BOTTOM(area);
4106             break;
4107         case OB_DIRECTION_WEST:
4108             head = RECT_RIGHT(area);
4109             tail = RECT_LEFT(area);
4110             break;
4111         case OB_DIRECTION_EAST:
4112             head = RECT_LEFT(area);
4113             tail = RECT_RIGHT(area);
4114             break;
4115         default:
4116             g_assert_not_reached();
4117     }
4118     switch (dir) {
4119         case OB_DIRECTION_NORTH:
4120         case OB_DIRECTION_WEST:
4121             /* check if our window is past the head of this window */
4122             if (my_head <= head + 1)
4123                 skip_head = TRUE;
4124             /* check if our window's tail is past the tail of this window */
4125             if (my_head + my_size - 1 <= tail)
4126                 skip_tail = TRUE;
4127             /* check if the head of this window is closer than the previously
4128                chosen edge (take into account that the previously chosen
4129                edge might have been a tail, not a head) */
4130             if (head + (*near_edge ? 0 : my_size) < *dest)
4131                 skip_head = TRUE;
4132             /* check if the tail of this window is closer than the previously
4133                chosen edge (take into account that the previously chosen
4134                edge might have been a head, not a tail) */
4135             if (tail - (!*near_edge ? 0 : my_size) < *dest)
4136                 skip_tail = TRUE;
4137             break;
4138         case OB_DIRECTION_SOUTH:
4139         case OB_DIRECTION_EAST:
4140             /* check if our window is past the head of this window */
4141             if (my_head >= head - 1)
4142                 skip_head = TRUE;
4143             /* check if our window's tail is past the tail of this window */
4144             if (my_head - my_size + 1 >= tail)
4145                 skip_tail = TRUE;
4146             /* check if the head of this window is closer than the previously
4147                chosen edge (take into account that the previously chosen
4148                edge might have been a tail, not a head) */
4149             if (head - (*near_edge ? 0 : my_size) > *dest)
4150                 skip_head = TRUE;
4151             /* check if the tail of this window is closer than the previously
4152                chosen edge (take into account that the previously chosen
4153                edge might have been a head, not a tail) */
4154             if (tail + (!*near_edge ? 0 : my_size) > *dest)
4155                 skip_tail = TRUE;
4156             break;
4157         default:
4158             g_assert_not_reached();
4159     }
4160
4161     ob_debug("my head %d size %d\n", my_head, my_size);
4162     ob_debug("head %d tail %d deest %d\n", head, tail, *dest);
4163     if (!skip_head) {
4164         ob_debug("using near edge %d\n", head);
4165         *dest = head;
4166         *near_edge = TRUE;
4167     }
4168     else if (!skip_tail) {
4169         ob_debug("using far edge %d\n", tail);
4170         *dest = tail;
4171         *near_edge = FALSE;
4172     }
4173 }
4174
4175 void client_find_edge_directional(ObClient *self, ObDirection dir,
4176                                   gint my_head, gint my_size,
4177                                   gint my_edge_start, gint my_edge_size,
4178                                   gint *dest, gboolean *near_edge)
4179 {
4180     GList *it;
4181     Rect *a, *mon;
4182     Rect dock_area;
4183     gint edge;
4184
4185     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
4186                     &self->frame->area);
4187     mon = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR,
4188                       &self->frame->area);
4189
4190     switch (dir) {
4191     case OB_DIRECTION_NORTH:
4192         if (my_head >= RECT_TOP(*mon) + 1)
4193             edge = RECT_TOP(*mon) - 1;
4194         else
4195             edge = RECT_TOP(*a) - 1;
4196         break;
4197     case OB_DIRECTION_SOUTH:
4198         if (my_head <= RECT_BOTTOM(*mon) - 1)
4199             edge = RECT_BOTTOM(*mon) + 1;
4200         else
4201             edge = RECT_BOTTOM(*a) + 1;
4202         break;
4203     case OB_DIRECTION_EAST:
4204         if (my_head <= RECT_RIGHT(*mon) - 1)
4205             edge = RECT_RIGHT(*mon) + 1;
4206         else
4207             edge = RECT_RIGHT(*a) + 1;
4208         break;
4209     case OB_DIRECTION_WEST:
4210         if (my_head >= RECT_LEFT(*mon) + 1)
4211             edge = RECT_LEFT(*mon) - 1;
4212         else
4213             edge = RECT_LEFT(*a) - 1;
4214         break;
4215     default:
4216         g_assert_not_reached();
4217     }
4218     /* default to the far edge, then narrow it down */
4219     *dest = edge;
4220     *near_edge = TRUE;
4221
4222     for (it = client_list; it; it = g_list_next(it)) {
4223         ObClient *cur = it->data;
4224
4225         /* skip windows to not bump into */
4226         if (cur == self)
4227             continue;
4228         if (cur->iconic)
4229             continue;
4230         if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&
4231             cur->desktop != screen_desktop)
4232             continue;
4233
4234         ob_debug("trying window %s\n", cur->title);
4235
4236         detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start,
4237                     my_edge_size, dest, near_edge);
4238     }
4239     dock_get_area(&dock_area);
4240     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
4241                 my_edge_size, dest, near_edge);
4242     g_free(a);
4243     g_free(mon);
4244 }
4245
4246 void client_find_move_directional(ObClient *self, ObDirection dir,
4247                                   gint *x, gint *y)
4248 {
4249     gint head, size;
4250     gint e, e_start, e_size;
4251     gboolean near;
4252
4253     switch (dir) {
4254     case OB_DIRECTION_EAST:
4255         head = RECT_RIGHT(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_WEST:
4261         head = RECT_LEFT(self->frame->area);
4262         size = self->frame->area.width;
4263         e_start = RECT_TOP(self->frame->area);
4264         e_size = self->frame->area.height;
4265         break;
4266     case OB_DIRECTION_NORTH:
4267         head = RECT_TOP(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     case OB_DIRECTION_SOUTH:
4273         head = RECT_BOTTOM(self->frame->area);
4274         size = self->frame->area.height;
4275         e_start = RECT_LEFT(self->frame->area);
4276         e_size = self->frame->area.width;
4277         break;
4278     default:
4279         g_assert_not_reached();
4280     }
4281
4282     client_find_edge_directional(self, dir, head, size,
4283                                  e_start, e_size, &e, &near);
4284     *x = self->frame->area.x;
4285     *y = self->frame->area.y;
4286     switch (dir) {
4287     case OB_DIRECTION_EAST:
4288         if (near) e -= self->frame->area.width;
4289         else      e++;
4290         *x = e;
4291         break;
4292     case OB_DIRECTION_WEST:
4293         if (near) e++;
4294         else      e -= self->frame->area.width;
4295         *x = e;
4296         break;
4297     case OB_DIRECTION_NORTH:
4298         if (near) e++;
4299         else      e -= self->frame->area.height;
4300         *y = e;
4301         break;
4302     case OB_DIRECTION_SOUTH:
4303         if (near) e -= self->frame->area.height;
4304         else      e++;
4305         *y = e;
4306         break;
4307     default:
4308         g_assert_not_reached();
4309     }
4310     frame_frame_gravity(self->frame, x, y);
4311 }
4312
4313 void client_find_resize_directional(ObClient *self, ObDirection side,
4314                                     gboolean grow,
4315                                     gint *x, gint *y, gint *w, gint *h)
4316 {
4317     gint head;
4318     gint e, e_start, e_size, delta;
4319     gboolean near;
4320     ObDirection dir;
4321
4322     switch (side) {
4323     case OB_DIRECTION_EAST:
4324         head = RECT_RIGHT(self->frame->area) +
4325             (self->size_inc.width - 1) * (grow ? 1 : -1);
4326         e_start = RECT_TOP(self->frame->area);
4327         e_size = self->frame->area.height;
4328         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
4329         break;
4330     case OB_DIRECTION_WEST:
4331         head = RECT_LEFT(self->frame->area) -
4332             (self->size_inc.width - 1) * (grow ? 1 : -1);
4333         e_start = RECT_TOP(self->frame->area);
4334         e_size = self->frame->area.height;
4335         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
4336         break;
4337     case OB_DIRECTION_NORTH:
4338         head = RECT_TOP(self->frame->area) -
4339             (self->size_inc.height - 1) * (grow ? 1 : -1);
4340         e_start = RECT_LEFT(self->frame->area);
4341         e_size = self->frame->area.width;
4342         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
4343         break;
4344     case OB_DIRECTION_SOUTH:
4345         head = RECT_BOTTOM(self->frame->area) +
4346             (self->size_inc.height - 1) * (grow ? 1 : -1);
4347         e_start = RECT_LEFT(self->frame->area);
4348         e_size = self->frame->area.width;
4349         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
4350         break;
4351     default:
4352         g_assert_not_reached();
4353     }
4354
4355     ob_debug("head %d dir %d\n", head, dir);
4356     client_find_edge_directional(self, dir, head, 1,
4357                                  e_start, e_size, &e, &near);
4358     ob_debug("edge %d\n", e);
4359     *x = self->frame->area.x;
4360     *y = self->frame->area.y;
4361     *w = self->frame->area.width;
4362     *h = self->frame->area.height;
4363     switch (side) {
4364     case OB_DIRECTION_EAST:
4365         if (grow == near) --e;
4366         delta = e - RECT_RIGHT(self->frame->area);
4367         *w += delta;
4368         break;
4369     case OB_DIRECTION_WEST:
4370         if (grow == near) ++e;
4371         delta = RECT_LEFT(self->frame->area) - e;
4372         *x -= delta;
4373         *w += delta;
4374         break;
4375     case OB_DIRECTION_NORTH:
4376         if (grow == near) ++e;
4377         delta = RECT_TOP(self->frame->area) - e;
4378         *y -= delta;
4379         *h += delta;
4380         break;
4381     case OB_DIRECTION_SOUTH:
4382         if (grow == near) --e;
4383         delta = e - RECT_BOTTOM(self->frame->area);
4384         *h += delta;
4385         break;
4386     default:
4387         g_assert_not_reached();
4388     }
4389     frame_frame_gravity(self->frame, x, y);
4390     *w -= self->frame->size.left + self->frame->size.right;
4391     *h -= self->frame->size.top + self->frame->size.bottom;
4392 }
4393
4394 ObClient* client_under_pointer(void)
4395 {
4396     gint x, y;
4397     GList *it;
4398     ObClient *ret = NULL;
4399
4400     if (screen_pointer_pos(&x, &y)) {
4401         for (it = stacking_list; it; it = g_list_next(it)) {
4402             if (WINDOW_IS_CLIENT(it->data)) {
4403                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4404                 if (c->frame->visible &&
4405                     /* check the desktop, this is done during desktop
4406                        switching and windows are shown/hidden status is not
4407                        reliable */
4408                     (c->desktop == screen_desktop ||
4409                      c->desktop == DESKTOP_ALL) &&
4410                     /* ignore all animating windows */
4411                     !frame_iconify_animating(c->frame) &&
4412                     RECT_CONTAINS(c->frame->area, x, y))
4413                 {
4414                     ret = c;
4415                     break;
4416                 }
4417             }
4418         }
4419     }
4420     return ret;
4421 }
4422
4423 gboolean client_has_group_siblings(ObClient *self)
4424 {
4425     return self->group && self->group->members->next;
4426 }