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