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