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