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