]> icculus.org git repositories - dana/openbox.git/blob - openbox/client.c
Don't edge-warp with the cursor based on a monitor that does not contain the pointer...
[dana/openbox.git] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    client.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "screen.h"
25 #include "moveresize.h"
26 #include "ping.h"
27 #include "place.h"
28 #include "frame.h"
29 #include "session.h"
30 #include "event.h"
31 #include "grab.h"
32 #include "prompt.h"
33 #include "focus.h"
34 #include "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_mouse_focusable(ObClient *self)
2778 {
2779     return !(self->type == OB_CLIENT_TYPE_MENU ||
2780              self->type == OB_CLIENT_TYPE_TOOLBAR ||
2781              self->type == OB_CLIENT_TYPE_SPLASH ||
2782              self->type == OB_CLIENT_TYPE_DOCK);
2783 }
2784
2785 gboolean client_enter_focusable(ObClient *self)
2786 {
2787     /* you can focus desktops but it shouldn't on enter */
2788     return (client_mouse_focusable(self) &&
2789             self->type != OB_CLIENT_TYPE_DESKTOP);
2790 }
2791
2792 static void client_apply_startup_state(ObClient *self,
2793                                        gint x, gint y, gint w, gint h)
2794 {
2795     /* save the states that we are going to apply */
2796     gboolean iconic = self->iconic;
2797     gboolean fullscreen = self->fullscreen;
2798     gboolean undecorated = self->undecorated;
2799     gboolean shaded = self->shaded;
2800     gboolean demands_attention = self->demands_attention;
2801     gboolean max_horz = self->max_horz;
2802     gboolean max_vert = self->max_vert;
2803     Rect oldarea;
2804     gint l;
2805
2806     /* turn them all off in the client, so they won't affect the window
2807        being placed */
2808     self->iconic = self->fullscreen = self->undecorated = self->shaded =
2809         self->demands_attention = self->max_horz = self->max_vert = FALSE;
2810
2811     /* move the client to its placed position, or it it's already there,
2812        generate a ConfigureNotify telling the client where it is.
2813
2814        do this after adjusting the frame. otherwise it gets all weird and
2815        clients don't work right
2816
2817        do this before applying the states so they have the correct
2818        pre-max/pre-fullscreen values
2819     */
2820     client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE);
2821     ob_debug("placed window 0x%x at %d, %d with size %d x %d",
2822              self->window, x, y, w, h);
2823     /* save the area, and make it where it should be for the premax stuff */
2824     oldarea = self->area;
2825     RECT_SET(self->area, x, y, w, h);
2826
2827     /* apply the states. these are in a carefully crafted order.. */
2828
2829     if (iconic)
2830         client_iconify(self, TRUE, FALSE, TRUE);
2831     if (undecorated)
2832         client_set_undecorated(self, TRUE);
2833     if (shaded)
2834         client_shade(self, TRUE);
2835     if (demands_attention)
2836         client_hilite(self, TRUE);
2837
2838     if (max_vert && max_horz)
2839         client_maximize(self, TRUE, 0);
2840     else if (max_vert)
2841         client_maximize(self, TRUE, 2);
2842     else if (max_horz)
2843         client_maximize(self, TRUE, 1);
2844
2845     /* fullscreen removes the ability to apply other states */
2846     if (fullscreen)
2847         client_fullscreen(self, TRUE);
2848
2849     /* make sure client_setup_decor_and_functions() is called at least once */
2850     client_setup_decor_and_functions(self, FALSE);
2851
2852     /* if the window hasn't been configured yet, then do so now, in fact the
2853        x,y,w,h may _not_ be the same as the area rect, which can end up
2854        meaning that the client isn't properly moved/resized by the fullscreen
2855        function
2856        pho can cause this because it maps at size of the screen but not 0,0
2857        so openbox moves it on screen to 0,0 (thus x,y=0,0 and area.x,y don't).
2858        then fullscreen'ing makes it go to 0,0 which it thinks it already is at
2859        cuz thats where the pre-fullscreen will be. however the actual area is
2860        not, so this needs to be called even if we have fullscreened/maxed
2861     */
2862     self->area = oldarea;
2863     client_configure(self, x, y, w, h, FALSE, TRUE, FALSE);
2864
2865     /* nothing to do for the other states:
2866        skip_taskbar
2867        skip_pager
2868        modal
2869        above
2870        below
2871     */
2872 }
2873
2874 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2875 {
2876     /* these should be the current values. this is for when you're not moving,
2877        just resizing */
2878     g_assert(*x == self->area.x);
2879     g_assert(oldw == self->area.width);
2880
2881     /* horizontal */
2882     switch (self->gravity) {
2883     default:
2884     case NorthWestGravity:
2885     case WestGravity:
2886     case SouthWestGravity:
2887     case StaticGravity:
2888     case ForgetGravity:
2889         break;
2890     case NorthGravity:
2891     case CenterGravity:
2892     case SouthGravity:
2893         *x -= (neww - oldw) / 2;
2894         break;
2895     case NorthEastGravity:
2896     case EastGravity:
2897     case SouthEastGravity:
2898         *x -= neww - oldw;
2899         break;
2900     }
2901 }
2902
2903 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2904 {
2905     /* these should be the current values. this is for when you're not moving,
2906        just resizing */
2907     g_assert(*y == self->area.y);
2908     g_assert(oldh == self->area.height);
2909
2910     /* vertical */
2911     switch (self->gravity) {
2912     default:
2913     case NorthWestGravity:
2914     case NorthGravity:
2915     case NorthEastGravity:
2916     case StaticGravity:
2917     case ForgetGravity:
2918         break;
2919     case WestGravity:
2920     case CenterGravity:
2921     case EastGravity:
2922         *y -= (newh - oldh) / 2;
2923         break;
2924     case SouthWestGravity:
2925     case SouthGravity:
2926     case SouthEastGravity:
2927         *y -= newh - oldh;
2928         break;
2929     }
2930 }
2931
2932 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2933                           gint *logicalw, gint *logicalh,
2934                           gboolean user)
2935 {
2936     Rect desired = {*x, *y, *w, *h};
2937     frame_rect_to_frame(self->frame, &desired);
2938
2939     /* make the frame recalculate its dimensions n shit without changing
2940        anything visible for real, this way the constraints below can work with
2941        the updated frame dimensions. */
2942     frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
2943
2944     /* cap any X windows at the size of an unsigned short */
2945     *w = MIN(*w,
2946              (gint)G_MAXUSHORT
2947              - self->frame->size.left - self->frame->size.right);
2948     *h = MIN(*h,
2949              (gint)G_MAXUSHORT
2950              - self->frame->size.top - self->frame->size.bottom);
2951
2952     /* gets the frame's position */
2953     frame_client_gravity(self->frame, x, y);
2954
2955     /* these positions are frame positions, not client positions */
2956
2957     /* set the size and position if fullscreen */
2958     if (self->fullscreen) {
2959         const Rect *a;
2960         guint i;
2961
2962         i = screen_find_monitor(&desired);
2963         a = screen_physical_area_monitor(i);
2964
2965         *x = a->x;
2966         *y = a->y;
2967         *w = a->width;
2968         *h = a->height;
2969
2970         user = FALSE; /* ignore if the client can't be moved/resized when it
2971                          is fullscreening */
2972     } else if (self->max_horz || self->max_vert) {
2973         Rect *a;
2974         guint i;
2975
2976         /* use all possible struts when maximizing to the full screen */
2977         i = screen_find_monitor(&desired);
2978         a = screen_area(self->desktop, i,
2979                         (self->max_horz && self->max_vert ? NULL : &desired));
2980
2981         /* set the size and position if maximized */
2982         if (self->max_horz) {
2983             *x = a->x;
2984             *w = a->width - self->frame->size.left - self->frame->size.right;
2985         }
2986         if (self->max_vert) {
2987             *y = a->y;
2988             *h = a->height - self->frame->size.top - self->frame->size.bottom;
2989         }
2990
2991         user = FALSE; /* ignore if the client can't be moved/resized when it
2992                          is maximizing */
2993
2994         g_slice_free(Rect, a);
2995     }
2996
2997     /* gets the client's position */
2998     frame_frame_gravity(self->frame, x, y);
2999
3000     /* work within the preferred sizes given by the window, these may have
3001        changed rather than it's requested width and height, so always run
3002        through this code */
3003     {
3004         gint basew, baseh, minw, minh;
3005         gint incw, inch, maxw, maxh;
3006         gfloat minratio, maxratio;
3007
3008         incw = self->size_inc.width;
3009         inch = self->size_inc.height;
3010         minratio = self->fullscreen || (self->max_horz && self->max_vert) ?
3011             0 : self->min_ratio;
3012         maxratio = self->fullscreen || (self->max_horz && self->max_vert) ?
3013             0 : self->max_ratio;
3014
3015         /* base size is substituted with min size if not specified */
3016         if (self->base_size.width >= 0 || self->base_size.height >= 0) {
3017             basew = self->base_size.width;
3018             baseh = self->base_size.height;
3019         } else {
3020             basew = self->min_size.width;
3021             baseh = self->min_size.height;
3022         }
3023         /* min size is substituted with base size if not specified */
3024         if (self->min_size.width || self->min_size.height) {
3025             minw = self->min_size.width;
3026             minh = self->min_size.height;
3027         } else {
3028             minw = self->base_size.width;
3029             minh = self->base_size.height;
3030         }
3031
3032         /* This comment is no longer true */
3033         /* if this is a user-requested resize, then check against min/max
3034            sizes */
3035
3036         /* smaller than min size or bigger than max size? */
3037         if (*w > self->max_size.width) *w = self->max_size.width;
3038         if (*w < minw) *w = minw;
3039         if (*h > self->max_size.height) *h = self->max_size.height;
3040         if (*h < minh) *h = minh;
3041
3042         *w -= basew;
3043         *h -= baseh;
3044
3045         /* the sizes to used for maximized */
3046         maxw = *w;
3047         maxh = *h;
3048
3049         /* keep to the increments */
3050         *w /= incw;
3051         *h /= inch;
3052
3053         /* you cannot resize to nothing */
3054         if (basew + *w < 1) *w = 1 - basew;
3055         if (baseh + *h < 1) *h = 1 - baseh;
3056
3057         /* save the logical size */
3058         *logicalw = incw > 1 ? *w : *w + basew;
3059         *logicalh = inch > 1 ? *h : *h + baseh;
3060
3061         *w *= incw;
3062         *h *= inch;
3063
3064         /* if maximized/fs then don't use the size increments */
3065         if (self->fullscreen || self->max_horz) *w = maxw;
3066         if (self->fullscreen || self->max_vert) *h = maxh;
3067
3068         *w += basew;
3069         *h += baseh;
3070
3071         /* adjust the height to match the width for the aspect ratios.
3072            for this, min size is not substituted for base size ever. */
3073         *w -= self->base_size.width;
3074         *h -= self->base_size.height;
3075
3076         if (minratio)
3077             if (*h * minratio > *w) {
3078                 *h = (gint)(*w / minratio);
3079
3080                 /* you cannot resize to nothing */
3081                 if (*h < 1) {
3082                     *h = 1;
3083                     *w = (gint)(*h * minratio);
3084                 }
3085             }
3086         if (maxratio)
3087             if (*h * maxratio < *w) {
3088                 *h = (gint)(*w / maxratio);
3089
3090                 /* you cannot resize to nothing */
3091                 if (*h < 1) {
3092                     *h = 1;
3093                     *w = (gint)(*h * minratio);
3094                 }
3095             }
3096
3097         *w += self->base_size.width;
3098         *h += self->base_size.height;
3099     }
3100
3101     /* these override the above states! if you cant move you can't move! */
3102     if (user) {
3103         if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
3104             *x = self->area.x;
3105             *y = self->area.y;
3106         }
3107         if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
3108             *w = self->area.width;
3109             *h = self->area.height;
3110         }
3111     }
3112
3113     g_assert(*w > 0);
3114     g_assert(*h > 0);
3115 }
3116
3117 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
3118                       gboolean user, gboolean final, gboolean force_reply)
3119 {
3120     Rect oldframe, oldclient;
3121     gboolean send_resize_client;
3122     gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
3123     gboolean fmoved, fresized;
3124     guint fdecor = self->frame->decorations;
3125     gboolean fhorz = self->frame->max_horz;
3126     gboolean fvert = self->frame->max_vert;
3127     gint logicalw, logicalh;
3128
3129     /* find the new x, y, width, and height (and logical size) */
3130     client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
3131
3132     /* set the logical size if things changed */
3133     if (!(w == self->area.width && h == self->area.height))
3134         SIZE_SET(self->logical_size, logicalw, logicalh);
3135
3136     /* figure out if we moved or resized or what */
3137     moved = (x != self->area.x || y != self->area.y);
3138     resized = (w != self->area.width || h != self->area.height);
3139
3140     oldframe = self->frame->area;
3141     oldclient = self->area;
3142     RECT_SET(self->area, x, y, w, h);
3143
3144     /* for app-requested resizes, always resize if 'resized' is true.
3145        for user-requested ones, only resize if final is true, or when
3146        resizing in redraw mode */
3147     send_resize_client = ((!user && resized) ||
3148                           (user && (final ||
3149                                     (resized && config_resize_redraw))));
3150
3151     /* if the client is enlarging, then resize the client before the frame */
3152     if (send_resize_client && (w > oldclient.width || h > oldclient.height)) {
3153         XMoveResizeWindow(obt_display, self->window,
3154                           self->frame->size.left, self->frame->size.top,
3155                           MAX(w, oldclient.width), MAX(h, oldclient.height));
3156         frame_adjust_client_area(self->frame);
3157     }
3158
3159     /* find the frame's dimensions and move/resize it */
3160     fmoved = moved;
3161     fresized = resized;
3162
3163     /* if decorations changed, then readjust everything for the frame */
3164     if (self->decorations != fdecor ||
3165         self->max_horz != fhorz || self->max_vert != fvert)
3166     {
3167         fmoved = fresized = TRUE;
3168     }
3169
3170     /* adjust the frame */
3171     if (fmoved || fresized) {
3172         gulong ignore_start;
3173         if (!user)
3174             ignore_start = event_start_ignore_all_enters();
3175
3176         /* replay pending pointer event before move the window, in case it
3177            would change what window gets the event */
3178         mouse_replay_pointer();
3179
3180         frame_adjust_area(self->frame, fmoved, fresized, FALSE);
3181
3182         if (!user)
3183             event_end_ignore_all_enters(ignore_start);
3184     }
3185
3186     if (!user || final) {
3187         gint oldrx = self->root_pos.x;
3188         gint oldry = self->root_pos.y;
3189         /* we have reset the client to 0 border width, so don't include
3190            it in these coords */
3191         POINT_SET(self->root_pos,
3192                   self->frame->area.x + self->frame->size.left -
3193                   self->border_width,
3194                   self->frame->area.y + self->frame->size.top -
3195                   self->border_width);
3196         if (self->root_pos.x != oldrx || self->root_pos.y != oldry)
3197             rootmoved = TRUE;
3198     }
3199
3200     /* This is kinda tricky and should not be changed.. let me explain!
3201
3202        When user = FALSE, then the request is coming from the application
3203        itself, and we are more strict about when to send a synthetic
3204        ConfigureNotify.  We strictly follow the rules of the ICCCM sec 4.1.5
3205        in this case (or send one if force_reply is true)
3206
3207        When user = TRUE, then the request is coming from "us", like when we
3208        maximize a window or something.  In this case we are more lenient.  We
3209        used to follow the same rules as above, but _Java_ Swing can't handle
3210        this. So just to appease Swing, when user = TRUE, we always send
3211        a synthetic ConfigureNotify to give the window its root coordinates.
3212        Lastly, if force_reply is TRUE, we always send a
3213        ConfigureNotify, which is needed during a resize with XSYNCronization.
3214     */
3215     if ((!user && !resized && (rootmoved || force_reply)) ||
3216         (user && ((!resized && force_reply) || (final && rootmoved))))
3217     {
3218         XEvent event;
3219
3220         event.type = ConfigureNotify;
3221         event.xconfigure.display = obt_display;
3222         event.xconfigure.event = self->window;
3223         event.xconfigure.window = self->window;
3224
3225         ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d",
3226                  self->title, self->root_pos.x, self->root_pos.y, w, h);
3227
3228         /* root window real coords */
3229         event.xconfigure.x = self->root_pos.x;
3230         event.xconfigure.y = self->root_pos.y;
3231         event.xconfigure.width = w;
3232         event.xconfigure.height = h;
3233         event.xconfigure.border_width = self->border_width;
3234         event.xconfigure.above = None;
3235         event.xconfigure.override_redirect = FALSE;
3236         XSendEvent(event.xconfigure.display, event.xconfigure.window,
3237                    FALSE, StructureNotifyMask, &event);
3238     }
3239
3240     /* if the client is shrinking, then resize the frame before the client.
3241
3242        both of these resize sections may run, because the top one only resizes
3243        in the direction that is growing
3244      */
3245     if (send_resize_client && (w <= oldclient.width || h <= oldclient.height))
3246     {
3247         frame_adjust_client_area(self->frame);
3248         XMoveResizeWindow(obt_display, self->window,
3249                           self->frame->size.left, self->frame->size.top, w, h);
3250     }
3251
3252     XFlush(obt_display);
3253
3254     /* if it moved between monitors, then this can affect the stacking
3255        layer of this window or others - for fullscreen windows.
3256        also if it changed to/from oldschool fullscreen then its layer may
3257        change
3258
3259        watch out tho, don't try change stacking stuff if the window is no
3260        longer being managed !
3261     */
3262     if (self->managed &&
3263         (screen_find_monitor(&self->frame->area) !=
3264          screen_find_monitor(&oldframe) ||
3265          (final && (client_is_oldfullscreen(self, &oldclient) !=
3266                     client_is_oldfullscreen(self, &self->area)))))
3267     {
3268         client_calc_layer(self);
3269     }
3270 }
3271
3272 void client_fullscreen(ObClient *self, gboolean fs)
3273 {
3274     gint x, y, w, h;
3275
3276     if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
3277         self->fullscreen == fs) return;                   /* already done */
3278
3279     self->fullscreen = fs;
3280     client_change_state(self); /* change the state hints on the client */
3281
3282     if (fs) {
3283         self->pre_fullscreen_area = self->area;
3284         self->pre_fullscreen_max_horz = self->max_horz;
3285         self->pre_fullscreen_max_vert = self->max_vert;
3286
3287         /* if the window is maximized, its area isn't all that meaningful.
3288            save its premax area instead. */
3289         if (self->max_horz) {
3290             self->pre_fullscreen_area.x = self->pre_max_area.x;
3291             self->pre_fullscreen_area.width = self->pre_max_area.width;
3292         }
3293         if (self->max_vert) {
3294             self->pre_fullscreen_area.y = self->pre_max_area.y;
3295             self->pre_fullscreen_area.height = self->pre_max_area.height;
3296         }
3297
3298         /* these will help configure_full figure out where to fullscreen
3299            the window */
3300         x = self->area.x;
3301         y = self->area.y;
3302         w = self->area.width;
3303         h = self->area.height;
3304     } else {
3305         g_assert(self->pre_fullscreen_area.width > 0 &&
3306                  self->pre_fullscreen_area.height > 0);
3307
3308         self->max_horz = self->pre_fullscreen_max_horz;
3309         self->max_vert = self->pre_fullscreen_max_vert;
3310         if (self->max_horz) {
3311             self->pre_max_area.x = self->pre_fullscreen_area.x;
3312             self->pre_max_area.width = self->pre_fullscreen_area.width;
3313         }
3314         if (self->max_vert) {
3315             self->pre_max_area.y = self->pre_fullscreen_area.y;
3316             self->pre_max_area.height = self->pre_fullscreen_area.height;
3317         }
3318
3319         x = self->pre_fullscreen_area.x;
3320         y = self->pre_fullscreen_area.y;
3321         w = self->pre_fullscreen_area.width;
3322         h = self->pre_fullscreen_area.height;
3323         RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
3324     }
3325
3326     ob_debug("Window %s going fullscreen (%d)",
3327              self->title, self->fullscreen);
3328
3329     if (fs) {
3330         /* make sure the window is on some monitor */
3331         client_find_onscreen(self, &x, &y, w, h, FALSE);
3332     }
3333
3334     client_setup_decor_and_functions(self, FALSE);
3335     client_move_resize(self, x, y, w, h);
3336
3337     /* and adjust our layer/stacking. do this after resizing the window,
3338        and applying decorations, because windows which fill the screen are
3339        considered "fullscreen" and it affects their layer */
3340     client_calc_layer(self);
3341
3342     if (fs) {
3343         /* try focus us when we go into fullscreen mode */
3344         client_focus(self);
3345     }
3346 }
3347
3348 static void client_iconify_recursive(ObClient *self,
3349                                      gboolean iconic, gboolean curdesk,
3350                                      gboolean hide_animation)
3351 {
3352     GSList *it;
3353     gboolean changed = FALSE;
3354
3355     if (self->iconic != iconic) {
3356         ob_debug("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
3357                  self->window);
3358
3359         if (iconic) {
3360             /* don't let non-normal windows iconify along with their parents
3361                or whatever */
3362             if (client_normal(self)) {
3363                 self->iconic = iconic;
3364
3365                 /* update the focus lists.. iconic windows go to the bottom of
3366                    the list. this will also call focus_cycle_addremove(). */
3367                 focus_order_to_bottom(self);
3368
3369                 changed = TRUE;
3370             }
3371         } else {
3372             self->iconic = iconic;
3373
3374             if (curdesk && self->desktop != screen_desktop &&
3375                 self->desktop != DESKTOP_ALL)
3376                 client_set_desktop(self, screen_desktop, FALSE, FALSE);
3377
3378             /* this puts it after the current focused window, this will
3379                also cause focus_cycle_addremove() to be called for the
3380                client */
3381             focus_order_like_new(self);
3382
3383             changed = TRUE;
3384         }
3385     }
3386
3387     if (changed) {
3388         client_change_state(self);
3389         if (config_animate_iconify && !hide_animation)
3390             frame_begin_iconify_animation(self->frame, iconic);
3391         /* do this after starting the animation so it doesn't flash */
3392         client_showhide(self);
3393     }
3394
3395     /* iconify all direct transients, and deiconify all transients
3396        (non-direct too) */
3397     for (it = self->transients; it; it = g_slist_next(it))
3398         if (it->data != self)
3399             if (client_is_direct_child(self, it->data) || !iconic)
3400                 client_iconify_recursive(it->data, iconic, curdesk,
3401                                          hide_animation);
3402 }
3403
3404 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3405                     gboolean hide_animation)
3406 {
3407     if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3408         /* move up the transient chain as far as possible first */
3409         self = client_search_top_direct_parent(self);
3410         client_iconify_recursive(self, iconic, curdesk, hide_animation);
3411     }
3412 }
3413
3414 void client_maximize(ObClient *self, gboolean max, gint dir)
3415 {
3416     gint x, y, w, h;
3417
3418     g_assert(dir == 0 || dir == 1 || dir == 2);
3419     if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && max) return;/* can't */
3420
3421     /* check if already done */
3422     if (max) {
3423         if (dir == 0 && self->max_horz && self->max_vert) return;
3424         if (dir == 1 && self->max_horz) return;
3425         if (dir == 2 && self->max_vert) return;
3426     } else {
3427         if (dir == 0 && !self->max_horz && !self->max_vert) return;
3428         if (dir == 1 && !self->max_horz) return;
3429         if (dir == 2 && !self->max_vert) return;
3430     }
3431
3432     /* these will help configure_full figure out which screen to fill with
3433        the window */
3434     x = self->area.x;
3435     y = self->area.y;
3436     w = self->area.width;
3437     h = self->area.height;
3438
3439     if (max) {
3440         if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3441             RECT_SET(self->pre_max_area,
3442                      self->area.x, self->pre_max_area.y,
3443                      self->area.width, self->pre_max_area.height);
3444         }
3445         if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3446             RECT_SET(self->pre_max_area,
3447                      self->pre_max_area.x, self->area.y,
3448                      self->pre_max_area.width, self->area.height);
3449         }
3450     } else {
3451         if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3452             g_assert(self->pre_max_area.width > 0);
3453
3454             x = self->pre_max_area.x;
3455             w = self->pre_max_area.width;
3456
3457             RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3458                      0, self->pre_max_area.height);
3459         }
3460         if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3461             g_assert(self->pre_max_area.height > 0);
3462
3463             y = self->pre_max_area.y;
3464             h = self->pre_max_area.height;
3465
3466             RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3467                      self->pre_max_area.width, 0);
3468         }
3469     }
3470
3471     if (dir == 0 || dir == 1) /* horz */
3472         self->max_horz = max;
3473     if (dir == 0 || dir == 2) /* vert */
3474         self->max_vert = max;
3475
3476     if (max) {
3477         /* make sure the window is on some monitor */
3478         client_find_onscreen(self, &x, &y, w, h, FALSE);
3479     }
3480
3481     client_change_state(self); /* change the state hints on the client */
3482
3483     client_setup_decor_and_functions(self, FALSE);
3484     client_move_resize(self, x, y, w, h);
3485 }
3486
3487 void client_shade(ObClient *self, gboolean shade)
3488 {
3489     if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3490          shade) ||                         /* can't shade */
3491         self->shaded == shade) return;     /* already done */
3492
3493     self->shaded = shade;
3494     client_change_state(self);
3495     client_change_wm_state(self); /* the window is being hidden/shown */
3496     /* resize the frame to just the titlebar */
3497     frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
3498 }
3499
3500 static void client_ping_event(ObClient *self, gboolean dead)
3501 {
3502     if (self->not_responding != dead) {
3503         self->not_responding = dead;
3504         client_update_title(self);
3505
3506         if (dead)
3507             /* the client isn't responding, so ask to kill it */
3508             client_prompt_kill(self);
3509         else {
3510             /* it came back to life ! */
3511
3512             if (self->kill_prompt) {
3513                 prompt_unref(self->kill_prompt);
3514                 self->kill_prompt = NULL;
3515             }
3516
3517             self->kill_level = 0;
3518         }
3519     }
3520 }
3521
3522 void client_close(ObClient *self)
3523 {
3524     if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3525
3526     /* if closing an internal obprompt, that is just cancelling it */
3527     if (self->prompt) {
3528         prompt_cancel(self->prompt);
3529         return;
3530     }
3531
3532     /* in the case that the client provides no means to requesting that it
3533        close, we just kill it */
3534     if (!self->delete_window)
3535         /* don't use client_kill(), we should only kill based on PID in
3536            response to a lack of PING replies */
3537         XKillClient(obt_display, self->window);
3538     else {
3539         /* request the client to close with WM_DELETE_WINDOW */
3540         OBT_PROP_MSG_TO(self->window, self->window, WM_PROTOCOLS,
3541                         OBT_PROP_ATOM(WM_DELETE_WINDOW), event_time(),
3542                         0, 0, 0, NoEventMask);
3543
3544         /* we're trying to close the window, so see if it is responding. if it
3545            is not, then we will let them kill the window */
3546         if (self->ping)
3547             ping_start(self, client_ping_event);
3548
3549         /* if we already know the window isn't responding (maybe they clicked
3550            no in the kill dialog but it hasn't come back to life), then show
3551            the kill dialog */
3552         if (self->not_responding)
3553             client_prompt_kill(self);
3554     }
3555 }
3556
3557 #define OB_KILL_RESULT_NO 0
3558 #define OB_KILL_RESULT_YES 1
3559
3560 static gboolean client_kill_requested(ObPrompt *p, gint result, gpointer data)
3561 {
3562     ObClient *self = data;
3563
3564     if (result == OB_KILL_RESULT_YES)
3565         client_kill(self);
3566     return TRUE; /* call the cleanup func */
3567 }
3568
3569 static void client_kill_cleanup(ObPrompt *p, gpointer data)
3570 {
3571     ObClient *self = data;
3572
3573     g_assert(p == self->kill_prompt);
3574
3575     prompt_unref(self->kill_prompt);
3576     self->kill_prompt = NULL;
3577 }
3578
3579 static void client_prompt_kill(ObClient *self)
3580 {
3581     /* check if we're already prompting */
3582     if (!self->kill_prompt) {
3583         ObPromptAnswer answers[] = {
3584             { 0, OB_KILL_RESULT_NO },
3585             { 0, OB_KILL_RESULT_YES }
3586         };
3587         gchar *m;
3588         const gchar *y, *title;
3589
3590         title = self->original_title;
3591         if (title[0] == '\0') {
3592             /* empty string, so use its parent */
3593             ObClient *p = client_search_top_direct_parent(self);
3594             if (p) title = p->original_title;
3595         }
3596
3597         if (client_on_localhost(self)) {
3598             const gchar *sig;
3599
3600             if (self->kill_level == 0)
3601                 sig = "terminate";
3602             else
3603                 sig = "kill";
3604
3605             m = g_strdup_printf
3606                 (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"),
3607                  title, sig);
3608             y = _("End Process");
3609         }
3610         else {
3611             m = g_strdup_printf
3612                 (_("The window \"%s\" does not seem to be responding.  Do you want to disconnect it from the X server?"),
3613                  title);
3614             y = _("Disconnect");
3615         }
3616         /* set the dialog buttons' text */
3617         answers[0].text = _("Cancel");  /* "no" */
3618         answers[1].text = y;            /* "yes" */
3619
3620         self->kill_prompt = prompt_new(m, NULL, answers,
3621                                        sizeof(answers)/sizeof(answers[0]),
3622                                        OB_KILL_RESULT_NO, /* default = no */
3623                                        OB_KILL_RESULT_NO, /* cancel = no */
3624                                        client_kill_requested,
3625                                        client_kill_cleanup,
3626                                        self);
3627         g_free(m);
3628     }
3629
3630     prompt_show(self->kill_prompt, self, TRUE);
3631 }
3632
3633 void client_kill(ObClient *self)
3634 {
3635     /* don't kill our own windows */
3636     if (self->prompt) return;
3637
3638     if (client_on_localhost(self) && self->pid) {
3639         /* running on the local host */
3640         if (self->kill_level == 0) {
3641             ob_debug("killing window 0x%x with pid %lu, with SIGTERM",
3642                      self->window, self->pid);
3643             kill(self->pid, SIGTERM);
3644             ++self->kill_level;
3645
3646             /* show that we're trying to kill it */
3647             client_update_title(self);
3648         }
3649         else {
3650             ob_debug("killing window 0x%x with pid %lu, with SIGKILL",
3651                      self->window, self->pid);
3652             kill(self->pid, SIGKILL); /* kill -9 */
3653         }
3654     }
3655     else {
3656         /* running on a remote host */
3657         XKillClient(obt_display, self->window);
3658     }
3659 }
3660
3661 void client_hilite(ObClient *self, gboolean hilite)
3662 {
3663     if (self->demands_attention == hilite)
3664         return; /* no change */
3665
3666     /* don't allow focused windows to hilite */
3667     self->demands_attention = hilite && !client_focused(self);
3668     if (self->frame != NULL) { /* if we're mapping, just set the state */
3669         if (self->demands_attention) {
3670             frame_flash_start(self->frame);
3671
3672             /* if the window is on another desktop then raise it and make it
3673                the most recently used window */
3674             if (self->desktop != screen_desktop &&
3675                 self->desktop != DESKTOP_ALL)
3676             {
3677                 stacking_raise(CLIENT_AS_WINDOW(self));
3678                 focus_order_to_top(self);
3679             }
3680         }
3681         else
3682             frame_flash_stop(self->frame);
3683         client_change_state(self);
3684     }
3685 }
3686
3687 static void client_set_desktop_recursive(ObClient *self,
3688                                          guint target,
3689                                          gboolean donthide,
3690                                          gboolean dontraise)
3691 {
3692     guint old;
3693     GSList *it;
3694
3695     if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3696
3697         ob_debug("Setting desktop %u", target+1);
3698
3699         g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3700
3701         old = self->desktop;
3702         self->desktop = target;
3703         OBT_PROP_SET32(self->window, NET_WM_DESKTOP, CARDINAL, target);
3704         /* the frame can display the current desktop state */
3705         frame_adjust_state(self->frame);
3706         /* 'move' the window to the new desktop */
3707         if (!donthide)
3708             client_hide(self);
3709         client_show(self);
3710         /* raise if it was not already on the desktop */
3711         if (old != DESKTOP_ALL && !dontraise)
3712             stacking_raise(CLIENT_AS_WINDOW(self));
3713         if (STRUT_EXISTS(self->strut))
3714             screen_update_areas();
3715         else
3716             /* the new desktop's geometry may be different, so we may need to
3717                resize, for example if we are maximized */
3718             client_reconfigure(self, FALSE);
3719
3720         focus_cycle_addremove(self, FALSE);
3721     }
3722
3723     /* move all transients */
3724     for (it = self->transients; it; it = g_slist_next(it))
3725         if (it->data != self)
3726             if (client_is_direct_child(self, it->data))
3727                 client_set_desktop_recursive(it->data, target,
3728                                              donthide, dontraise);
3729 }
3730
3731 void client_set_desktop(ObClient *self, guint target,
3732                         gboolean donthide, gboolean dontraise)
3733 {
3734     self = client_search_top_direct_parent(self);
3735     client_set_desktop_recursive(self, target, donthide, dontraise);
3736
3737     focus_cycle_addremove(NULL, TRUE);
3738 }
3739
3740 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3741 {
3742     while (child != parent && (child = client_direct_parent(child)));
3743     return child == parent;
3744 }
3745
3746 ObClient *client_search_modal_child(ObClient *self)
3747 {
3748     GSList *it;
3749     ObClient *ret;
3750
3751     for (it = self->transients; it; it = g_slist_next(it)) {
3752         ObClient *c = it->data;
3753         if ((ret = client_search_modal_child(c))) return ret;
3754         if (c->modal) return c;
3755     }
3756     return NULL;
3757 }
3758
3759 struct ObClientFindDestroyUnmap {
3760     Window window;
3761     gint ignore_unmaps;
3762 };
3763
3764 static gboolean find_destroy_unmap(XEvent *e, gpointer data)
3765 {
3766     struct ObClientFindDestroyUnmap *find = data;
3767     if (e->type == DestroyNotify)
3768         return e->xdestroywindow.window == find->window;
3769     if (e->type == UnmapNotify && e->xunmap.window == find->window)
3770         /* ignore the first $find->ignore_unmaps$ many unmap events */
3771         return --find->ignore_unmaps < 0;
3772     return FALSE;
3773 }
3774
3775 gboolean client_validate(ObClient *self)
3776 {
3777     struct ObClientFindDestroyUnmap find;
3778
3779     XSync(obt_display, FALSE); /* get all events on the server */
3780
3781     find.window = self->window;
3782     find.ignore_unmaps = self->ignore_unmaps;
3783     if (xqueue_exists_local(find_destroy_unmap, &find))
3784         return FALSE;
3785
3786     return TRUE;
3787 }
3788
3789 void client_set_wm_state(ObClient *self, glong state)
3790 {
3791     if (state == self->wmstate) return; /* no change */
3792
3793     switch (state) {
3794     case IconicState:
3795         client_iconify(self, TRUE, TRUE, FALSE);
3796         break;
3797     case NormalState:
3798         client_iconify(self, FALSE, TRUE, FALSE);
3799         break;
3800     }
3801 }
3802
3803 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3804 {
3805     gboolean shaded = self->shaded;
3806     gboolean fullscreen = self->fullscreen;
3807     gboolean undecorated = self->undecorated;
3808     gboolean max_horz = self->max_horz;
3809     gboolean max_vert = self->max_vert;
3810     gboolean modal = self->modal;
3811     gboolean iconic = self->iconic;
3812     gboolean demands_attention = self->demands_attention;
3813     gboolean above = self->above;
3814     gboolean below = self->below;
3815     gint i;
3816     gboolean value;
3817
3818     if (!(action == OBT_PROP_ATOM(NET_WM_STATE_ADD) ||
3819           action == OBT_PROP_ATOM(NET_WM_STATE_REMOVE) ||
3820           action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)))
3821         /* an invalid action was passed to the client message, ignore it */
3822         return;
3823
3824     for (i = 0; i < 2; ++i) {
3825         Atom state = i == 0 ? data1 : data2;
3826
3827         if (!state) continue;
3828
3829         /* if toggling, then pick whether we're adding or removing */
3830         if (action == OBT_PROP_ATOM(NET_WM_STATE_TOGGLE)) {
3831             if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL))
3832                 value = modal;
3833             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT))
3834                 value = self->max_vert;
3835             else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ))
3836                 value = self->max_horz;
3837             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED))
3838                 value = shaded;
3839             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR))
3840                 value = self->skip_taskbar;
3841             else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER))
3842                 value = self->skip_pager;
3843             else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN))
3844                 value = self->iconic;
3845             else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN))
3846                 value = fullscreen;
3847             else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE))
3848                 value = self->above;
3849             else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW))
3850                 value = self->below;
3851             else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION))
3852                 value = self->demands_attention;
3853             else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
3854                 value = undecorated;
3855             else
3856                 g_assert_not_reached();
3857             action = value ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
3858                              OBT_PROP_ATOM(NET_WM_STATE_ADD);
3859         }
3860
3861         value = action == OBT_PROP_ATOM(NET_WM_STATE_ADD);
3862         if (state == OBT_PROP_ATOM(NET_WM_STATE_MODAL)) {
3863             modal = value;
3864         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_VERT)) {
3865             max_vert = value;
3866         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_MAXIMIZED_HORZ)) {
3867             max_horz = value;
3868         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SHADED)) {
3869             shaded = value;
3870         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_TASKBAR)) {
3871             self->skip_taskbar = value;
3872         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_SKIP_PAGER)) {
3873             self->skip_pager = value;
3874         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_HIDDEN)) {
3875             iconic = value;
3876         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_FULLSCREEN)) {
3877             fullscreen = value;
3878         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_ABOVE)) {
3879             above = value;
3880             /* only unset below when setting above, otherwise you can't get to
3881                the normal layer */
3882             if (value)
3883                 below = FALSE;
3884         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_BELOW)) {
3885             /* and vice versa */
3886             if (value)
3887                 above = FALSE;
3888             below = value;
3889         } else if (state == OBT_PROP_ATOM(NET_WM_STATE_DEMANDS_ATTENTION)){
3890             demands_attention = value;
3891         } else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED)) {
3892             undecorated = value;
3893         }
3894     }
3895
3896     if (max_horz != self->max_horz || max_vert != self->max_vert) {
3897         if (max_horz != self->max_horz && max_vert != self->max_vert) {
3898             /* toggling both */
3899             if (max_horz == max_vert) { /* both going the same way */
3900                 client_maximize(self, max_horz, 0);
3901             } else {
3902                 client_maximize(self, max_horz, 1);
3903                 client_maximize(self, max_vert, 2);
3904             }
3905         } else {
3906             /* toggling one */
3907             if (max_horz != self->max_horz)
3908                 client_maximize(self, max_horz, 1);
3909             else
3910                 client_maximize(self, max_vert, 2);
3911         }
3912     }
3913     /* change fullscreen state before shading, as it will affect if the window
3914        can shade or not */
3915     if (fullscreen != self->fullscreen)
3916         client_fullscreen(self, fullscreen);
3917     if (shaded != self->shaded)
3918         client_shade(self, shaded);
3919     if (undecorated != self->undecorated)
3920         client_set_undecorated(self, undecorated);
3921     if (above != self->above || below != self->below) {
3922         self->above = above;
3923         self->below = below;
3924         client_calc_layer(self);
3925     }
3926
3927     if (modal != self->modal) {
3928         self->modal = modal;
3929         /* when a window changes modality, then its stacking order with its
3930            transients needs to change */
3931         stacking_raise(CLIENT_AS_WINDOW(self));
3932
3933         /* it also may get focused. if something is focused that shouldn't
3934            be focused anymore, then move the focus */
3935         if (focus_client && client_focus_target(focus_client) != focus_client)
3936             client_focus(focus_client);
3937     }
3938
3939     if (iconic != self->iconic)
3940         client_iconify(self, iconic, FALSE, FALSE);
3941
3942     if (demands_attention != self->demands_attention)
3943         client_hilite(self, demands_attention);
3944
3945     client_change_state(self); /* change the hint to reflect these changes */
3946
3947     focus_cycle_addremove(self, TRUE);
3948 }
3949
3950 ObClient *client_focus_target(ObClient *self)
3951 {
3952     ObClient *child = NULL;
3953
3954     child = client_search_modal_child(self);
3955     if (child) return child;
3956     return self;
3957 }
3958
3959 gboolean client_can_focus(ObClient *self)
3960 {
3961     /* choose the correct target */
3962     self = client_focus_target(self);
3963
3964     if (!self->frame->visible)
3965         return FALSE;
3966
3967     if (!(self->can_focus || self->focus_notify))
3968         return FALSE;
3969
3970     return TRUE;
3971 }
3972
3973 gboolean client_focus(ObClient *self)
3974 {
3975     if (!client_validate(self)) return FALSE;
3976
3977     /* we might not focus this window, so if we have modal children which would
3978        be focused instead, bring them to this desktop */
3979     client_bring_modal_windows(self);
3980
3981     /* choose the correct target */
3982     self = client_focus_target(self);
3983
3984     if (!client_can_focus(self)) {
3985         ob_debug_type(OB_DEBUG_FOCUS,
3986                       "Client %s can't be focused", self->title);
3987         return FALSE;
3988     }
3989
3990     /* if we have helper windows they should be there with the window */
3991     client_bring_helper_windows(self);
3992
3993     ob_debug_type(OB_DEBUG_FOCUS,
3994                   "Focusing client \"%s\" (0x%x) at time %u",
3995                   self->title, self->window, event_time());
3996
3997     /* if using focus_delay, stop the timer now so that focus doesn't
3998        go moving on us */
3999     event_halt_focus_delay();
4000
4001     obt_display_ignore_errors(TRUE);
4002
4003     if (self->can_focus) {
4004         /* This can cause a BadMatch error with CurrentTime, or if an app
4005            passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
4006         XSetInputFocus(obt_display, self->window, RevertToPointerRoot,
4007                        event_time());
4008     }
4009
4010     if (self->focus_notify) {
4011         XEvent ce;
4012         ce.xclient.type = ClientMessage;
4013         ce.xclient.message_type = OBT_PROP_ATOM(WM_PROTOCOLS);
4014         ce.xclient.display = obt_display;
4015         ce.xclient.window = self->window;
4016         ce.xclient.format = 32;
4017         ce.xclient.data.l[0] = OBT_PROP_ATOM(WM_TAKE_FOCUS);
4018         ce.xclient.data.l[1] = event_time();
4019         ce.xclient.data.l[2] = 0l;
4020         ce.xclient.data.l[3] = 0l;
4021         ce.xclient.data.l[4] = 0l;
4022         XSendEvent(obt_display, self->window, FALSE, NoEventMask, &ce);
4023     }
4024
4025     obt_display_ignore_errors(FALSE);
4026
4027     ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d",
4028                   obt_display_error_occured);
4029     return !obt_display_error_occured;
4030 }
4031
4032 static void client_present(ObClient *self, gboolean here, gboolean raise,
4033                            gboolean unshade)
4034 {
4035     if (client_normal(self) && screen_showing_desktop)
4036         screen_show_desktop(FALSE, self);
4037     if (self->iconic)
4038         client_iconify(self, FALSE, here, FALSE);
4039     if (self->desktop != DESKTOP_ALL &&
4040         self->desktop != screen_desktop)
4041     {
4042         if (here)
4043             client_set_desktop(self, screen_desktop, FALSE, TRUE);
4044         else
4045             screen_set_desktop(self->desktop, FALSE);
4046     } else if (!self->frame->visible)
4047         /* if its not visible for other reasons, then don't mess
4048            with it */
4049         return;
4050     if (self->shaded && unshade)
4051         client_shade(self, FALSE);
4052     if (raise)
4053         stacking_raise(CLIENT_AS_WINDOW(self));
4054
4055     client_focus(self);
4056 }
4057
4058 /* this function exists to map to the net_active_window message in the ewmh */
4059 void client_activate(ObClient *self, gboolean desktop,
4060                      gboolean here, gboolean raise,
4061                      gboolean unshade, gboolean user)
4062 {
4063     self = client_focus_target(self);
4064
4065     if (client_can_steal_focus(self, desktop, user, event_time(), CurrentTime))
4066         client_present(self, here, raise, unshade);
4067     else
4068         client_hilite(self, TRUE);
4069 }
4070
4071 static void client_bring_windows_recursive(ObClient *self,
4072                                            guint desktop,
4073                                            gboolean helpers,
4074                                            gboolean modals,
4075                                            gboolean iconic)
4076 {
4077     GSList *it;
4078
4079     for (it = self->transients; it; it = g_slist_next(it))
4080         client_bring_windows_recursive(it->data, desktop,
4081                                        helpers, modals, iconic);
4082
4083     if (((helpers && client_helper(self)) ||
4084          (modals && self->modal)) &&
4085         (!screen_compare_desktops(self->desktop, desktop) ||
4086          (iconic && self->iconic)))
4087     {
4088         if (iconic && self->iconic)
4089             client_iconify(self, FALSE, TRUE, FALSE);
4090         else
4091             client_set_desktop(self, desktop, FALSE, FALSE);
4092     }
4093 }
4094
4095 void client_bring_helper_windows(ObClient *self)
4096 {
4097     client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE);
4098 }
4099
4100 void client_bring_modal_windows(ObClient *self)
4101 {
4102     client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE);
4103 }
4104
4105 gboolean client_focused(ObClient *self)
4106 {
4107     return self == focus_client;
4108 }
4109
4110 RrImage* client_icon(ObClient *self)
4111 {
4112     RrImage *ret = NULL;
4113
4114     if (self->icon_set)
4115         ret = self->icon_set;
4116     else if (self->parents) {
4117         GSList *it;
4118         for (it = self->parents; it && !ret; it = g_slist_next(it))
4119             ret = client_icon(it->data);
4120     }
4121     if (!ret)
4122         ret = client_default_icon;
4123     return ret;
4124 }
4125
4126 void client_set_layer(ObClient *self, gint layer)
4127 {
4128     if (layer < 0) {
4129         self->below = TRUE;
4130         self->above = FALSE;
4131     } else if (layer == 0) {
4132         self->below = self->above = FALSE;
4133     } else {
4134         self->below = FALSE;
4135         self->above = TRUE;
4136     }
4137     client_calc_layer(self);
4138     client_change_state(self); /* reflect this in the state hints */
4139 }
4140
4141 void client_set_undecorated(ObClient *self, gboolean undecorated)
4142 {
4143     if (self->undecorated != undecorated &&
4144         /* don't let it undecorate if the function is missing, but let
4145            it redecorate */
4146         (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
4147     {
4148         self->undecorated = undecorated;
4149         client_setup_decor_and_functions(self, TRUE);
4150         client_change_state(self); /* reflect this in the state hints */
4151     }
4152 }
4153
4154 guint client_monitor(ObClient *self)
4155 {
4156     return screen_find_monitor(&self->frame->area);
4157 }
4158
4159 ObClient *client_direct_parent(ObClient *self)
4160 {
4161     if (!self->parents) return NULL;
4162     if (self->transient_for_group) return NULL;
4163     return self->parents->data;
4164 }
4165
4166 ObClient *client_search_top_direct_parent(ObClient *self)
4167 {
4168     ObClient *p;
4169     while ((p = client_direct_parent(self))) self = p;
4170     return self;
4171 }
4172
4173 static GSList *client_search_all_top_parents_internal(ObClient *self,
4174                                                       gboolean bylayer,
4175                                                       ObStackingLayer layer)
4176 {
4177     GSList *ret;
4178     ObClient *p;
4179
4180     /* move up the direct transient chain as far as possible */
4181     while ((p = client_direct_parent(self)) &&
4182            (!bylayer || p->layer == layer))
4183         self = p;
4184
4185     if (!self->parents)
4186         ret = g_slist_prepend(NULL, self);
4187     else
4188         ret = g_slist_copy(self->parents);
4189
4190     return ret;
4191 }
4192
4193 GSList *client_search_all_top_parents(ObClient *self)
4194 {
4195     return client_search_all_top_parents_internal(self, FALSE, 0);
4196 }
4197
4198 GSList *client_search_all_top_parents_layer(ObClient *self)
4199 {
4200     return client_search_all_top_parents_internal(self, TRUE, self->layer);
4201 }
4202
4203 ObClient *client_search_focus_parent(ObClient *self)
4204 {
4205     GSList *it;
4206
4207     for (it = self->parents; it; it = g_slist_next(it))
4208         if (client_focused(it->data)) return it->data;
4209
4210     return NULL;
4211 }
4212
4213 ObClient *client_search_focus_parent_full(ObClient *self)
4214 {
4215     GSList *it;
4216     ObClient *ret = NULL;
4217
4218     for (it = self->parents; it; it = g_slist_next(it)) {
4219         if (client_focused(it->data))
4220             ret = it->data;
4221         else
4222             ret = client_search_focus_parent_full(it->data);
4223         if (ret) break;
4224     }
4225     return ret;
4226 }
4227
4228 ObClient *client_search_parent(ObClient *self, ObClient *search)
4229 {
4230     GSList *it;
4231
4232     for (it = self->parents; it; it = g_slist_next(it))
4233         if (it->data == search) return search;
4234
4235     return NULL;
4236 }
4237
4238 ObClient *client_search_transient(ObClient *self, ObClient *search)
4239 {
4240     GSList *sit;
4241
4242     for (sit = self->transients; sit; sit = g_slist_next(sit)) {
4243         if (sit->data == search)
4244             return search;
4245         if (client_search_transient(sit->data, search))
4246             return search;
4247     }
4248     return NULL;
4249 }
4250
4251 static void detect_edge(Rect area, ObDirection dir,
4252                         gint my_head, gint my_size,
4253                         gint my_edge_start, gint my_edge_size,
4254                         gint *dest, gboolean *near_edge)
4255 {
4256     gint edge_start, edge_size, head, tail;
4257     gboolean skip_head = FALSE, skip_tail = FALSE;
4258
4259     switch (dir) {
4260         case OB_DIRECTION_NORTH:
4261         case OB_DIRECTION_SOUTH:
4262             edge_start = area.x;
4263             edge_size = area.width;
4264             break;
4265         case OB_DIRECTION_EAST:
4266         case OB_DIRECTION_WEST:
4267             edge_start = area.y;
4268             edge_size = area.height;
4269             break;
4270         default:
4271             g_assert_not_reached();
4272     }
4273
4274     /* do we collide with this window? */
4275     if (!RANGES_INTERSECT(my_edge_start, my_edge_size,
4276                 edge_start, edge_size))
4277         return;
4278
4279     switch (dir) {
4280         case OB_DIRECTION_NORTH:
4281             head = RECT_BOTTOM(area);
4282             tail = RECT_TOP(area);
4283             break;
4284         case OB_DIRECTION_SOUTH:
4285             head = RECT_TOP(area);
4286             tail = RECT_BOTTOM(area);
4287             break;
4288         case OB_DIRECTION_WEST:
4289             head = RECT_RIGHT(area);
4290             tail = RECT_LEFT(area);
4291             break;
4292         case OB_DIRECTION_EAST:
4293             head = RECT_LEFT(area);
4294             tail = RECT_RIGHT(area);
4295             break;
4296         default:
4297             g_assert_not_reached();
4298     }
4299     switch (dir) {
4300         case OB_DIRECTION_NORTH:
4301         case OB_DIRECTION_WEST:
4302             /* check if our window is past the head of this window */
4303             if (my_head <= head + 1)
4304                 skip_head = TRUE;
4305             /* check if our window's tail is past the tail of this window */
4306             if (my_head + my_size - 1 <= tail)
4307                 skip_tail = TRUE;
4308             /* check if the head of this window is closer than the previously
4309                chosen edge (take into account that the previously chosen
4310                edge might have been a tail, not a head) */
4311             if (head + (*near_edge ? 0 : my_size) <= *dest)
4312                 skip_head = TRUE;
4313             /* check if the tail of this window is closer than the previously
4314                chosen edge (take into account that the previously chosen
4315                edge might have been a head, not a tail) */
4316             if (tail - (!*near_edge ? 0 : my_size) <= *dest)
4317                 skip_tail = TRUE;
4318             break;
4319         case OB_DIRECTION_SOUTH:
4320         case OB_DIRECTION_EAST:
4321             /* check if our window is past the head of this window */
4322             if (my_head >= head - 1)
4323                 skip_head = TRUE;
4324             /* check if our window's tail is past the tail of this window */
4325             if (my_head - my_size + 1 >= tail)
4326                 skip_tail = TRUE;
4327             /* check if the head of this window is closer than the previously
4328                chosen edge (take into account that the previously chosen
4329                edge might have been a tail, not a head) */
4330             if (head - (*near_edge ? 0 : my_size) >= *dest)
4331                 skip_head = TRUE;
4332             /* check if the tail of this window is closer than the previously
4333                chosen edge (take into account that the previously chosen
4334                edge might have been a head, not a tail) */
4335             if (tail + (!*near_edge ? 0 : my_size) >= *dest)
4336                 skip_tail = TRUE;
4337             break;
4338         default:
4339             g_assert_not_reached();
4340     }
4341
4342     ob_debug("my head %d size %d", my_head, my_size);
4343     ob_debug("head %d tail %d dest %d", head, tail, *dest);
4344     if (!skip_head) {
4345         ob_debug("using near edge %d", head);
4346         *dest = head;
4347         *near_edge = TRUE;
4348     }
4349     else if (!skip_tail) {
4350         ob_debug("using far edge %d", tail);
4351         *dest = tail;
4352         *near_edge = FALSE;
4353     }
4354 }
4355
4356 void client_find_edge_directional(ObClient *self, ObDirection dir,
4357                                   gint my_head, gint my_size,
4358                                   gint my_edge_start, gint my_edge_size,
4359                                   gint *dest, gboolean *near_edge)
4360 {
4361     GList *it;
4362     Rect *a;
4363     Rect dock_area;
4364     gint edge;
4365     guint i;
4366
4367     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
4368                     &self->frame->area);
4369
4370     switch (dir) {
4371     case OB_DIRECTION_NORTH:
4372         edge = RECT_TOP(*a) - 1;
4373         break;
4374     case OB_DIRECTION_SOUTH:
4375         edge = RECT_BOTTOM(*a) + 1;
4376         break;
4377     case OB_DIRECTION_EAST:
4378         edge = RECT_RIGHT(*a) + 1;
4379         break;
4380     case OB_DIRECTION_WEST:
4381         edge = RECT_LEFT(*a) - 1;
4382         break;
4383     default:
4384         g_assert_not_reached();
4385     }
4386     /* default to the far edge, then narrow it down */
4387     *dest = edge;
4388     *near_edge = TRUE;
4389
4390     /* search for edges of monitors */
4391     for (i = 0; i < screen_num_monitors; ++i) {
4392         Rect *area = screen_area(self->desktop, i, NULL);
4393         detect_edge(*area, dir, my_head, my_size, my_edge_start,
4394                     my_edge_size, dest, near_edge);
4395         g_slice_free(Rect, area);
4396     }
4397
4398     /* search for edges of clients */
4399     for (it = client_list; it; it = g_list_next(it)) {
4400         ObClient *cur = it->data;
4401
4402         /* skip windows to not bump into */
4403         if (cur == self)
4404             continue;
4405         if (cur->iconic)
4406             continue;
4407         if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL &&
4408             cur->desktop != screen_desktop)
4409             continue;
4410
4411         ob_debug("trying window %s", cur->title);
4412
4413         detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start,
4414                     my_edge_size, dest, near_edge);
4415     }
4416     dock_get_area(&dock_area);
4417     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
4418                 my_edge_size, dest, near_edge);
4419
4420     g_slice_free(Rect, a);
4421 }
4422
4423 void client_find_move_directional(ObClient *self, ObDirection dir,
4424                                   gint *x, gint *y)
4425 {
4426     gint head, size;
4427     gint e, e_start, e_size;
4428     gboolean near;
4429
4430     switch (dir) {
4431     case OB_DIRECTION_EAST:
4432         head = RECT_RIGHT(self->frame->area);
4433         size = self->frame->area.width;
4434         e_start = RECT_TOP(self->frame->area);
4435         e_size = self->frame->area.height;
4436         break;
4437     case OB_DIRECTION_WEST:
4438         head = RECT_LEFT(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_NORTH:
4444         head = RECT_TOP(self->frame->area);
4445         size = self->frame->area.height;
4446         e_start = RECT_LEFT(self->frame->area);
4447         e_size = self->frame->area.width;
4448         break;
4449     case OB_DIRECTION_SOUTH:
4450         head = RECT_BOTTOM(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     default:
4456         g_assert_not_reached();
4457     }
4458
4459     client_find_edge_directional(self, dir, head, size,
4460                                  e_start, e_size, &e, &near);
4461     *x = self->frame->area.x;
4462     *y = self->frame->area.y;
4463     switch (dir) {
4464     case OB_DIRECTION_EAST:
4465         if (near) e -= self->frame->area.width;
4466         else      e++;
4467         *x = e;
4468         break;
4469     case OB_DIRECTION_WEST:
4470         if (near) e++;
4471         else      e -= self->frame->area.width;
4472         *x = e;
4473         break;
4474     case OB_DIRECTION_NORTH:
4475         if (near) e++;
4476         else      e -= self->frame->area.height;
4477         *y = e;
4478         break;
4479     case OB_DIRECTION_SOUTH:
4480         if (near) e -= self->frame->area.height;
4481         else      e++;
4482         *y = e;
4483         break;
4484     default:
4485         g_assert_not_reached();
4486     }
4487     frame_frame_gravity(self->frame, x, y);
4488 }
4489
4490 void client_find_resize_directional(ObClient *self, ObDirection side,
4491                                     gboolean grow,
4492                                     gint *x, gint *y, gint *w, gint *h)
4493 {
4494     gint head;
4495     gint e, e_start, e_size, delta;
4496     gboolean near;
4497     ObDirection dir;
4498
4499     switch (side) {
4500     case OB_DIRECTION_EAST:
4501         head = RECT_RIGHT(self->frame->area) +
4502             (self->size_inc.width - 1) * (grow ? 1 : 0);
4503         e_start = RECT_TOP(self->frame->area);
4504         e_size = self->frame->area.height;
4505         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
4506         break;
4507     case OB_DIRECTION_WEST:
4508         head = RECT_LEFT(self->frame->area) -
4509             (self->size_inc.width - 1) * (grow ? 1 : 0);
4510         e_start = RECT_TOP(self->frame->area);
4511         e_size = self->frame->area.height;
4512         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
4513         break;
4514     case OB_DIRECTION_NORTH:
4515         head = RECT_TOP(self->frame->area) -
4516             (self->size_inc.height - 1) * (grow ? 1 : 0);
4517         e_start = RECT_LEFT(self->frame->area);
4518         e_size = self->frame->area.width;
4519         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
4520         break;
4521     case OB_DIRECTION_SOUTH:
4522         head = RECT_BOTTOM(self->frame->area) +
4523             (self->size_inc.height - 1) * (grow ? 1 : 0);
4524         e_start = RECT_LEFT(self->frame->area);
4525         e_size = self->frame->area.width;
4526         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;
4527         break;
4528     default:
4529         g_assert_not_reached();
4530     }
4531
4532     ob_debug("head %d dir %d", head, dir);
4533     client_find_edge_directional(self, dir, head, 1,
4534                                  e_start, e_size, &e, &near);
4535     ob_debug("edge %d", e);
4536     *x = self->frame->area.x;
4537     *y = self->frame->area.y;
4538     *w = self->frame->area.width;
4539     *h = self->frame->area.height;
4540     switch (side) {
4541     case OB_DIRECTION_EAST:
4542         if (grow == near) --e;
4543         delta = e - RECT_RIGHT(self->frame->area);
4544         *w += delta;
4545         break;
4546     case OB_DIRECTION_WEST:
4547         if (grow == near) ++e;
4548         delta = RECT_LEFT(self->frame->area) - e;
4549         *x -= delta;
4550         *w += delta;
4551         break;
4552     case OB_DIRECTION_NORTH:
4553         if (grow == near) ++e;
4554         delta = RECT_TOP(self->frame->area) - e;
4555         *y -= delta;
4556         *h += delta;
4557         break;
4558     case OB_DIRECTION_SOUTH:
4559         if (grow == near) --e;
4560         delta = e - RECT_BOTTOM(self->frame->area);
4561         *h += delta;
4562         break;
4563     default:
4564         g_assert_not_reached();
4565     }
4566     frame_frame_gravity(self->frame, x, y);
4567     *w -= self->frame->size.left + self->frame->size.right;
4568     *h -= self->frame->size.top + self->frame->size.bottom;
4569 }
4570
4571 ObClient* client_under_pointer(void)
4572 {
4573     gint x, y;
4574     GList *it;
4575     ObClient *ret = NULL;
4576
4577     if (screen_pointer_pos(&x, &y)) {
4578         for (it = stacking_list; it; it = g_list_next(it)) {
4579             if (WINDOW_IS_CLIENT(it->data)) {
4580                 ObClient *c = WINDOW_AS_CLIENT(it->data);
4581                 if (c->frame->visible &&
4582                     /* check the desktop, this is done during desktop
4583                        switching and windows are shown/hidden status is not
4584                        reliable */
4585                     (c->desktop == screen_desktop ||
4586                      c->desktop == DESKTOP_ALL) &&
4587                     /* ignore all animating windows */
4588                     !frame_iconify_animating(c->frame) &&
4589                     RECT_CONTAINS(c->frame->area, x, y))
4590                 {
4591                     ret = c;
4592                     break;
4593                 }
4594             }
4595         }
4596     }
4597     return ret;
4598 }
4599
4600 gboolean client_has_group_siblings(ObClient *self)
4601 {
4602     return self->group && self->group->members->next;
4603 }
4604
4605 gboolean client_has_relative(ObClient *self)
4606 {
4607     return client_has_parent(self) ||
4608         client_has_group_siblings(self) ||
4609         client_has_children(self);
4610 }
4611
4612 /*! Returns TRUE if the client is running on the same machine as Openbox */
4613 gboolean client_on_localhost(ObClient *self)
4614 {
4615     return self->client_machine == NULL;
4616 }