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