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