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