/* windows.c -- window manipulation
$Id: windows.c,v 1.127 2003/01/09 13:49:55 jsh Exp $
Copyright (C) 1999 John Harper <john@dcs.warwick.ac.uk>
This file is part of sawmill.
sawmill is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
sawmill is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with sawmill; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "sawmill.h"
#include <assert.h>
#include <X11/extensions/shape.h>
Lisp_Window *window_list;
int window_type;
Lisp_Window *focus_window;
int pending_destroys;
static bool initialising;
DEFSYM(add_window_hook, "add-window-hook");
DEFSYM(before_add_window_hook, "before-add-window-hook");
DEFSYM(after_add_window_hook, "after-add-window-hook");
DEFSYM(place_window_hook, "place-window-hook");
DEFSYM(placed, "placed");
DEFSYM(after_framing_hook, "after-framing-hook");
DEFSYM(after_initialization_hook, "after-initialization-hook");
DEFSYM(remove_window_hook, "remove-window-hook");
/* for visibility-notify-hook */
DEFSYM(fully_obscured, "fully-obscured");
DEFSYM(partially_obscured, "partially-obscured");
DEFSYM(unobscured, "unobscured");
/* for window-size-hints */
DEFSYM(min_width, "min-width");
DEFSYM(min_height, "min-height");
DEFSYM(max_width, "max-width");
DEFSYM(max_height, "max-height");
DEFSYM(width_inc, "width-inc");
DEFSYM(height_inc, "height-inc");
DEFSYM(base_width, "base-width");
DEFSYM(base_height, "base-height");
DEFSYM(min_aspect, "min-aspect");
DEFSYM(max_aspect, "max-aspect");
DEFSYM(user_size, "user-size");
DEFSYM(program_size, "program-size");
DEFSYM(user_position, "user-position");
DEFSYM(program_position, "program-position");
DEFSYM(window_gravity, "window-gravity");
/* for window-gravity */
DEFSYM(forget, "forget");
DEFSYM(static, "static");
DEFSYM(north_west, "north-west");
DEFSYM(north, "north");
DEFSYM(north_east, "north-east");
DEFSYM(west, "west");
DEFSYM(center, "center");
DEFSYM(east, "east");
DEFSYM(south_west, "south-west");
DEFSYM(south, "south");
DEFSYM(south_east, "south-east");
static repv gravity_map[StaticGravity+1];
struct prop_handler {
struct prop_handler *next;
repv prop;
void (*callback) (Lisp_Window *w, repv prop, repv old, repv new);
};
static struct prop_handler *prop_handlers;
/* utilities */
/* Returns true if we should manage window ID */
bool
mapped_not_override_p (Window id)
{
XWindowAttributes wa;
XGetWindowAttributes(dpy, id, &wa);
return ((wa.map_state != IsUnmapped) && (wa.override_redirect != True));
}
/* Returns true if the window's Input hint is set (or defaults to being set) */
static bool
window_input_hint_p (Lisp_Window *w)
{
if (w->wmhints != 0 && (w->wmhints->flags & InputHint))
return w->wmhints->input;
else
return TRUE;
}
static Window queued_focus_id;
static bool queued_take_focus;
static bool queued_set_focus;
static int queued_focus_revert;
static Time queued_focus_time;
/* We can lose the focus sometimes, notably after a was-focused
window is closed while a keyboard grab exists.. (netscape) */
static void
check_for_lost_focus (void)
{
Window focus;
int revert_to;
XGetInputFocus (dpy, &focus, &revert_to);
if (focus == None || focus == PointerRoot)
{
DB (("lost focus (%ld)\n", focus));
focus_on_window (focus_window);
}
}
void
commit_queued_focus_change (void)
{
if (0 && queued_focus_id == 0)
check_for_lost_focus ();
if (queued_focus_id != 0)
{
if (queued_take_focus)
{
DB((" sending WM_TAKE_FOCUS %x %ld\n",
(unsigned) queued_focus_id, queued_focus_time));
send_client_message (queued_focus_id,
xa_wm_take_focus,
queued_focus_time);
}
if (queued_set_focus)
{
DB((" focusing %x %ld\n",
(unsigned) queued_focus_id, queued_focus_time));
XSetInputFocus (dpy, queued_focus_id,
queued_focus_revert, queued_focus_time);
}
queued_focus_id = 0;
}
}
/* Give the input focus to window W, or to no window if W is null */
void
focus_on_window (Lisp_Window *w)
{
/* something's going to change, so */
queued_focus_id = 0;
queued_focus_time = last_event_time;
queued_set_focus = FALSE;
queued_take_focus = FALSE;
if (w != 0 && !WINDOW_IS_GONE_P (w) && w->visible)
{
DB(("focus_on_window (%s)\n", rep_STR(w->name)));
if (!w->client_unmapped)
{
queued_focus_id = w->id;
if (w->does_wm_take_focus)
{
queued_take_focus = TRUE;
if (window_input_hint_p (w))
queued_set_focus = TRUE;
}
else
queued_set_focus = TRUE;
}
else
{
queued_focus_id = w->frame;
queued_set_focus = TRUE;
}
queued_focus_revert = RevertToParent;
}
if (queued_focus_id == 0 || (!queued_set_focus && !queued_take_focus))
{
DB(("focus_on_window (nil)\n"));
queued_focus_id = no_focus_window;
queued_set_focus = TRUE;
queued_focus_revert = RevertToNone;
queued_focus_time = last_event_time;
}
}
/* Should be called when W is no longer focusable. */
void
focus_off_window (Lisp_Window *w)
{
if (w == focus_window)
{
focus_window = 0;
/* Do this immediately. Any real focus-change will be queued,
so will happen after this. Doing this here just prevents us
getting stuck with focus on nothing in some cases.. */
XSetInputFocus (dpy, no_focus_window, RevertToNone, last_event_time);
}
}
/* Set flags in W relating to which window manager protocols are recognised
by window W. */
void
get_window_protocols (Lisp_Window *w)
{
Atom *prot;
u_int n;
w->does_wm_take_focus = 0;
w->does_wm_delete_window = 0;
if (XGetWMProtocols (dpy, w->id, &prot, &n) != 0)
{
int i;
for (i = 0; i < n; i++)
{
if (prot[i] == xa_wm_take_focus)
{
w->does_wm_take_focus = 1;
DB((" WM_TAKE_FOCUS is set\n"));
}
if (prot[i] == xa_wm_delete_window)
{
w->does_wm_delete_window = 1;
DB((" WM_DELETE_WINDOW is set\n"));
}
}
XFree (prot);
}
}
/* These two functions are used to bracket Xlib requests that would map,
unmap, or reparent the client window. They ensure that any
StructureNotifymask events generated between calling before_local_map ()
and after_local_map () are discarded, but no others (so that we don't
lose client-generated events) */
void
before_local_map (Lisp_Window *w)
{
Fgrab_server ();
XSelectInput (dpy, w->id, CLIENT_EVENTS & ~StructureNotifyMask);
}
void
after_local_map (Lisp_Window *w)
{
XSelectInput (dpy, w->id, CLIENT_EVENTS);
Fungrab_server ();
}
/* manipulating the Lisp window structures */
/* Return the window object containing ID, or a null pointer */
Lisp_Window *
find_window_by_id (Window id)
{
Lisp_Window *w;
w = window_list;
while (w != 0 && w->id != id && w->frame != id)
w = w->next;
if (w != 0 && WINDOW_IS_GONE_P (w))
w = 0;
return w;
}
/* This is different to the above in that it could return a window
that doesn't have a client window. */
Lisp_Window *
x_find_window_by_id (Window id)
{
Lisp_Window *w;
w = window_list;
while (w != 0 && w->saved_id != id && w->frame != id)
w = w->next;
return w;
}
void
install_window_frame (Lisp_Window *w)
{
DB(("install_window_frame (%s)\n", rep_STR(w->name)));
if (!w->reparented && w->frame != 0 && !WINDOW_IS_GONE_P (w))
{
XSetWindowAttributes wa;
XSelectInput (dpy, w->frame, FRAME_EVENTS);
before_local_map (w);
XReparentWindow (dpy, w->id, w->frame, -w->frame_x, -w->frame_y);
w->reparented = TRUE;
after_local_map (w);
restack_window (w);
if (queued_focus_id == w->id)
queued_focus_id = w->frame;
XAddToSaveSet (dpy, w->id);
restack_frame_parts (w);
reset_frame_parts (w);
wa.win_gravity = StaticGravity;
XChangeWindowAttributes (dpy, w->id, CWWinGravity, &wa);
DB((" reparented to %lx [%dx%d%+d%+d]\n",
w->frame, w->frame_width, w->frame_height,
w->frame_x, w->frame_y));
}
}
void
remove_window_frame (Lisp_Window *w)
{
DB(("remove_window_frame (%s)\n", rep_STR(w->name)));
if (w->reparented && !WINDOW_IS_GONE_P (w))
{
XSetWindowAttributes wa;
/* reparent the subwindow back to the root window */
wa.win_gravity = w->attr.win_gravity;
XChangeWindowAttributes (dpy, w->id, CWWinGravity, &wa);
before_local_map (w);
XReparentWindow (dpy, w->id, root_window, w->attr.x, w->attr.y);
w->reparented = FALSE;
after_local_map (w);
restack_window (w);
if (queued_focus_id == w->frame)
queued_focus_id = w->id;
if (!w->mapped)
XRemoveFromSaveSet (dpy, w->id);
}
}
/* Add the top-level window ID to the manager's data structures */
Lisp_Window *
add_window (Window id)
{
char *tem;
Lisp_Window *w = rep_ALLOC_CELL(sizeof (Lisp_Window));
if (w != 0)
{
rep_GC_root gc_win;
repv win = rep_VAL(w);
XWindowChanges xwc;
u_int xwcm;
long supplied;
XTextProperty prop;
DB(("add_window (%lx)\n", id));
if (id == root_window)
DB((" ** adding root window!?\n"));
rep_data_after_gc += sizeof (Lisp_Window);
memset (w, 0, sizeof (Lisp_Window));
/* First initialise the Lisp stuff.. */
w->next = window_list;
window_list = w;
w->car = window_type;
w->id = id;
w->saved_id = id;
w->plist = Qnil;
w->frame_style = Qnil;;
w->icon_image = rep_NULL;
/* have to put it somewhere until it finds the right place */
insert_in_stacking_list_above_all (w);
restack_window (w);
/* ..now do the X11 stuff */
XSelectInput (dpy, id, CLIENT_EVENTS);
XGetWindowAttributes (dpy, id, &w->attr);
DB((" orig: width=%d height=%d x=%d y=%d\n",
w->attr.width, w->attr.height, w->attr.x, w->attr.y));
if (XGetWMName (dpy, id, &prop) && prop.value)
{
if (prop.nitems > 0)
{
char **list;
int count;
prop.nitems = strlen(prop.value);
if (XmbTextPropertyToTextList (dpy, &prop, &list, &count)
>= Success)
{
if (count > 0)
w->name = rep_string_dup (list[0]);
XFreeStringList (list);
}
}
XFree (prop.value);
}
if (w->name == 0)
w->name = rep_null_string ();
w->full_name = w->name;
if (XGetIconName (dpy, id, &tem))
{
w->icon_name = rep_string_dup (tem);
XFree (tem);
}
else
w->icon_name = w->name;
w->wmhints = XGetWMHints (dpy, id);
if (!XGetWMNormalHints (dpy, w->id, &w->hints, &supplied))
w->hints.flags = 0;
get_window_protocols (w);
if (!XGetWMColormapWindows (dpy, w->id,
&w->cmap_windows, &w->n_cmap_windows))
{
w->n_cmap_windows = 0;
}
{
/* Is the window shaped? */
int xws, yws, xbs, ybs;
u_int wws, hws, wbs, hbs;
int bounding, clip;
XShapeSelectInput (dpy, w->id, ShapeNotifyMask);
XShapeQueryExtents (dpy, w->id, &bounding, &xws, &yws, &wws, &hws,
&clip, &xbs, &ybs, &wbs, &hbs);
w->shaped = bounding ? 1 : 0;
}
DB((" name=`%s' x=%d y=%d width=%d height=%d\n",
rep_STR(w->name), w->attr.x, w->attr.y,
w->attr.width, w->attr.height));
xwcm = CWX | CWX | CWWidth | CWHeight | CWBorderWidth;
xwc.x = w->attr.x;
xwc.y = w->attr.y;
xwc.width = w->attr.width;
xwc.height = w->attr.height;
xwc.border_width = 0;
XConfigureWindow (dpy, id, xwcm, &xwc);
w->visible = TRUE;
w->mapped = TRUE; /* only called from map request */
if (initialising)
Fwindow_put (rep_VAL (w), Qplaced, Qt);
/* ..then call the add-window-hook's.. */
rep_PUSHGC(gc_win, win);
Fcall_window_hook (Qbefore_add_window_hook, rep_VAL(w), Qnil, Qnil);
Fcall_window_hook (Qadd_window_hook, rep_VAL(w), Qnil, Qnil);
rep_POPGC;
/* In case the window disappeared during the hook call */
if (!WINDOW_IS_GONE_P (w))
{
Fgrab_server ();
/* this is where we create and reparent the window frame */
create_window_frame (w);
install_window_frame (w);
/* this grabs bound events in the subwindow */
grab_window_events (w, TRUE);
Fungrab_server ();
}
else
emit_pending_destroys ();
if (!WINDOW_IS_GONE_P (w))
{
repv tem = Fwindow_get (rep_VAL(w), Qplaced);
if (initialising || (tem && tem == Qnil))
{
/* ..then the place-window-hook.. */
rep_PUSHGC(gc_win, win);
Fcall_window_hook (Qplace_window_hook, rep_VAL(w), Qnil, Qor);
rep_POPGC;
}
}
Fwindow_put (rep_VAL(w), Qplaced, Qt);
if (!WINDOW_IS_GONE_P (w))
Fcall_window_hook (Qafter_add_window_hook, rep_VAL(w), Qnil, Qnil);
if (!WINDOW_IS_GONE_P (w))
{
/* Tell the window where it ended up.. */
send_synthetic_configure (w);
}
}
return w;
}
/* Remove W from the managed windows. If DESTROYED is nil, then the
window will be reparented back to the root window */
void
remove_window (Lisp_Window *w, repv destroyed, repv from_error)
{
DB(("remove_window (%s, %s)\n",
rep_STR(w->name), destroyed == Qnil ? "nil" : "t"));
if (w->id != 0)
{
if (destroyed == Qnil && from_error == Qnil)
{
grab_window_events (w, FALSE);
remove_window_frame (w);
/* Restore original border width of the client */
XSetWindowBorderWidth (dpy, w->id, w->attr.border_width);
}
if (from_error == Qnil)
destroy_window_frame (w, FALSE);
if (!WINDOW_IS_GONE_P (w))
remove_from_stacking_list (w);
if (from_error == Qnil)
focus_off_window (w);
w->id = 0;
pending_destroys++;
/* gc will do the rest... */
}
else if (w->frame != 0 && from_error == Qnil)
destroy_window_frame (w, FALSE);
}
void
fix_window_size (Lisp_Window *w)
{
Fgrab_server ();
if (w->frame != 0 && w->rebuild_frame != 0)
w->rebuild_frame (w);
else
XResizeWindow (dpy, w->id, w->attr.width, w->attr.height);
Fungrab_server ();
}
/* Call destroy-notify-hook on any newly-dead windows */
void
emit_pending_destroys (void)
{
if (pending_destroys > 0)
{
Lisp_Window *w;
again:
for (w = window_list; w != 0 && !rep_INTERRUPTP; w = w->next)
{
if (WINDOW_IS_GONE_P (w) && !w->destroyed)
{
w->destroyed = 1;
Fcall_window_hook (Qdestroy_notify_hook,
rep_VAL(w), Qnil, Qnil);
focus_off_window (w);
/* gc may have reordered the list, so we have to start
at the beginning again.. */
goto again;
}
}
}
pending_destroys = 0;
}
/* Lisp functions */
DEFUN("window-get", Fwindow_get, Swindow_get,
(repv win, repv prop), rep_Subr2) /*
::doc:sawfish.wm.windows.subrs#window-get::
window-get WINDOW PROPERTY
Return the value of the property named PROPERTY (a symbol) of WINDOW.
Note that these are Lisp properties not X properties.
::end:: */
{
repv plist;
rep_DECLARE1(win, XWINDOWP);
plist = VWIN(win)->plist;
while (rep_CONSP(plist) && rep_CONSP(rep_CDR(plist)))
{
if (rep_CAR(plist) == prop
|| (!rep_SYMBOLP(prop)
&& rep_value_cmp (rep_CAR(plist), prop) == 0))
{
return rep_CAR(rep_CDR(plist));
}
plist = rep_CDR(rep_CDR(plist));
}
return Qnil;
}
DEFUN("map-window-properties", Fmap_window_properties,
Smap_window_properties, (repv fun, repv win), rep_Subr2) /*
::doc:sawfish.wm.windows.subrs#map-window-properties::
map-window-properties FUNCTION WINDOW
Call (FUNCTION PROPERTY VALUE) for all Lisp properties set on window
object WINDOW.
::end:: */
{
repv ret = Qnil, plist;
rep_GC_root gc_plist, gc_fun;
rep_DECLARE2 (win, XWINDOWP);
plist = VWIN(win)->plist;
rep_PUSHGC (gc_plist, plist);
rep_PUSHGC (gc_fun, fun);
while (rep_CONSP(plist) && rep_CONSP(rep_CDR(plist)))
{
ret = rep_call_lisp2 (fun, rep_CAR (plist), rep_CADR (plist));
if (ret == rep_NULL)
break;
plist = rep_CDDR (plist);
}
rep_POPGC; rep_POPGC;
return ret;
}
void
register_property_monitor (repv prop,
void (*callback) (Lisp_Window *, repv, repv, repv))
{
struct prop_handler *ph = rep_alloc (sizeof (struct prop_handler));
ph->next = prop_handlers;
prop_handlers = ph;
ph->prop = prop;
ph->callback = callback;
}
DEFUN("window-put", Fwindow_put, Swindow_put,
(repv win, repv prop, repv val), rep_Subr3) /*
::doc:sawfish.wm.windows.subrs#window-put::
window-put WINDOW PROPERTY VALUE
Set the value of the property named PROPERTY (a symbol) of WINDOW to VALUE.
Note that these are Lisp properties not X properties.
::end:: */
{
repv plist;
rep_DECLARE1(win, XWINDOWP);
plist = VWIN(win)->plist;
while (rep_CONSP(plist) && rep_CONSP(rep_CDR(plist)))
{
if (rep_CAR(plist) == prop
|| (!rep_SYMBOLP(prop)
&& rep_value_cmp (rep_CAR(plist), prop) == 0))
{
struct prop_handler *ph;
for (ph = prop_handlers; ph != 0; ph = ph->next)
{
repv old = rep_CADR (plist);
if (ph->prop == prop && old != val)
ph->callback (VWIN (win), prop, old, val);
}
rep_CAR(rep_CDR(plist)) = val;
return val;
}
plist = rep_CDR(rep_CDR(plist));
}
plist = Fcons(prop, Fcons(val, VWIN(win)->plist));
if (plist != rep_NULL)
VWIN(win)->plist = plist;
return val;
}
DEFUN("window-name", Fwindow_name, Swindow_name, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-name::
window-name WINDOW
Return the name of window object WINDOW.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->name;
}
DEFUN("window-full-name", Fwindow_full_name, Swindow_full_name,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-full-name::
window-full-name WINDOW
Return the full name of window object WINDOW.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->full_name;
}
DEFUN("window-icon-name", Fwindow_icon_name, Swindow_icon_name,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-icon-name::
window-icon-name WINDOW
Return the name of window object WINDOW's icon.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->icon_name;
}
DEFUN("window-mapped-p", Fwindow_mapped_p, Swindow_mapped_p,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-mapped-p::
window-mapped-p WINDOW
Return t if the client window associated with object WINDOW is mapped.
(This doesn't necessarily mean that it is visible.)
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->mapped ? Qt : Qnil;
}
DEFUN("window-frame", Fwindow_frame, Swindow_frame, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-frame::
window-frame WINDOW
Return the frame object associated with WINDOW.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->frame_style;
}
DEFUN("set-window-frame", Fset_window_frame, Sset_window_frame,
(repv win, repv frame), rep_Subr2) /*
::doc:sawfish.wm.windows.subrs#set-window-frame::
set-window-frame WINDOW FRAME
Set the frame associated with the window object WINDOW to FRAME (a
list). If the window is mapped the old frame will be destroyed and a
new frame constructed as specified by FRAME.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
rep_DECLARE2(frame, rep_LISTP);
Fgrab_server ();
if (VWIN(win)->reparented)
destroy_window_frame (VWIN(win), TRUE);
VWIN(win)->frame_style = frame;
if (VWIN(win)->reparented)
create_window_frame (VWIN(win));
Fungrab_server ();
Fcall_window_hook (Qafter_framing_hook, win, Qnil, Qnil);
return VWIN(win)->frame_style;
}
DEFUN("rebuild-frame", Frebuild_frame, Srebuild_frame, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#rebuild-frame::
rebuild-frame WINDOW
Reinitialises and recalibrates the window frame of WINDOW.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
if (VWIN(win)->frame != 0 && VWIN(win)->rebuild_frame != 0)
{
VWIN(win)->rebuild_frame (VWIN(win));
refresh_frame_parts (VWIN(win));
Fcall_window_hook (Qafter_framing_hook, win, Qnil, Qnil);
}
return win;
}
DEFUN("window-position", Fwindow_position, Swindow_position,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-position::
window-position WINDOW
Return (X . Y) defining the current position of WINDOW.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return Fcons (rep_MAKE_INT(VWIN(win)->attr.x),
rep_MAKE_INT(VWIN(win)->attr.y));
}
DEFUN("window-dimensions", Fwindow_dimensions, Swindow_dimensions,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-dimensions::
window-dimensions WINDOW
Return (WIDTH . HEIGHT) defining the current dimensions of the client
window associated with WINDOW.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return Fcons (rep_MAKE_INT(VWIN(win)->attr.width),
rep_MAKE_INT(VWIN(win)->attr.height));
}
DEFUN("window-frame-dimensions", Fwindow_frame_dimensions,
Swindow_frame_dimensions, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-frame-dimensions::
window-frame-dimensions WINDOW
Return (WIDTH . HEIGHT) defining the current dimensions of the frame
surrounding WINDOW.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
if (VWIN(win)->reparented)
{
return Fcons (rep_MAKE_INT(VWIN(win)->frame_width),
rep_MAKE_INT(VWIN(win)->frame_height));
}
else
return Fwindow_dimensions (win);
}
DEFUN("window-frame-offset", Fwindow_frame_offset,
Swindow_frame_offset, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-frame-offset::
window-frame-offset WINDOW
Return (X . Y) defining the offset from the origin of the client window
associated with WINDOW to its frame window.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return Fcons (rep_MAKE_INT(VWIN(win)->frame_x),
rep_MAKE_INT(VWIN(win)->frame_y));
}
DEFUN("windowp", Fwindowp, Swindowp, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#windowp::
windowp ARG
Return t if ARG is a window object.
::end:: */
{
return WINDOWP(win) ? Qt : Qnil;
}
DEFUN("set-input-focus", Fset_input_focus, Sset_input_focus,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#set-input-focus::
set-input-focus WINDOW
Set the input focus to WINDOW. If WINDOW is nil, then no window will
have the focus.
::end:: */
{
if (win != Qnil && win != Qroot)
{
rep_DECLARE1(win, WINDOWP);
focus_on_window (VWIN(win));
}
else
focus_on_window (0);
return win;
}
DEFUN("input-focus", Finput_focus, Sinput_focus, (void), rep_Subr0) /*
::doc:sawfish.wm.windows.subrs#input-focus::
input-focus
Return the window object that has the input focus, or nil if none does.
::end:: */
{
return (focus_window == 0) ? Qnil : rep_VAL(focus_window);
}
DEFUN("window-wants-input-p", Fwindow_wants_input_p, Swindow_wants_input_p,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-wants-input-p::
window-wants-input-p WINDOW
Return t if the client window associated with object WINDOW has hinted
that it would like to be given the input focus when applicable.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
if (VWIN(win)->does_wm_take_focus)
return Qt;
else
return window_input_hint_p (VWIN (win)) ? Qt : Qnil;
}
DEFUN("managed-windows", Fmanaged_windows, Smanaged_windows,
(void), rep_Subr0) /*
::doc:sawfish.wm.windows.subrs#managed-windows::
managed-windows
Return a list of all known client window objects.
::end:: */
{
repv list = Qnil;
Lisp_Window *w = window_list;
while (w != 0)
{
if (!WINDOW_IS_GONE_P (w))
list = Fcons (rep_VAL(w), list);
w = w->next;
}
return list;
}
DEFUN("get-window-by-id", Fget_window_by_id, Sget_window_by_id,
(repv id), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#get-window-by-id::
get-window-by-id ID
Return the window object associated with xid ID, or nil.
::end:: */
{
Lisp_Window *w;
rep_DECLARE1(id, rep_INTEGERP);
w = find_window_by_id (rep_get_long_uint (id));
return w ? rep_VAL(w) : Qnil;
}
DEFUN("stacking-order", Fstacking_order, Sstacking_order, (void), rep_Subr0) /*
::doc:sawfish.wm.windows.subrs#stacking-order::
stacking-order
Return a list of windows defining the current stacking order of all
client windows.
::end:: */
{
return make_stacking_list ();
}
DEFUN("window-visibility", Fwindow_visibility, Swindow_visibility,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-visibility::
window-visibility WINDOW
Return a symbol defining the visibility of WINDOW. Possible returned
symbols are `fully-obscured', `partially-obscured' or `unobscured'.
::end:: */
{
repv sym = Qnil;
rep_DECLARE1(win, WINDOWP);
switch (VWIN(win)->frame_vis)
{
case VisibilityFullyObscured:
sym = Qfully_obscured;
break;
case VisibilityPartiallyObscured:
sym = Qpartially_obscured;
break;
case VisibilityUnobscured:
sym = Qunobscured;
break;
}
return sym;
}
DEFUN("window-urgent-p", Fwindow_urgent_p, Swindow_urgent_p,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-urgent-p::
window-urgent-p WINDOW
Return true if the `Urgency' hint of the window associated with WINDOW
is set.
::end:: */
{
rep_DECLARE1 (win, WINDOWP);
return ((VWIN (win)->wmhints
&& VWIN (win)->wmhints->flags & XUrgencyHint) ? Qt : Qnil);
}
DEFUN("window-shaped-p", Fwindow_shaped_p, Swindow_shaped_p,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-shaped-p::
window-shaped-p WINDOW
Return non-nil if WINDOW is shaped.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->shaped ? Qt : Qnil;
}
DEFUN("hide-window", Fhide_window, Shide_window, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#hide-window::
hide-window WINDOW
Prevent WINDOW from being displayed. See `show-window'.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
if (VWIN(win)->visible)
{
if (VWIN(win)->mapped)
{
if (VWIN(win)->frame)
XUnmapWindow (dpy, VWIN(win)->frame);
if (!VWIN(win)->client_unmapped)
{
before_local_map (VWIN(win));
XUnmapWindow (dpy, VWIN(win)->id);
VWIN(win)->client_unmapped = 1;
after_local_map (VWIN(win));
}
}
VWIN(win)->visible = 0;
reset_frame_parts (VWIN(win));
}
return win;
}
DEFUN("show-window", Fshow_window, Sshow_window, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#show-window::
show-window WINDOW
Ensure that WINDOW (if it has been mapped) is visible. See `hide-window'.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
if (!VWIN(win)->visible)
{
if (VWIN(win)->mapped)
{
if (VWIN(win)->client_unmapped && !VWIN(win)->client_hidden)
{
before_local_map (VWIN(win));
XMapWindow (dpy, VWIN(win)->id);
VWIN(win)->client_unmapped = 0;
after_local_map (VWIN(win));
}
if (VWIN(win)->frame)
XMapWindow (dpy, VWIN(win)->frame);
}
VWIN(win)->visible = 1;
}
return win;
}
DEFUN("window-visible-p", Fwindow_visible_p, Swindow_visible_p,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-visible-p::
window-visible-p WINDOW
Return t if WINDOW is currently visible (i.e. not hidden, see `hide-window').
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->visible ? Qt : Qnil;
}
DEFUN("window-framed-p", Fwindow_framed_p, Swindow_framed_p,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-framed-p::
window-framed-p WINDOW
Return t if WINDOW has been reparented to a frame window.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->reparented ? Qt : Qnil;
}
DEFUN("window-id", Fwindow_id, Swindow_id, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-id::
window-id WINDOW
Return the numeric id of the client window associated with object
WINDOW. Returns nil if the client window has been deleted.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return VWIN(win)->id ? rep_MAKE_INT (VWIN(win)->id) : Qnil;
}
DEFUN("window-group-id", Fwindow_group_id, Swindow_group_id,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-group-id::
window-group-id WINDOW
Return the numeric id defining the leader of the group that WINDOW is a
member of, or nil if it is not a member of a group.
::end:: */
{
rep_DECLARE1(win, WINDOWP);
if (VWIN(win)->wmhints == 0)
return Qnil;
return ((VWIN(win)->wmhints->flags & WindowGroupHint)
? rep_MAKE_INT (VWIN(win)->wmhints->window_group)
: Qnil);
}
DEFUN("window-border-width", Fwindow_border_width, Swindow_border_width,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-border-width::
window-border-width WINDOW
::end:: */
{
rep_DECLARE1(win, WINDOWP);
return rep_MAKE_INT(VWIN(win)->attr.border_width);
}
DEFUN("window-size-hints", Fwindow_size_hints, Swindow_size_hints,
(repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-size-hints::
window-size-hints WINDOW
Return an alist defining the size-hints specified by the client window
associated with WINDOW. Possible keys in the alist are `min-height',
`max-height', `min-width', `max-width', `height-inc', `width-inc',
`min-aspect', `max-aspect', `base-height', `base-width',
`user-position', `program-position', `user-size', `program-size',
`window-gravity', `border-size'.
::end:: */
{
repv ret = Qnil;
XSizeHints *hints;
long flags;
rep_DECLARE1(win, WINDOWP);
hints = &VWIN(win)->hints;
flags = hints->flags;
/* Some sanity checking */
if ((flags & PMinSize)
&& (hints->min_width < 0 || hints->min_height < 0))
flags &= ~PMinSize;
if ((flags & PMaxSize)
&& (hints->max_width <= 0 || hints->max_height <= 0))
flags &= ~PMaxSize;
if ((flags & PResizeInc)
&& (hints->width_inc <= 0 || hints->width_inc <= 0))
flags &= ~PResizeInc;
if ((flags & PBaseSize)
&& (hints->base_width <= 0 || hints->base_height <= 0))
flags &= ~PBaseSize;
if (flags & PMinSize)
{
ret = Fcons (Fcons (Qmin_width, rep_MAKE_INT(MAX(hints->min_width,1))),
Fcons (Fcons (Qmin_height, rep_MAKE_INT(MAX(hints->min_height, 1))), ret));
}
if (flags & PMaxSize)
{
ret = Fcons (Fcons (Qmax_width, rep_MAKE_INT(hints->max_width)),
Fcons (Fcons (Qmax_height,
rep_MAKE_INT(hints->max_height)), ret));
}
if (flags & PResizeInc)
{
ret = Fcons (Fcons (Qwidth_inc, rep_MAKE_INT(hints->width_inc)),
Fcons (Fcons (Qheight_inc,
rep_MAKE_INT(hints->height_inc)), ret));
}
if (flags & PBaseSize)
{
ret = Fcons (Fcons (Qbase_width, rep_MAKE_INT(hints->base_width)),
Fcons (Fcons (Qbase_height,
rep_MAKE_INT(hints->base_height)), ret));
}
if (flags & PAspect)
{
ret = Fcons (Fcons (Qmin_aspect,
Fcons (rep_MAKE_INT(hints->min_aspect.x),
rep_MAKE_INT(hints->min_aspect.y))),
Fcons (Fcons (Qmax_aspect,
Fcons (rep_MAKE_INT(hints->max_aspect.x),
rep_MAKE_INT(hints->max_aspect.y))),
ret));
}
if (flags & USPosition)
ret = Fcons (Fcons (Quser_position, Qt), ret);
else if (flags & PPosition)
ret = Fcons (Fcons (Qprogram_position, Qt), ret);
if (flags & USSize)
ret = Fcons (Fcons (Quser_size, Qt), ret);
else if (flags & PSize)
ret = Fcons (Fcons (Qprogram_size, Qt), ret);
if ((flags & PWinGravity)
&& hints->win_gravity >= ForgetGravity
&& hints->win_gravity <= StaticGravity)
{
ret = Fcons (Fcons (Qwindow_gravity,
gravity_map[hints->win_gravity]), ret);
}
return ret;
}
DEFUN("call-window-hook", Fcall_window_hook, Scall_window_hook,
(repv hook, repv win, repv args, repv type), rep_Subr4) /*
::doc:sawfish.wm.windows.subrs#call-window-hook::
call-window-hook HOOK WINDOW &optional ARGS HOOK-TYPE
Call HOOK for WINDOW with extra arguments ARGS. See `call-hook' for a
description of HOOK-TYPE.
::end:: */
{
repv tem;
rep_GC_root gc_hook, gc_args, gc_type;
rep_DECLARE1(hook, rep_SYMBOLP);
rep_DECLARE2(win, XWINDOWP);
args = Fcons (win, args);
rep_PUSHGC(gc_hook, hook);
rep_PUSHGC(gc_args, args);
rep_PUSHGC(gc_type, type);
tem = Fwindow_get (win, hook);
if (tem && tem != Qnil)
{
tem = Fcall_hook (tem, args, type);
if (!tem || (type == Qand && tem == Qnil)
|| (type == Qor && tem != Qnil))
{
goto out;
}
}
tem = Fcall_hook (hook, args, type);
out:
rep_POPGC; rep_POPGC; rep_POPGC;
return tem;
}
DEFUN("window-icon-image", Fwindow_icon_image,
Swindow_icon_image, (repv win), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#window-icon-image::
window-icon-image WINDOW
Returns an image object representing the icon currently associated with
WINDOW. Returns the symbol `nil' if no such image.
::end:: */
{
rep_DECLARE1 (win, WINDOWP);
if (VWIN (win)->icon_image == rep_NULL)
{
Window pixmap_id = 0, mask_id = 0;
if (VWIN (win)->wmhints != 0)
{
if (VWIN (win)->wmhints->flags & IconPixmapHint
&& VWIN (win)->wmhints->icon_pixmap != 0)
{
pixmap_id = VWIN (win)->wmhints->icon_pixmap;
}
if (VWIN (win)->wmhints->flags & IconMaskHint
&& VWIN (win)->wmhints->icon_mask != 0)
{
mask_id = VWIN (win)->wmhints->icon_mask;
}
}
if (pixmap_id == 0 && !WINDOW_IS_GONE_P (VWIN (win)))
{
Atom actual_type;
int actual_format;
long nitems, bytes_after;
u_long *data = 0;
static Atom kwm_win_icon = 0;
if (kwm_win_icon == 0)
kwm_win_icon = XInternAtom (dpy, "KWM_WIN_ICON", False);
if (XGetWindowProperty (dpy, VWIN (win)->id, kwm_win_icon,
0, 2, False, kwm_win_icon,
&actual_type, &actual_format,
&nitems, &bytes_after,
(u_char **) &data) == Success
&& actual_type == kwm_win_icon
&& bytes_after == 0)
{
pixmap_id = data[0];
mask_id = data[1];
}
if (data != 0)
XFree (data);
}
VWIN (win)->icon_image = Qnil;
if (pixmap_id != 0)
{
VWIN (win)->icon_image = (Fmake_image_from_x_drawable
(rep_MAKE_INT (pixmap_id),
mask_id ? rep_MAKE_INT (mask_id) : Qnil));
}
}
return VWIN (win)->icon_image;
}
DEFUN ("map-windows", Fmap_windows, Smap_windows, (repv fun), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#map-windows::
map-windows FUN
Map the single-parameter function FUN over all existing windows.
::end:: */
{
repv w;
rep_GC_root gc_fun, gc_w;
repv ret = Qnil;
rep_PUSHGC (gc_fun, fun);
rep_PUSHGC (gc_w, w);
for (w = rep_VAL (window_list); w != rep_NULL; w = rep_VAL (VWIN(w)->next))
{
if (!WINDOW_IS_GONE_P (VWIN (w)))
{
ret = rep_call_lisp1 (fun, w);
if (ret == rep_NULL)
break;
}
}
rep_POPGC; rep_POPGC;
return ret;
}
DEFUN ("filter-windows", Ffilter_windows,
Sfilter_windows, (repv pred), rep_Subr1) /*
::doc:sawfish.wm.windows.subrs#filter-windows::
filter-windows PRED
Return the list of windows that match the predicate function PRED.
::end:: */
{
repv w, output = Qnil, *ptr = &output;
rep_GC_root gc_pred, gc_w, gc_output;
rep_PUSHGC(gc_pred, pred);
rep_PUSHGC(gc_w, w);
rep_PUSHGC(gc_output, output);
for (w = rep_VAL (window_list); w != rep_NULL; w = rep_VAL (VWIN(w)->next))
{
if (!WINDOW_IS_GONE_P (VWIN (w)))
{
repv tem = rep_call_lisp1 (pred, w);
if (tem == rep_NULL)
{
output = rep_NULL;
break;
}
if (tem != Qnil)
{
*ptr = Fcons (w, Qnil);
ptr = rep_CDRLOC (*ptr);
}
}
}
rep_POPGC; rep_POPGC; rep_POPGC;
return output;
}
/* type hooks */
static int
window_cmp (repv w1, repv w2)
{
return w1 != w2;
}
static void
window_prin (repv stream, repv win)
{
char buf[128];
sprintf (buf, "#<window %lx>", VWIN(win)->id);
rep_stream_puts (stream, buf, -1, FALSE);
}
static void
window_mark (repv win)
{
rep_MARKVAL(VWIN(win)->plist);
rep_MARKVAL(VWIN(win)->frame_style);
mark_frame_parts (VWIN(win));
rep_MARKVAL(VWIN(win)->name);
rep_MARKVAL(VWIN(win)->full_name);
rep_MARKVAL(VWIN(win)->icon_name);
rep_MARKVAL(VWIN(win)->icon_image);
}
static void
window_mark_type (void)
{
Lisp_Window *w;
struct prop_handler *ph;
for (w = window_list; w != 0; w = w->next)
{
if (!WINDOW_IS_GONE_P (w) || !w->destroyed)
rep_MARKVAL(rep_VAL(w));
}
for (ph = prop_handlers; ph != 0; ph = ph->next)
rep_MARKVAL (ph->prop);
rep_MARKVAL (rep_VAL (focus_window));
}
static void
window_sweep (void)
{
Lisp_Window **ptr = &window_list;
while (*ptr != 0)
{
Lisp_Window *w = *ptr;
if (!rep_GC_CELL_MARKEDP(rep_VAL(w)))
{
assert (!window_in_stacking_list_p (w));
destroy_window_frame (w, FALSE);
if (w->wmhints != 0)
XFree (w->wmhints);
if (w->n_cmap_windows > 0)
XFree (w->cmap_windows);
*ptr = w->next;
rep_FREE_CELL(w);
}
else
{
ptr = &(w->next);
rep_GC_CLR_CELL(rep_VAL(w));
}
}
}
/* initialisation */
void
manage_windows (void)
{
Window root, parent, *children, focus;
unsigned int nchildren, i;
int revert_to;
Fgrab_server ();
XGetInputFocus (dpy, &focus, &revert_to);
if (focus == PointerRoot)
{
Window root, child;
Bool found;
int rx, ry, wx, wy;
unsigned mask;
found = XQueryPointer (dpy, DefaultRootWindow(dpy), &root,
&child, &rx, &ry, &wx, &wy, &mask);
if (!found)
{
found = XQueryPointer (dpy, root, &root, &child,
&rx, &ry, &wx, &wy, &mask);
}
focus = child;
}
XQueryTree (dpy, root_window, &root, &parent, &children, &nchildren);
initialising = TRUE;
for (i = 0; i < nchildren; i++)
{
if (mapped_not_override_p (children[i]))
{
XEvent fake;
Lisp_Window *w;
fake.xmaprequest.window = children[i];
/* Make sure the window is initially unmapped. We expect to
get map-notify events when we later remap it.. #67601 */
XUnmapWindow (dpy, children[i]);
map_request (&fake);
w = find_window_by_id (children[i]);
}
}
initialising = FALSE;
if (nchildren > 0)
XFree (children);
/* Try to keep the current focus state. */
focus_on_window (0);
if (focus != None)
{
Lisp_Window *w = find_window_by_id (focus);
if (w != 0)
focus_on_window (w);
}
Fungrab_server ();
Fcall_hook (Qafter_initialization_hook, Qnil, Qnil);
}
void
windows_init (void)
{
repv tem;
window_type = rep_register_new_type ("window", window_cmp, window_prin,
window_prin, window_sweep,
window_mark, window_mark_type,
0, 0, 0, 0, 0, 0);
tem = rep_push_structure ("sawfish.wm.windows.subrs");
rep_ADD_SUBR(Swindow_get);
rep_ADD_SUBR(Smap_window_properties);
rep_ADD_SUBR(Swindow_put);
rep_ADD_SUBR(Swindow_name);
rep_ADD_SUBR(Swindow_full_name);
rep_ADD_SUBR(Swindow_icon_name);
rep_ADD_SUBR(Swindow_mapped_p);
rep_ADD_SUBR(Swindow_frame);
rep_ADD_SUBR(Sset_window_frame);
rep_ADD_SUBR(Srebuild_frame);
rep_ADD_SUBR(Swindow_position);
rep_ADD_SUBR(Swindow_dimensions);
rep_ADD_SUBR(Swindow_frame_dimensions);
rep_ADD_SUBR(Swindow_frame_offset);
rep_ADD_SUBR(Swindowp);
rep_ADD_SUBR(Sset_input_focus);
rep_ADD_SUBR(Sinput_focus);
rep_ADD_SUBR(Swindow_wants_input_p);
rep_ADD_SUBR(Smanaged_windows);
rep_ADD_SUBR(Sget_window_by_id);
rep_ADD_SUBR(Sstacking_order);
rep_ADD_SUBR(Swindow_visibility);
rep_ADD_SUBR(Swindow_urgent_p);
rep_ADD_SUBR(Swindow_shaped_p);
rep_ADD_SUBR(Shide_window);
rep_ADD_SUBR(Sshow_window);
rep_ADD_SUBR(Swindow_visible_p);
rep_ADD_SUBR(Swindow_framed_p);
rep_ADD_SUBR(Swindow_id);
rep_ADD_SUBR(Swindow_group_id);
rep_ADD_SUBR(Swindow_size_hints);
rep_ADD_SUBR(Scall_window_hook);
rep_ADD_SUBR(Swindow_border_width);
rep_ADD_SUBR(Swindow_icon_image);
rep_ADD_SUBR(Smap_windows);
rep_ADD_SUBR(Sfilter_windows);
rep_pop_structure (tem);
rep_INTERN_SPECIAL(before_add_window_hook);
rep_INTERN_SPECIAL(add_window_hook);
rep_INTERN_SPECIAL(after_add_window_hook);
rep_INTERN_SPECIAL(place_window_hook);
rep_INTERN(placed);
rep_INTERN_SPECIAL(after_framing_hook);
rep_INTERN_SPECIAL(after_initialization_hook);
rep_INTERN_SPECIAL(remove_window_hook);
rep_INTERN(fully_obscured);
rep_INTERN(partially_obscured);
rep_INTERN(unobscured);
rep_INTERN(min_width);
rep_INTERN(min_height);
rep_INTERN(max_width);
rep_INTERN(max_height);
rep_INTERN(width_inc);
rep_INTERN(height_inc);
rep_INTERN(base_width);
rep_INTERN(base_height);
rep_INTERN(min_aspect);
rep_INTERN(max_aspect);
rep_INTERN(user_size);
rep_INTERN(user_position);
rep_INTERN(program_size);
rep_INTERN(program_position);
rep_INTERN(window_gravity);
rep_INTERN(forget);
rep_INTERN(static);
rep_INTERN(north_west);
rep_INTERN(north);
rep_INTERN(north_east);
rep_INTERN(west);
rep_INTERN(center);
rep_INTERN(east);
rep_INTERN(south_west);
rep_INTERN(south);
rep_INTERN(south_east);
gravity_map[ForgetGravity] = Qforget;
gravity_map[NorthWestGravity] = Qnorth_west;
gravity_map[NorthGravity] = Qnorth;
gravity_map[NorthEastGravity] = Qnorth_east;
gravity_map[WestGravity] = Qwest;
gravity_map[CenterGravity] = Qcenter;
gravity_map[EastGravity] = Qeast;
gravity_map[SouthWestGravity] = Qsouth_west;
gravity_map[SouthGravity] = Qsouth;
gravity_map[SouthEastGravity] = Qsouth_east;
gravity_map[StaticGravity] = Qstatic;
}
void
windows_kill (void)
{
Lisp_Window *w = window_list;
repv next;
rep_GC_root gc_next;
rep_PUSHGC (gc_next, next);
while (w != 0)
{
next = rep_VAL (w->next);
Fcall_window_hook (Qremove_window_hook, rep_VAL (w), Qnil, Qnil);
remove_window (w, Qnil, Qnil);
w = VWIN (next);
}
}
syntax highlighted by Code2HTML, v. 0.9.1