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