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