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