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