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