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