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