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