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