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