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