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