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