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