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