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