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