]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/client.c
Resizing removes maximization now, so don't only snap "grow to edge" to screen edges.
[mikachu/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "screen.h"
25 #include "moveresize.h"
26 #include "ping.h"
27 #include "place.h"
28 #include "frame.h"
29 #include "session.h"
30 #include "event.h"
31 #include "grab.h"
32 #include "prompt.h"
33 #include "focus.h"
34 #include "focus_cycle.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "obrender/render.h"
43 #include "gettext.h"
44 #include "obt/display.h"
45 #include "obt/xqueue.h"
46 #include "obt/prop.h"
47
48 #ifdef HAVE_UNISTD_H
49 #  include <unistd.h>
50 #endif
51
52 #ifdef HAVE_SIGNAL_H
53 #  include <signal.h> /* for kill() */
54 #endif
55
56 #include <glib.h>
57 #include <X11/Xutil.h>
58
59 /*! The event mask to grab on client windows */
60 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | \
61                           ColormapChangeMask)
62
63 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
64                                 ButtonMotionMask)
65
66 typedef struct
67 {
68     ObClientCallback func;
69     gpointer data;
70 } ClientCallback;
71
72 GList          *client_list             = NULL;
73
74 static GSList  *client_destroy_notifies = NULL;
75 static RrImage *client_default_icon     = NULL;
76
77 static void client_get_all(ObClient *self, gboolean real);
78 static void client_get_startup_id(ObClient *self);
79 static void client_get_session_ids(ObClient *self);
80 static void client_save_app_rule_values(ObClient *self);
81 static void client_get_area(ObClient *self);
82 static void client_get_desktop(ObClient *self);
83 static void client_get_state(ObClient *self);
84 static void client_get_shaped(ObClient *self);
85 static void client_get_colormap(ObClient *self);
86 static void client_set_desktop_recursive(ObClient *self,
87                                          guint target,
88                                          gboolean donthide,
89                                          gboolean dontraise);
90 static void client_change_allowed_actions(ObClient *self);
91 static void client_change_state(ObClient *self);
92 static void client_change_wm_state(ObClient *self);
93 static void client_apply_startup_state(ObClient *self,
94                                        gint x, gint y, gint w, gint h);
95 static void client_restore_session_state(ObClient *self);
96 static gboolean client_restore_session_stacking(ObClient *self);
97 static ObAppSettings *client_get_settings_state(ObClient *self);
98 static void client_update_transient_tree(ObClient *self,
99                                          ObGroup *oldgroup, ObGroup *newgroup,
100                                          gboolean oldgtran, gboolean newgtran,
101                                          ObClient* oldparent,
102                                          ObClient *newparent);
103 static void client_present(ObClient *self, gboolean here, gboolean raise,
104                            gboolean unshade);
105 static GSList *client_search_all_top_parents_internal(ObClient *self,
106                                                       gboolean bylayer,
107                                                       ObStackingLayer layer);
108 static void client_call_notifies(ObClient *self, GSList *list);
109 static void client_ping_event(ObClient *self, gboolean dead);
110 static void client_prompt_kill(ObClient *self);
111 static gboolean client_can_steal_focus(ObClient *self,
112                                        gboolean allow_other_desktop,
113                                        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     /* cap any X windows at the size of an unsigned short */
2878     *w = MIN(*w,
2879              G_MAXUSHORT - self->frame->size.left - self->frame->size.right);
2880     *h = MIN(*h,
2881              G_MAXUSHORT - self->frame->size.top - self->frame->size.bottom);
2882
2883
2884     /* gets the frame's position */
2885     frame_client_gravity(self->frame, x, y);
2886
2887     /* these positions are frame positions, not client positions */
2888
2889     /* set the size and position if fullscreen */
2890     if (self->fullscreen) {
2891         const Rect *a;
2892         guint i;
2893
2894         i = screen_find_monitor(&desired);
2895         a = screen_physical_area_monitor(i);
2896
2897         *x = a->x;
2898         *y = a->y;
2899         *w = a->width;
2900         *h = a->height;
2901
2902         user = FALSE; /* ignore if the client can't be moved/resized when it
2903                          is fullscreening */
2904     } else if (self->max_horz || self->max_vert) {
2905         Rect *a;
2906         guint i;
2907
2908         /* use all possible struts when maximizing to the full screen */
2909         i = screen_find_monitor(&desired);
2910         a = screen_area(self->desktop, i,
2911                         (self->max_horz && self->max_vert ? NULL : &desired));
2912
2913         /* set the size and position if maximized */
2914         if (self->max_horz) {
2915             *x = a->x;
2916             *w = a->width - self->frame->size.left - self->frame->size.right;
2917         }
2918         if (self->max_vert) {
2919             *y = a->y;
2920             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2921         }
2922
2923         user = FALSE; /* ignore if the client can't be moved/resized when it
2924                          is maximizing */
2925
2926         g_slice_free(Rect, a);
2927     }
2928
2929     /* gets the client's position */
2930     frame_frame_gravity(self->frame, x, y);
2931
2932     /* work within the preferred sizes given by the window, these may have
2933        changed rather than it's requested width and height, so always run
2934        through this code */
2935     {
2936         gint basew, baseh, minw, minh;
2937         gint incw, inch, maxw, maxh;
2938         gfloat minratio, maxratio;
2939
2940         incw = self->size_inc.width;
2941         inch = self->size_inc.height;
2942         minratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2943             0 : self->min_ratio;
2944         maxratio = self->fullscreen || (self->max_horz && self->max_vert) ?
2945             0 : self->max_ratio;
2946
2947         /* base size is substituted with min size if not specified */
2948         if (self->base_size.width >= 0 || self->base_size.height >= 0) {
2949             basew = self->base_size.width;
2950             baseh = self->base_size.height;
2951         } else {
2952             basew = self->min_size.width;
2953             baseh = self->min_size.height;
2954         }
2955         /* min size is substituted with base size if not specified */
2956         if (self->min_size.width || self->min_size.height) {
2957             minw = self->min_size.width;
2958             minh = self->min_size.height;
2959         } else {
2960             minw = self->base_size.width;
2961             minh = self->base_size.height;
2962         }
2963
2964         /* This comment is no longer true */
2965         /* if this is a user-requested resize, then check against min/max
2966            sizes */
2967
2968         /* smaller than min size or bigger than max size? */
2969         if (*w > self->max_size.width) *w = self->max_size.width;
2970         if (*w < minw) *w = minw;
2971         if (*h > self->max_size.height) *h = self->max_size.height;
2972         if (*h < minh) *h = minh;
2973
2974         *w -= basew;
2975         *h -= baseh;
2976
2977         /* the sizes to used for maximized */
2978         maxw = *w;
2979         maxh = *h;
2980
2981         /* keep to the increments */
2982         *w /= incw;
2983         *h /= inch;
2984
2985         /* you cannot resize to nothing */
2986         if (basew + *w < 1) *w = 1 - basew;
2987         if (baseh + *h < 1) *h = 1 - baseh;
2988
2989         /* save the logical size */
2990         *logicalw = incw > 1 ? *w : *w + basew;
2991         *logicalh = inch > 1 ? *h : *h + baseh;
2992
2993         *w *= incw;
2994         *h *= inch;
2995
2996         /* if maximized/fs then don't use the size increments */
2997         if (self->fullscreen || self->max_horz) *w = maxw;
2998         if (self->fullscreen || self->max_vert) *h = maxh;
2999
3000         *w += basew;
3001         *h += baseh;
3002
3003         /* adjust the height to match the width for the aspect ratios.
3004            for this, min size is not substituted for base size ever. */
3005         *w -= self->base_size.width;
3006         *h -= self->base_size.height;
3007
3008         if (minratio)
3009             if (*h * minratio > *w) {
3010                 *h = (gint)(*w / minratio);
3011
3012                 /* you cannot resize to nothing */
3013                 if (*h < 1) {
3014                     *h = 1;
3015                     *w = (gint)(*h * minratio);
3016                 }
3017             }
3018         if (maxratio)
3019             if (*h * maxratio < *w) {
3020                 *h = (gint)(*w / maxratio);
3021
3022                 /* you cannot resize to nothing */
3023                 if (*h < 1) {
3024                     *h = 1;
3025                     *w = (gint)(*h * minratio);
3026                 }
3027             }
3028
3029         *w += self->base_size.width;
3030         *h += self->base_size.height;
3031     }
3032
3033     /* these override the above states! if you cant move you can't move! */
3034     if (user) {
3035         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
3036             *x = self->area.x;
3037             *y = self->area.y;
3038         }
3039         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
3040             *w = self->area.width;
3041             *h = self->area.height;
3042         }
3043     }
3044
3045     g_assert(*w > 0);
3046     g_assert(*h > 0);
3047 }
3048
3049 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
3050                       gboolean user, gboolean final, gboolean force_reply)
3051 {
3052     Rect oldframe, oldclient;
3053     gboolean send_resize_client;
3054     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
3055     gboolean fmoved, fresized;
3056     guint fdecor = self->frame->decorations;
3057     gboolean fhorz = self->frame->max_horz;
3058     gboolean fvert = self->frame->max_vert;
3059     gint logicalw, logicalh;
3060
3061     /* find the new x, y, width, and height (and logical size) */
3062     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
3063
3064     /* set the logical size if things changed */
3065     if (!(w == self->area.width && h == self->area.height))
3066         SIZE_SET(self->logical_size, logicalw, logicalh);
3067
3068     /* figure out if we moved or resized or what */
3069     moved = (x != self->area.x || y != self->area.y);
3070     resized = (w != self->area.width || h != self->area.height);
3071
3072     oldframe = self->frame->area;
3073     oldclient = self->area;
3074     RECT_SET(self->area, x, y, w, h);
3075
3076     /* for app-requested resizes, always resize if 'resized' is true.
3077        for user-requested ones, only resize if final is true, or when
3078        resizing in redraw mode */
3079     send_resize_client = ((!user && resized) ||
3080                           (user && (final ||
3081                                     (resized && config_resize_redraw))));
3082
3083     /* if the client is enlarging, then resize the client before the frame */
3084     if (send_resize_client && (w > oldclient.width || h > oldclient.height)) {
3085         XMoveResizeWindow(obt_display, self->window,
3086                           self->frame->size.left, self->frame->size.top,
3087                           MAX(w, oldclient.width), MAX(h, oldclient.height));
3088         frame_adjust_client_area(self->frame);
3089     }
3090
3091     /* find the frame's dimensions and move/resize it */
3092     fmoved = moved;
3093     fresized = resized;
3094
3095     /* if decorations changed, then readjust everything for the frame */
3096     if (self->decorations != fdecor ||
3097         self->max_horz != fhorz || self->max_vert != fvert)
3098     {
3099         fmoved = fresized = TRUE;
3100     }
3101
3102     /* adjust the frame */
3103     if (fmoved || fresized) {
3104         gulong ignore_start;
3105         if (!user)
3106             ignore_start = event_start_ignore_all_enters();
3107
3108         /* replay pending pointer event before move the window, in case it
3109            would change what window gets the event */
3110         mouse_replay_pointer();
3111
3112         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
3113
3114         if (!user)
3115             event_end_ignore_all_enters(ignore_start);
3116     }
3117
3118     if (!user || final) {
3119         gint oldrx = self->root_pos.x;
3120         gint oldry = self->root_pos.y;
3121         /* we have reset the client to 0 border width, so don't include
3122            it in these coords */
3123         POINT_SET(self->root_pos,
3124                   self->frame->area.x + self->frame->size.left -
3125                   self->border_width,
3126                   self->frame->area.y + self->frame->size.top -
3127                   self->border_width);
3128         if (self->root_pos.x != oldrx || self->root_pos.y != oldry)
3129             rootmoved = TRUE;
3130     }
3131
3132     /* This is kinda tricky and should not be changed.. let me explain!
3133
3134        When user = FALSE, then the request is coming from the application
3135        itself, and we are more strict about when to send a synthetic
3136        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
3137        in this case (or send one if force_reply is true)
3138
3139        When user = TRUE, then the request is coming from "us", like when we
3140        maximize a window or something.  In this case we are more lenient.  We
3141        used to follow the same rules as above, but _Java_ Swing can't handle
3142        this. So just to appease Swing, when user = TRUE, we always send
3143        a synthetic ConfigureNotify to give the window its root coordinates.
3144        Lastly, if force_reply is TRUE, we always send a
3145        ConfigureNotify, which is needed during a resize with XSYNCronization.
3146     */
3147     if ((!user && !resized && (rootmoved || force_reply)) ||
3148         (user && ((!resized && force_reply) || (final && rootmoved))))
3149     {
3150         XEvent event;
3151
3152         event.type = ConfigureNotify;
3153         event.xconfigure.display = obt_display;
3154         event.xconfigure.event = self->window;
3155         event.xconfigure.window = self->window;
3156
3157         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d",
3158                  self->title, self->root_pos.x, self->root_pos.y, w, h);
3159
3160         /* root window real coords */
3161         event.xconfigure.x = self->root_pos.x;
3162         event.xconfigure.y = self->root_pos.y;
3163         event.xconfigure.width = w;
3164         event.xconfigure.height = h;
3165         event.xconfigure.border_width = self->border_width;
3166         event.xconfigure.above = None;
3167         event.xconfigure.override_redirect = FALSE;
3168         XSendEvent(event.xconfigure.display, event.xconfigure.window,
3169                    FALSE, StructureNotifyMask, &event);
3170     }
3171
3172     /* if the client is shrinking, then resize the frame before the client.
3173
3174        both of these resize sections may run, because the top one only resizes
3175        in the direction that is growing
3176      */
3177     if (send_resize_client && (w <= oldclient.width || h <= oldclient.height))
3178     {
3179         frame_adjust_client_area(self->frame);
3180         XMoveResizeWindow(obt_display, self->window,
3181                           self->frame->size.left, self->frame->size.top, w, h);
3182     }
3183
3184     XFlush(obt_display);
3185
3186     /* if it moved between monitors, then this can affect the stacking
3187        layer of this window or others - for fullscreen windows.
3188        also if it changed to/from oldschool fullscreen then its layer may
3189        change
3190
3191        watch out tho, don't try change stacking stuff if the window is no
3192        longer being managed !
3193     */
3194     if (self->managed &&
3195         (screen_find_monitor(&self->frame->area) !=
3196          screen_find_monitor(&oldframe) ||
3197          (final && (client_is_oldfullscreen(self, &oldclient) !=
3198                     client_is_oldfullscreen(self, &self->area)))))
3199     {
3200         client_calc_layer(self);
3201     }
3202 }
3203
3204 void client_fullscreen(ObClient *self, gboolean fs)
3205 {
3206     gint x, y, w, h;
3207
3208     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
3209         self->fullscreen == fs) return;                   /* already done */
3210
3211     self->fullscreen = fs;
3212     client_change_state(self); /* change the state hints on the client */
3213
3214     if (fs) {
3215         self->pre_fullscreen_area = self->area;
3216         self->pre_fullscreen_max_horz = self->max_horz;
3217         self->pre_fullscreen_max_vert = self->max_vert;
3218
3219         /* if the window is maximized, its area isn't all that meaningful.
3220            save its premax area instead. */
3221         if (self->max_horz) {
3222             self->pre_fullscreen_area.x = self->pre_max_area.x;
3223             self->pre_fullscreen_area.width = self->pre_max_area.width;
3224         }
3225         if (self->max_vert) {
3226             self->pre_fullscreen_area.y = self->pre_max_area.y;
3227             self->pre_fullscreen_area.height = self->pre_max_area.height;
3228         }
3229
3230         /* these will help configure_full figure out where to fullscreen
3231            the window */
3232         x = self->area.x;
3233         y = self->area.y;
3234         w = self->area.width;
3235         h = self->area.height;
3236     } else {
3237         g_assert(self->pre_fullscreen_area.width > 0 &&
3238                  self->pre_fullscreen_area.height > 0);
3239
3240         self->max_horz = self->pre_fullscreen_max_horz;
3241         self->max_vert = self->pre_fullscreen_max_vert;
3242         if (self->max_horz) {
3243             self->pre_max_area.x = self->pre_fullscreen_area.x;
3244             self->pre_max_area.width = self->pre_fullscreen_area.width;
3245         }
3246         if (self->max_vert) {
3247             self->pre_max_area.y = self->pre_fullscreen_area.y;
3248             self->pre_max_area.height = self->pre_fullscreen_area.height;
3249         }
3250
3251         x = self->pre_fullscreen_area.x;
3252         y = self->pre_fullscreen_area.y;
3253         w = self->pre_fullscreen_area.width;
3254         h = self->pre_fullscreen_area.height;
3255         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3256     }
3257
3258     ob_debug("Window %s going fullscreen (%d)",
3259              self->title, self->fullscreen);
3260
3261     if (fs) {
3262         /* make sure the window is on some monitor */
3263         client_find_onscreen(self, &x, &y, w, h, FALSE);
3264     }
3265
3266     client_setup_decor_and_functions(self, FALSE);
3267     client_move_resize(self, x, y, w, h);
3268
3269     /* and adjust our layer/stacking. do this after resizing the window,
3270        and applying decorations, because windows which fill the screen are
3271        considered "fullscreen" and it affects their layer */
3272     client_calc_layer(self);
3273
3274     if (fs) {
3275         /* try focus us when we go into fullscreen mode */
3276         client_focus(self);
3277     }
3278 }
3279
3280 static void client_iconify_recursive(ObClient *self,
3281                                      gboolean iconic, gboolean curdesk,
3282                                      gboolean hide_animation)
3283 {
3284     GSList *it;
3285     gboolean changed = FALSE;
3286
3287     if (self->iconic != iconic) {
3288         ob_debug("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
3289                  self->window);
3290
3291         if (iconic) {
3292             /* don't let non-normal windows iconify along with their parents
3293                or whatever */
3294             if (client_normal(self)) {
3295                 self->iconic = iconic;
3296
3297                 /* update the focus lists.. iconic windows go to the bottom of
3298                    the list. this will also call focus_cycle_addremove(). */
3299                 focus_order_to_bottom(self);
3300
3301                 changed = TRUE;
3302             }
3303         } else {
3304             self->iconic = iconic;
3305
3306             if (curdesk && self->desktop != screen_desktop &&
3307                 self->desktop != DESKTOP_ALL)
3308                 client_set_desktop(self, screen_desktop, FALSE, FALSE);
3309
3310             /* this puts it after the current focused window, this will
3311                also cause focus_cycle_addremove() to be called for the
3312                client */
3313             focus_order_like_new(self);
3314
3315             changed = TRUE;
3316         }
3317     }
3318
3319     if (changed) {
3320         client_change_state(self);
3321         if (config_animate_iconify && !hide_animation)
3322             frame_begin_iconify_animation(self->frame, iconic);
3323         /* do this after starting the animation so it doesn't flash */
3324         client_showhide(self);
3325     }
3326
3327     /* iconify all direct transients, and deiconify all transients
3328        (non-direct too) */
3329     for (it = self->transients; it; it = g_slist_next(it))
3330         if (it->data != self)
3331             if (client_is_direct_child(self, it->data) || !iconic)
3332                 client_iconify_recursive(it->data, iconic, curdesk,
3333                                          hide_animation);
3334 }
3335
3336 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3337                     gboolean hide_animation)
3338 {
3339     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3340         /* move up the transient chain as far as possible first */
3341         self = client_search_top_direct_parent(self);
3342         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3343     }
3344 }
3345
3346 void client_maximize(ObClient *self, gboolean max, gint dir)
3347 {
3348     gint x, y, w, h;
3349
3350     g_assert(dir == 0 || dir == 1 || dir == 2);
3351     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && max) return;/* can't */
3352
3353     /* check if already done */
3354     if (max) {
3355         if (dir == 0 && self->max_horz && self->max_vert) return;
3356         if (dir == 1 && self->max_horz) return;
3357         if (dir == 2 && self->max_vert) return;
3358     } else {
3359         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3360         if (dir == 1 && !self->max_horz) return;
3361         if (dir == 2 && !self->max_vert) return;
3362     }
3363
3364     /* these will help configure_full figure out which screen to fill with
3365        the window */
3366     x = self->area.x;
3367     y = self->area.y;
3368     w = self->area.width;
3369     h = self->area.height;
3370
3371     if (max) {
3372         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3373             RECT_SET(self->pre_max_area,
3374                      self->area.x, self->pre_max_area.y,
3375                      self->area.width, self->pre_max_area.height);
3376         }
3377         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3378             RECT_SET(self->pre_max_area,
3379                      self->pre_max_area.x, self->area.y,
3380                      self->pre_max_area.width, self->area.height);
3381         }
3382     } else {
3383         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3384             g_assert(self->pre_max_area.width > 0);
3385
3386             x = self->pre_max_area.x;
3387             w = self->pre_max_area.width;
3388
3389             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3390                      0, self->pre_max_area.height);
3391         }
3392         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3393             g_assert(self->pre_max_area.height > 0);
3394
3395             y = self->pre_max_area.y;
3396             h = self->pre_max_area.height;
3397
3398             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3399                      self->pre_max_area.width, 0);
3400         }
3401     }
3402
3403     if (dir == 0 || dir == 1) /* horz */
3404         self->max_horz = max;
3405     if (dir == 0 || dir == 2) /* vert */
3406         self->max_vert = max;
3407
3408     if (max) {
3409         /* make sure the window is on some monitor */
3410         client_find_onscreen(self, &x, &y, w, h, FALSE);
3411     }
3412
3413     client_change_state(self); /* change the state hints on the client */
3414
3415     client_setup_decor_and_functions(self, FALSE);
3416     client_move_resize(self, x, y, w, h);
3417 }
3418
3419 void client_shade(ObClient *self, gboolean shade)
3420 {
3421     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3422          shade) ||                         /* can't shade */
3423         self->shaded == shade) return;     /* already done */
3424
3425     self->shaded = shade;
3426     client_change_state(self);
3427     client_change_wm_state(self); /* the window is being hidden/shown */
3428     /* resize the frame to just the titlebar */
3429     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
3430 }
3431
3432 static void client_ping_event(ObClient *self, gboolean dead)
3433 {
3434     if (self->not_responding != dead) {
3435         self->not_responding = dead;
3436         client_update_title(self);
3437
3438         if (dead)
3439             /* the client isn't responding, so ask to kill it */
3440             client_prompt_kill(self);
3441         else {
3442             /* it came back to life ! */
3443
3444             if (self->kill_prompt) {
3445                 prompt_unref(self->kill_prompt);
3446                 self->kill_prompt = NULL;
3447             }
3448
3449             self->kill_level = 0;
3450         }
3451     }
3452 }
3453
3454 void client_close(ObClient *self)
3455 {
3456     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3457
3458     /* if closing an internal obprompt, that is just cancelling it */
3459     if (self->prompt) {
3460         prompt_cancel(self->prompt);
3461         return;
3462     }
3463
3464     /* in the case that the client provides no means to requesting that it
3465        close, we just kill it */
3466     if (!self->delete_window)
3467         /* don't use client_kill(), we should only kill based on PID in
3468            response to a lack of PING replies */
3469         XKillClient(obt_display, self->window);
3470     else {
3471         /* request the client to close with WM_DELETE_WINDOW */
3472         OBT_PROP_MSG_TO(self->window, self->window, WM_PROTOCOLS,
3473                         OBT_PROP_ATOM(WM_DELETE_WINDOW), event_time(),
3474                         0, 0, 0, NoEventMask);
3475
3476         /* we're trying to close the window, so see if it is responding. if it
3477            is not, then we will let them kill the window */
3478         if (self->ping)
3479             ping_start(self, client_ping_event);
3480
3481         /* if we already know the window isn't responding (maybe they clicked
3482            no in the kill dialog but it hasn't come back to life), then show
3483            the kill dialog */
3484         if (self->not_responding)
3485             client_prompt_kill(self);
3486     }
3487 }
3488
3489 #define OB_KILL_RESULT_NO 0
3490 #define OB_KILL_RESULT_YES 1
3491
3492 static gboolean client_kill_requested(ObPrompt *p, gint result, gpointer data)
3493 {
3494     ObClient *self = data;
3495
3496     if (result == OB_KILL_RESULT_YES)
3497         client_kill(self);
3498     return TRUE; /* call the cleanup func */
3499 }
3500
3501 static void client_kill_cleanup(ObPrompt *p, gpointer data)
3502 {
3503     ObClient *self = data;
3504
3505     g_assert(p == self->kill_prompt);
3506
3507     prompt_unref(self->kill_prompt);
3508     self->kill_prompt = NULL;
3509 }
3510
3511 static void client_prompt_kill(ObClient *self)
3512 {
3513     /* check if we're already prompting */
3514     if (!self->kill_prompt) {
3515         ObPromptAnswer answers[] = {
3516             { 0, OB_KILL_RESULT_NO },
3517             { 0, OB_KILL_RESULT_YES }
3518         };
3519         gchar *m;
3520         const gchar *y, *title;
3521
3522         title = self->original_title;
3523         if (title[0] == '\0') {
3524             /* empty string, so use its parent */
3525             ObClient *p = client_search_top_direct_parent(self);
3526             if (p) title = p->original_title;
3527         }
3528
3529         if (client_on_localhost(self)) {
3530             const gchar *sig;
3531
3532             if (self->kill_level == 0)
3533                 sig = "terminate";
3534             else
3535                 sig = "kill";
3536
3537             m = g_strdup_printf
3538                 (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"),
3539                  title, sig);
3540             y = _("End Process");
3541         }
3542         else {
3543             m = g_strdup_printf
3544                 (_("The window \"%s\" does not seem to be responding.  Do you want to disconnect it from the X server?"),
3545                  title);
3546             y = _("Disconnect");
3547         }
3548         /* set the dialog buttons' text */
3549         answers[0].text = _("Cancel");  /* "no" */
3550         answers[1].text = y;            /* "yes" */
3551
3552         self->kill_prompt = prompt_new(m, NULL, answers,
3553                                        sizeof(answers)/sizeof(answers[0]),
3554                                        OB_KILL_RESULT_NO, /* default = no */
3555                                        OB_KILL_RESULT_NO, /* cancel = no */
3556                                        client_kill_requested,
3557                                        client_kill_cleanup,
3558                                        self);
3559         g_free(m);
3560     }
3561
3562     prompt_show(self->kill_prompt, self, TRUE);
3563 }
3564
3565 void client_kill(ObClient *self)
3566 {
3567     /* don't kill our own windows */
3568     if (self->prompt) return;
3569
3570     if (client_on_localhost(self) && self->pid) {
3571         /* running on the local host */
3572         if (self->kill_level == 0) {
3573             ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
3574                      self->window, self->pid);
3575             kill(self->pid, SIGTERM);
3576             ++self->kill_level;
3577
3578             /* show that we're trying to kill it */
3579             client_update_title(self);
3580         }
3581         else {
3582             ob_debug("killing window 0x%x with pid %lu, with SIGKILL",
3583                      self->window, self->pid);
3584             kill(self->pid, SIGKILL); /* kill -9 */
3585         }
3586     }
3587     else {
3588         /* running on a remote host */
3589         XKillClient(obt_display, self->window);
3590     }
3591 }
3592
3593 void client_hilite(ObClient *self, gboolean hilite)
3594 {
3595     if (self->demands_attention == hilite)
3596         return; /* no change */
3597
3598     /* don't allow focused windows to hilite */
3599     self->demands_attention = hilite && !client_focused(self);
3600     if (self->frame != NULL) { /* if we're mapping, just set the state */
3601         if (self->demands_attention) {
3602             frame_flash_start(self->frame);
3603
3604             /* if the window is on another desktop then raise it and make it
3605                the most recently used window */
3606             if (self->desktop != screen_desktop &&
3607                 self->desktop != DESKTOP_ALL)
3608             {
3609                 stacking_raise(CLIENT_AS_WINDOW(self));
3610                 focus_order_to_top(self);
3611             }
3612         }
3613         else
3614             frame_flash_stop(self->frame);
3615         client_change_state(self);
3616     }
3617 }
3618
3619 static void client_set_desktop_recursive(ObClient *self,
3620                                          guint target,
3621                                          gboolean donthide,
3622                                          gboolean dontraise)
3623 {
3624     guint old;
3625     GSList *it;
3626
3627     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3628
3629         ob_debug("Setting desktop %u", target+1);
3630
3631         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3632
3633         old = self->desktop;
3634         self->desktop = target;
3635         OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, target);
3636         /* the frame can display the current desktop state */
3637         frame_adjust_state(self->frame);
3638         /* 'move' the window to the new desktop */
3639         if (!donthide)
3640             client_hide(self);
3641         client_show(self);
3642         /* raise if it was not already on the desktop */
3643         if (old != DESKTOP_ALL && !dontraise)
3644             stacking_raise(CLIENT_AS_WINDOW(self));
3645         if (STRUT_EXISTS(self->strut))
3646             screen_update_areas();
3647         else
3648             /* the new desktop's geometry may be different, so we may need to
3649                resize, for example if we are maximized */
3650             client_reconfigure(self, FALSE);
3651
3652         focus_cycle_addremove(self, FALSE);
3653     }
3654
3655     /* move all transients */
3656     for (it = self->transients; it; it = g_slist_next(it))
3657         if (it->data != self)
3658             if (client_is_direct_child(self, it->data))
3659                 client_set_desktop_recursive(it->data, target,
3660                                              donthide, dontraise);
3661 }
3662
3663 void client_set_desktop(ObClient *self, guint target,
3664                         gboolean donthide, gboolean dontraise)
3665 {
3666     self = client_search_top_direct_parent(self);
3667     client_set_desktop_recursive(self, target, donthide, dontraise);
3668
3669     focus_cycle_addremove(NULL, TRUE);
3670 }
3671
3672 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3673 {
3674     while (child != parent && (child = client_direct_parent(child)));
3675     return child == parent;
3676 }
3677
3678 ObClient *client_search_modal_child(ObClient *self)
3679 {
3680     GSList *it;
3681     ObClient *ret;
3682
3683     for (it = self->transients; it; it = g_slist_next(it)) {
3684         ObClient *c = it->data;
3685         if ((ret = client_search_modal_child(c))) return ret;
3686         if (c->modal) return c;
3687     }
3688     return NULL;
3689 }
3690
3691 struct ObClientFindDestroyUnmap {
3692     Window window;
3693     gint ignore_unmaps;
3694 };
3695
3696 static gboolean find_destroy_unmap(XEvent *e, gpointer data)
3697 {
3698     struct ObClientFindDestroyUnmap *find = data;
3699     if (e->type == DestroyNotify)
3700         return e->xdestroywindow.window == find->window;
3701     if (e->type == UnmapNotify && e->xunmap.window == find->window)
3702         /* ignore the first $find->ignore_unmaps$ many unmap events */
3703         return --find->ignore_unmaps < 0;
3704     return FALSE;
3705 }
3706
3707 gboolean client_validate(ObClient *self)
3708 {
3709     struct ObClientFindDestroyUnmap find;
3710
3711     XSync(obt_display, FALSE); /* get all events on the server */
3712
3713     find.window = self->window;
3714     find.ignore_unmaps = self->ignore_unmaps;
3715     if (xqueue_exists_local(find_destroy_unmap, &find))
3716         return FALSE;
3717
3718     return TRUE;
3719 }
3720
3721 void client_set_wm_state(ObClient *self, glong state)
3722 {
3723     if (state == self->wmstate) return; /* no change */
3724
3725     switch (state) {
3726     case IconicState:
3727         client_iconify(self, TRUE, TRUE, FALSE);
3728         break;
3729     case NormalState:
3730         client_iconify(self, FALSE, TRUE, FALSE);
3731         break;
3732     }
3733 }
3734
3735 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3736 {
3737     gboolean shaded = self->shaded;
3738     gboolean fullscreen = self->fullscreen;
3739     gboolean undecorated = self->undecorated;
3740     gboolean max_horz = self->max_horz;
3741     gboolean max_vert = self->max_vert;
3742     gboolean modal = self->modal;
3743     gboolean iconic = self->iconic;
3744     gboolean demands_attention = self->demands_attention;
3745     gboolean above = self->above;
3746     gboolean below = self->below;
3747     gint i;
3748     gboolean value;
3749
3750     if (!(action == OBT_PROP_ATOM(NET_WM_STATE_ADD) ||
3751           action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) ||
3752           action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)))
3753         /* an invalid action was passed to the client message, ignore it */
3754         return;
3755
3756     for (i = 0; i < 2; ++i) {
3757         Atom state = i == 0 ? data1 : data2;
3758
3759         if (!state) continue;
3760
3761         /* if toggling, then pick whether we're adding or removing */
3762         if (action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)) {
3763             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
3764                 value = modal;
3765             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT))
3766                 value = self->max_vert;
3767             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ))
3768                 value = self->max_horz;
3769             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
3770                 value = shaded;
3771             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
3772                 value = self->skip_taskbar;
3773             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
3774                 value = self->skip_pager;
3775             else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
3776                 value = self->iconic;
3777             else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
3778                 value = fullscreen;
3779             else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
3780                 value = self->above;
3781             else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
3782                 value = self->below;
3783             else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
3784                 value = self->demands_attention;
3785             else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
3786                 value = undecorated;
3787             else
3788                 g_assert_not_reached();
3789             action = value ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3790                              OBT_PROP_ATOM(NET_WM_STATE_ADD);
3791         }
3792
3793         value = action == OBT_PROP_ATOM(NET_WM_STATE_ADD);
3794         if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3795             modal = value;
3796         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3797             max_vert = value;
3798         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3799             max_horz = value;
3800         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3801             shaded = value;
3802         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3803             self->skip_taskbar = value;
3804         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3805             self->skip_pager = value;
3806         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3807             iconic = value;
3808         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3809             fullscreen = value;
3810         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3811             above = value;
3812             /* only unset below when setting above, otherwise you can't get to
3813                the normal layer */
3814             if (value)
3815                 below = FALSE;
3816         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3817             /* and vice versa */
3818             if (value)
3819                 above = FALSE;
3820             below = value;
3821         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3822             demands_attention = value;
3823         } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3824             undecorated = value;
3825         }
3826     }
3827
3828     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3829         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3830             /* toggling both */
3831             if (max_horz == max_vert) { /* both going the same way */
3832                 client_maximize(self, max_horz, 0);
3833             } else {
3834                 client_maximize(self, max_horz, 1);
3835                 client_maximize(self, max_vert, 2);
3836             }
3837         } else {
3838             /* toggling one */
3839             if (max_horz != self->max_horz)
3840                 client_maximize(self, max_horz, 1);
3841             else
3842                 client_maximize(self, max_vert, 2);
3843         }
3844     }
3845     /* change fullscreen state before shading, as it will affect if the window
3846        can shade or not */
3847     if (fullscreen != self->fullscreen)
3848         client_fullscreen(self, fullscreen);
3849     if (shaded != self->shaded)
3850         client_shade(self, shaded);
3851     if (undecorated != self->undecorated)
3852         client_set_undecorated(self, undecorated);
3853     if (above != self->above || below != self->below) {
3854         self->above = above;
3855         self->below = below;
3856         client_calc_layer(self);
3857     }
3858
3859     if (modal != self->modal) {
3860         self->modal = modal;
3861         /* when a window changes modality, then its stacking order with its
3862            transients needs to change */
3863         stacking_raise(CLIENT_AS_WINDOW(self));
3864
3865         /* it also may get focused. if something is focused that shouldn't
3866            be focused anymore, then move the focus */
3867         if (focus_client && client_focus_target(focus_client) != focus_client)
3868             client_focus(focus_client);
3869     }
3870
3871     if (iconic != self->iconic)
3872         client_iconify(self, iconic, FALSE, FALSE);
3873
3874     if (demands_attention != self->demands_attention)
3875         client_hilite(self, demands_attention);
3876
3877     client_change_state(self); /* change the hint to reflect these changes */
3878
3879     focus_cycle_addremove(self, TRUE);
3880 }
3881
3882 ObClient *client_focus_target(ObClient *self)
3883 {
3884     ObClient *child = NULL;
3885
3886     child = client_search_modal_child(self);
3887     if (child) return child;
3888     return self;
3889 }
3890
3891 gboolean client_can_focus(ObClient *self)
3892 {
3893     /* choose the correct target */
3894     self = client_focus_target(self);
3895
3896     if (!self->frame->visible)
3897         return FALSE;
3898
3899     if (!(self->can_focus || self->focus_notify))
3900         return FALSE;
3901
3902     return TRUE;
3903 }
3904
3905 gboolean client_focus(ObClient *self)
3906 {
3907     if (!client_validate(self)) return FALSE;
3908
3909     /* we might not focus this window, so if we have modal children which would
3910        be focused instead, bring them to this desktop */
3911     client_bring_modal_windows(self);
3912
3913     /* choose the correct target */
3914     self = client_focus_target(self);
3915
3916     if (!client_can_focus(self)) {
3917         ob_debug_type(OB_DEBUG_FOCUS,
3918                       "Client %s can't be focused", self->title);
3919         return FALSE;
3920     }
3921
3922     ob_debug_type(OB_DEBUG_FOCUS,
3923                   "Focusing client \"%s\" (0x%x) at time %u",
3924                   self->title, self->window, event_time());
3925
3926     /* if using focus_delay, stop the timer now so that focus doesn't
3927        go moving on us */
3928     event_halt_focus_delay();
3929
3930     obt_display_ignore_errors(TRUE);
3931
3932     if (self->can_focus) {
3933         /* This can cause a BadMatch error with CurrentTime, or if an app
3934            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3935         XSetInputFocus(obt_display, self->window, RevertToPointerRoot,
3936                        event_time());
3937     }
3938
3939     if (self->focus_notify) {
3940         XEvent ce;
3941         ce.xclient.type = ClientMessage;
3942         ce.xclient.message_type = OBT_PROP_ATOM(WM_PROTOCOLS);
3943         ce.xclient.display = obt_display;
3944         ce.xclient.window = self->window;
3945         ce.xclient.format = 32;
3946         ce.xclient.data.l[0] = OBT_PROP_ATOM(WM_TAKE_FOCUS);
3947         ce.xclient.data.l[1] = event_time();
3948         ce.xclient.data.l[2] = 0l;
3949         ce.xclient.data.l[3] = 0l;
3950         ce.xclient.data.l[4] = 0l;
3951         XSendEvent(obt_display, self->window, FALSE, NoEventMask, &ce);
3952     }
3953
3954     obt_display_ignore_errors(FALSE);
3955
3956     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d",
3957                   obt_display_error_occured);
3958     return !obt_display_error_occured;
3959 }
3960
3961 static void client_present(ObClient *self, gboolean here, gboolean raise,
3962                            gboolean unshade)
3963 {
3964     if (client_normal(self) && screen_showing_desktop)
3965         screen_show_desktop(FALSE, self);
3966     if (self->iconic)
3967         client_iconify(self, FALSE, here, FALSE);
3968     if (self->desktop != DESKTOP_ALL &&
3969         self->desktop != screen_desktop)
3970     {
3971         if (here)
3972             client_set_desktop(self, screen_desktop, FALSE, TRUE);
3973         else
3974             screen_set_desktop(self->desktop, FALSE);
3975     } else if (!self->frame->visible)
3976         /* if its not visible for other reasons, then don't mess
3977            with it */
3978         return;
3979     if (self->shaded && unshade)
3980         client_shade(self, FALSE);
3981     if (raise)
3982         stacking_raise(CLIENT_AS_WINDOW(self));
3983
3984     client_focus(self);
3985 }
3986
3987 /* this function exists to map to the net_active_window message in the ewmh */
3988 void client_activate(ObClient *self, gboolean desktop,
3989                      gboolean here, gboolean raise,
3990                      gboolean unshade, gboolean user)
3991 {
3992     self = client_focus_target(self);
3993
3994     if (client_can_steal_focus(self, desktop, user, event_time(), CurrentTime))
3995         client_present(self, here, raise, unshade);
3996     else
3997         client_hilite(self, TRUE);
3998 }
3999
4000 static void client_bring_windows_recursive(ObClient *self,
4001                                            guint desktop,
4002                                            gboolean helpers,
4003                                            gboolean modals,
4004                                            gboolean iconic)
4005 {
4006     GSList *it;
4007
4008     for (it = self->transients; it; it = g_slist_next(it))
4009         client_bring_windows_recursive(it->data, desktop,
4010                                        helpers, modals, iconic);
4011
4012     if (((helpers && client_helper(self)) ||
4013          (modals && self->modal)) &&
4014         ((self->desktop != desktop && self->desktop != DESKTOP_ALL) ||
4015          (iconic && self->iconic)))
4016     {
4017         if (iconic && self->iconic)
4018             client_iconify(self, FALSE, TRUE, FALSE);
4019         else
4020             client_set_desktop(self, desktop, FALSE, FALSE);
4021     }
4022 }
4023
4024 void client_bring_helper_windows(ObClient *self)
4025 {
4026     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
4027 }
4028
4029 void client_bring_modal_windows(ObClient *self)
4030 {
4031     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
4032 }
4033
4034 gboolean client_focused(ObClient *self)
4035 {
4036     return self == focus_client;
4037 }
4038
4039 RrImage* client_icon(ObClient *self)
4040 {
4041     RrImage *ret = NULL;
4042
4043     if (self->icon_set)
4044         ret = self->icon_set;
4045     else if (self->parents) {
4046         GSList *it;
4047         for (it = self->parents; it && !ret; it = g_slist_next(it))
4048             ret = client_icon(it->data);
4049     }
4050     if (!ret)
4051         ret = client_default_icon;
4052     return ret;
4053 }
4054
4055 void client_set_layer(ObClient *self, gint layer)
4056 {
4057     if (layer < 0) {
4058         self->below = TRUE;
4059         self->above = FALSE;
4060     } else if (layer == 0) {
4061         self->below = self->above = FALSE;
4062     } else {
4063         self->below = FALSE;
4064         self->above = TRUE;
4065     }
4066     client_calc_layer(self);
4067     client_change_state(self); /* reflect this in the state hints */
4068 }
4069
4070 void client_set_undecorated(ObClient *self, gboolean undecorated)
4071 {
4072     if (self->undecorated != undecorated &&
4073         /* don't let it undecorate if the function is missing, but let
4074            it redecorate */
4075         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
4076     {
4077         self->undecorated = undecorated;
4078         client_setup_decor_and_functions(self, TRUE);
4079         client_change_state(self); /* reflect this in the state hints */
4080     }
4081 }
4082
4083 guint client_monitor(ObClient *self)
4084 {
4085     return screen_find_monitor(&self->frame->area);
4086 }
4087
4088 ObClient *client_direct_parent(ObClient *self)
4089 {
4090     if (!self->parents) return NULL;
4091     if (self->transient_for_group) return NULL;
4092     return self->parents->data;
4093 }
4094
4095 ObClient *client_search_top_direct_parent(ObClient *self)
4096 {
4097     ObClient *p;
4098     while ((p = client_direct_parent(self))) self = p;
4099     return self;
4100 }
4101
4102 static GSList *client_search_all_top_parents_internal(ObClient *self,
4103                                                       gboolean bylayer,
4104                                                       ObStackingLayer layer)
4105 {
4106     GSList *ret;
4107     ObClient *p;
4108
4109     /* move up the direct transient chain as far as possible */
4110     while ((p = client_direct_parent(self)) &&
4111            (!bylayer || p->layer == layer))
4112         self = p;
4113
4114     if (!self->parents)
4115         ret = g_slist_prepend(NULL, self);
4116     else
4117         ret = g_slist_copy(self->parents);
4118
4119     return ret;
4120 }
4121
4122 GSList *client_search_all_top_parents(ObClient *self)
4123 {
4124     return client_search_all_top_parents_internal(self, FALSE, 0);
4125 }
4126
4127 GSList *client_search_all_top_parents_layer(ObClient *self)
4128 {
4129     return client_search_all_top_parents_internal(self, TRUE, self->layer);
4130 }
4131
4132 ObClient *client_search_focus_parent(ObClient *self)
4133 {
4134     GSList *it;
4135
4136     for (it = self->parents; it; it = g_slist_next(it))
4137         if (client_focused(it->data)) return it->data;
4138
4139     return NULL;
4140 }
4141
4142 ObClient *client_search_focus_parent_full(ObClient *self)
4143 {
4144     GSList *it;
4145     ObClient *ret = NULL;
4146
4147     for (it = self->parents; it; it = g_slist_next(it)) {
4148         if (client_focused(it->data))
4149             ret = it->data;
4150         else
4151             ret = client_search_focus_parent_full(it->data);
4152         if (ret) break;
4153     }
4154     return ret;
4155 }
4156
4157 ObClient *client_search_parent(ObClient *self, ObClient *search)
4158 {
4159     GSList *it;
4160
4161     for (it = self->parents; it; it = g_slist_next(it))
4162         if (it->data == search) return search;
4163
4164     return NULL;
4165 }
4166
4167 ObClient *client_search_transient(ObClient *self, ObClient *search)
4168 {
4169     GSList *sit;
4170
4171     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
4172         if (sit->data == search)
4173             return search;
4174         if (client_search_transient(sit->data, search))
4175             return search;
4176     }
4177     return NULL;
4178 }
4179
4180 static void detect_edge(Rect area, ObDirection dir,
4181                         gint my_head, gint my_size,
4182                         gint my_edge_start, gint my_edge_size,
4183                         gint *dest, gboolean *near_edge)
4184 {
4185     gint edge_start, edge_size, head, tail;
4186     gboolean skip_head = FALSE, skip_tail = FALSE;
4187
4188     switch (dir) {
4189         case OB_DIRECTION_NORTH:
4190         case OB_DIRECTION_SOUTH:
4191             edge_start = area.x;
4192             edge_size = area.width;
4193             break;
4194         case OB_DIRECTION_EAST:
4195         case OB_DIRECTION_WEST:
4196             edge_start = area.y;
4197             edge_size = area.height;
4198             break;
4199         default:
4200             g_assert_not_reached();
4201     }
4202
4203     /* do we collide with this window? */
4204     if (!RANGES_INTERSECT(my_edge_start, my_edge_size,
4205                 edge_start, edge_size))
4206         return;
4207
4208     switch (dir) {
4209         case OB_DIRECTION_NORTH:
4210             head = RECT_BOTTOM(area);
4211             tail = RECT_TOP(area);
4212             break;
4213         case OB_DIRECTION_SOUTH:
4214             head = RECT_TOP(area);
4215             tail = RECT_BOTTOM(area);
4216             break;
4217         case OB_DIRECTION_WEST:
4218             head = RECT_RIGHT(area);
4219             tail = RECT_LEFT(area);
4220             break;
4221         case OB_DIRECTION_EAST:
4222             head = RECT_LEFT(area);
4223             tail = RECT_RIGHT(area);
4224             break;
4225         default:
4226             g_assert_not_reached();
4227     }
4228     switch (dir) {
4229         case OB_DIRECTION_NORTH:
4230         case OB_DIRECTION_WEST:
4231             /* check if our window is past the head of this window */
4232             if (my_head <= head + 1)
4233                 skip_head = TRUE;
4234             /* check if our window's tail is past the tail of this window */
4235             if (my_head + my_size - 1 <= tail)
4236                 skip_tail = TRUE;
4237             /* check if the head of this window is closer than the previously
4238                chosen edge (take into account that the previously chosen
4239                edge might have been a tail, not a head) */
4240             if (head + (*near_edge ? 0 : my_size) <= *dest)
4241                 skip_head = TRUE;
4242             /* check if the tail of this window is closer than the previously
4243                chosen edge (take into account that the previously chosen
4244                edge might have been a head, not a tail) */
4245             if (tail - (!*near_edge ? 0 : my_size) <= *dest)
4246                 skip_tail = TRUE;
4247             break;
4248         case OB_DIRECTION_SOUTH:
4249         case OB_DIRECTION_EAST:
4250             /* check if our window is past the head of this window */
4251             if (my_head >= head - 1)
4252                 skip_head = TRUE;
4253             /* check if our window's tail is past the tail of this window */
4254             if (my_head - my_size + 1 >= tail)
4255                 skip_tail = TRUE;
4256             /* check if the head of this window is closer than the previously
4257                chosen edge (take into account that the previously chosen
4258                edge might have been a tail, not a head) */
4259             if (head - (*near_edge ? 0 : my_size) >= *dest)
4260                 skip_head = TRUE;
4261             /* check if the tail of this window is closer than the previously
4262                chosen edge (take into account that the previously chosen
4263                edge might have been a head, not a tail) */
4264             if (tail + (!*near_edge ? 0 : my_size) >= *dest)
4265                 skip_tail = TRUE;
4266             break;
4267         default:
4268             g_assert_not_reached();
4269     }
4270
4271     ob_debug("my head %d size %d", my_head, my_size);
4272     ob_debug("head %d tail %d dest %d", head, tail, *dest);
4273     if (!skip_head) {
4274         ob_debug("using near edge %d", head);
4275         *dest = head;
4276         *near_edge = TRUE;
4277     }
4278     else if (!skip_tail) {
4279         ob_debug("using far edge %d", tail);
4280         *dest = tail;
4281         *near_edge = FALSE;
4282     }
4283 }
4284
4285 void client_find_edge_directional(ObClient *self, ObDirection dir,
4286                                   gint my_head, gint my_size,
4287                                   gint my_edge_start, gint my_edge_size,
4288                                   gint *dest, gboolean *near_edge)
4289 {
4290     GList *it;
4291     Rect *a;
4292     Rect dock_area;
4293     gint edge;
4294     guint i;
4295
4296     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
4297                     &self->frame->area);
4298
4299     switch (dir) {
4300     case OB_DIRECTION_NORTH:
4301         edge = RECT_TOP(*a) - 1;
4302         break;
4303     case OB_DIRECTION_SOUTH:
4304         edge = RECT_BOTTOM(*a) + 1;
4305         break;
4306     case OB_DIRECTION_EAST:
4307         edge = RECT_RIGHT(*a) + 1;
4308         break;
4309     case OB_DIRECTION_WEST:
4310         edge = RECT_LEFT(*a) - 1;
4311         break;
4312     default:
4313         g_assert_not_reached();
4314     }
4315     /* default to the far edge, then narrow it down */
4316     *dest = edge;
4317     *near_edge = TRUE;
4318
4319     /* search for edges of monitors */
4320     for (i = 0; i < screen_num_monitors; ++i) {
4321         Rect *area = screen_area(self->desktop, i, NULL);
4322         detect_edge(*area, dir, my_head, my_size, my_edge_start,
4323                     my_edge_size, dest, near_edge);
4324         g_slice_free(Rect, area);
4325     }
4326
4327     /* search for edges of clients */
4328     for (it = client_list; it; it = g_list_next(it)) {
4329         ObClient *cur = it->data;
4330
4331         /* skip windows to not bump into */
4332         if (cur == self)
4333             continue;
4334         if (cur->iconic)
4335             continue;
4336         if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&
4337             cur->desktop != screen_desktop)
4338             continue;
4339
4340         ob_debug("trying window %s", cur->title);
4341
4342         detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start,
4343                     my_edge_size, dest, near_edge);
4344     }
4345     dock_get_area(&dock_area);
4346     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
4347                 my_edge_size, dest, near_edge);
4348
4349     g_slice_free(Rect, a);
4350 }
4351
4352 void client_find_move_directional(ObClient *self, ObDirection dir,
4353                                   gint *x, gint *y)
4354 {
4355     gint head, size;
4356     gint e, e_start, e_size;
4357     gboolean near;
4358
4359     switch (dir) {
4360     case OB_DIRECTION_EAST:
4361         head = RECT_RIGHT(self->frame->area);
4362         size = self->frame->area.width;
4363         e_start = RECT_TOP(self->frame->area);
4364         e_size = self->frame->area.height;
4365         break;
4366     case OB_DIRECTION_WEST:
4367         head = RECT_LEFT(self->frame->area);
4368         size = self->frame->area.width;
4369         e_start = RECT_TOP(self->frame->area);
4370         e_size = self->frame->area.height;
4371         break;
4372     case OB_DIRECTION_NORTH:
4373         head = RECT_TOP(self->frame->area);
4374         size = self->frame->area.height;
4375         e_start = RECT_LEFT(self->frame->area);
4376         e_size = self->frame->area.width;
4377         break;
4378     case OB_DIRECTION_SOUTH:
4379         head = RECT_BOTTOM(self->frame->area);
4380         size = self->frame->area.height;
4381         e_start = RECT_LEFT(self->frame->area);
4382         e_size = self->frame->area.width;
4383         break;
4384     default:
4385         g_assert_not_reached();
4386     }
4387
4388     client_find_edge_directional(self, dir, head, size,
4389                                  e_start, e_size, &e, &near);
4390     *x = self->frame->area.x;
4391     *y = self->frame->area.y;
4392     switch (dir) {
4393     case OB_DIRECTION_EAST:
4394         if (near) e -= self->frame->area.width;
4395         else      e++;
4396         *x = e;
4397         break;
4398     case OB_DIRECTION_WEST:
4399         if (near) e++;
4400         else      e -= self->frame->area.width;
4401         *x = e;
4402         break;
4403     case OB_DIRECTION_NORTH:
4404         if (near) e++;
4405         else      e -= self->frame->area.height;
4406         *y = e;
4407         break;
4408     case OB_DIRECTION_SOUTH:
4409         if (near) e -= self->frame->area.height;
4410         else      e++;
4411         *y = e;
4412         break;
4413     default:
4414         g_assert_not_reached();
4415     }
4416     frame_frame_gravity(self->frame, x, y);
4417 }
4418
4419 void client_find_resize_directional(ObClient *self, ObDirection side,
4420                                     gboolean grow,
4421                                     gint *x, gint *y, gint *w, gint *h)
4422 {
4423     gint head;
4424     gint e, e_start, e_size, delta;
4425     gboolean near;
4426     ObDirection dir;
4427
4428     switch (side) {
4429     case OB_DIRECTION_EAST:
4430         head = RECT_RIGHT(self->frame->area) +
4431             (self->size_inc.width - 1) * (grow ? 1 : 0);
4432         e_start = RECT_TOP(self->frame->area);
4433         e_size = self->frame->area.height;
4434         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
4435         break;
4436     case OB_DIRECTION_WEST:
4437         head = RECT_LEFT(self->frame->area) -
4438             (self->size_inc.width - 1) * (grow ? 1 : 0);
4439         e_start = RECT_TOP(self->frame->area);
4440         e_size = self->frame->area.height;
4441         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
4442         break;
4443     case OB_DIRECTION_NORTH:
4444         head = RECT_TOP(self->frame->area) -
4445             (self->size_inc.height - 1) * (grow ? 1 : 0);
4446         e_start = RECT_LEFT(self->frame->area);
4447         e_size = self->frame->area.width;
4448         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
4449         break;
4450     case OB_DIRECTION_SOUTH:
4451         head = RECT_BOTTOM(self->frame->area) +
4452             (self->size_inc.height - 1) * (grow ? 1 : 0);
4453         e_start = RECT_LEFT(self->frame->area);
4454         e_size = self->frame->area.width;
4455         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
4456         break;
4457     default:
4458         g_assert_not_reached();
4459     }
4460
4461     ob_debug("head %d dir %d", head, dir);
4462     client_find_edge_directional(self, dir, head, 1,
4463                                  e_start, e_size, &e, &near);
4464     ob_debug("edge %d", e);
4465     *x = self->frame->area.x;
4466     *y = self->frame->area.y;
4467     *w = self->frame->area.width;
4468     *h = self->frame->area.height;
4469     switch (side) {
4470     case OB_DIRECTION_EAST:
4471         if (grow == near) --e;
4472         delta = e - RECT_RIGHT(self->frame->area);
4473         *w += delta;
4474         break;
4475     case OB_DIRECTION_WEST:
4476         if (grow == near) ++e;
4477         delta = RECT_LEFT(self->frame->area) - e;
4478         *x -= delta;
4479         *w += delta;
4480         break;
4481     case OB_DIRECTION_NORTH:
4482         if (grow == near) ++e;
4483         delta = RECT_TOP(self->frame->area) - e;
4484         *y -= delta;
4485         *h += delta;
4486         break;
4487     case OB_DIRECTION_SOUTH:
4488         if (grow == near) --e;
4489         delta = e - RECT_BOTTOM(self->frame->area);
4490         *h += delta;
4491         break;
4492     default:
4493         g_assert_not_reached();
4494     }
4495     frame_frame_gravity(self->frame, x, y);
4496     *w -= self->frame->size.left + self->frame->size.right;
4497     *h -= self->frame->size.top + self->frame->size.bottom;
4498 }
4499
4500 ObClient* client_under_pointer(void)
4501 {
4502     gint x, y;
4503     GList *it;
4504     ObClient *ret = NULL;
4505
4506     if (screen_pointer_pos(&x, &y)) {
4507         for (it = stacking_list; it; it = g_list_next(it)) {
4508             if (WINDOW_IS_CLIENT(it->data)) {
4509                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4510                 if (c->frame->visible &&
4511                     /* check the desktop, this is done during desktop
4512                        switching and windows are shown/hidden status is not
4513                        reliable */
4514                     (c->desktop == screen_desktop ||
4515                      c->desktop == DESKTOP_ALL) &&
4516                     /* ignore all animating windows */
4517                     !frame_iconify_animating(c->frame) &&
4518                     RECT_CONTAINS(c->frame->area, x, y))
4519                 {
4520                     ret = c;
4521                     break;
4522                 }
4523             }
4524         }
4525     }
4526     return ret;
4527 }
4528
4529 gboolean client_has_group_siblings(ObClient *self)
4530 {
4531     return self->group && self->group->members->next;
4532 }
4533
4534 gboolean client_has_relative(ObClient *self)
4535 {
4536     return client_has_parent(self) ||
4537         client_has_group_siblings(self) ||
4538         client_has_children(self);
4539 }
4540
4541 /*! Returns TRUE if the client is running on the same machine as Openbox */
4542 gboolean client_on_localhost(ObClient *self)
4543 {
4544     return self->client_machine == NULL;
4545 }