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