From 718539f1673682cf65c8944f292fae50524743a3 Mon Sep 17 00:00:00 2001 From: jenglish Date: Wed, 16 Nov 2005 02:51:38 +0000 Subject: Added support for [wm attributes] on X11 [TIP#231, patch#1062022]. --- ChangeLog | 4 + doc/wm.n | 28 +++- tests/unixWm.test | 38 +++++- unix/tkUnixWm.c | 400 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 436 insertions(+), 34 deletions(-) diff --git a/ChangeLog b/ChangeLog index b196ebc..6c06024 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2005-11-15 Joe English + * unix/tkUnixWm.c, tests/unixWm.test, doc/wm.n: + Support for [wm attributes] on X11 [TIP#231, patch#1062022]. + 2005-11-14 Joe English * library/bgerror.tcl: Truncate error messages at 45 characters instead of 30 [#1224235]. diff --git a/doc/wm.n b/doc/wm.n index adee805..9b2afa9 100644 --- a/doc/wm.n +++ b/doc/wm.n @@ -5,7 +5,7 @@ '\" See the file "license.terms" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -'\" RCS: @(#) $Id: wm.n,v 1.23 2005/04/06 21:11:54 dkf Exp $ +'\" RCS: @(#) $Id: wm.n,v 1.24 2005/11/16 02:51:38 jenglish Exp $ '\" .so man.macros .TH wm n 8.5 Tk "Tk Built-In Commands" @@ -82,7 +82,31 @@ finder icon). \fB\-alpha\fR sets the alpha transparency level of the window, it accepts a value from \fB0.0\fR (fully transparent) to \fB1.0\fR (opaque), values outside that range will be constrained. .PP -On Unix, there are currently no special attribute values. +On X11, the following attributes may be set. +These are not supported by all window managers, +and will have no effect under older WMs. +.\" See http://www.freedesktop.org/Standards/wm-spec +.RS +.TP +\fB-topmost\fP +Requests that this window should be kept above +all other windows that do not also have the \fB-topmost\fP +attribute set. +.TP +\fB-zoomed\fP +Requests that the window should be maximized. +This is the same as \fB[wm state zoomed]\fP on Windows. +.TP +\fB-fullscreen\fP +Requests that the window should fill the entire screen +and have no window decorations. +.RE +.PP +On X11, changes to window attributes are performed asynchronously. +Querying the value of an attribute returns the current state, +which will not be the same as the value most recently set +if the window manager has not yet processed the request +or if it does not support the attribute. .RE .TP \fBwm client \fIwindow\fR ?\fIname\fR? diff --git a/tests/unixWm.test b/tests/unixWm.test index 026f665..fcce160 100644 --- a/tests/unixWm.test +++ b/tests/unixWm.test @@ -7,7 +7,7 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: unixWm.test,v 1.40 2004/10/05 22:04:45 hobbs Exp $ +# RCS: @(#) $Id: unixWm.test,v 1.41 2005/11/16 02:51:38 jenglish Exp $ package require tcltest 2.2 eval tcltest::configure $argv @@ -2438,16 +2438,42 @@ test unixWm-59.3 {exit processing} unix { list $error $msg } {0 {}} -test unixWm-60.1 {wm attributes} unix { +# +# wm attributes tests: +# +# NOTE: since [wm attributes] is not guaranteed to have any effect, +# the only thing we can really test here is the syntax. +# +test unixWm-60.1 {wm attributes - test} -constraints unix -body { destroy .t toplevel .t wm attributes .t -} {} -test unixWm-60.2 {wm attributes} unix { +} -result [list -alpha 1.0 -topmost 0 -zoomed 0 -fullscreen 0] + +test unixWm-60.2 {wm attributes - test} -constraints unix -body { + destroy .t + toplevel .t + wm attributes .t -topmost +} -result 0 + +test unixWm-60.3 {wm attributes - set (unrealized)} -constraints unix -body { + destroy .t + toplevel .t + wm attributes .t -topmost 1 +} + +test unixWm-60.4 {wm attributes - set (realized)} -constraints unix -body { + destroy .t + toplevel .t + tkwait visibility .t + wm attributes .t -topmost 1 +} + +test unixWm-60.5 {wm attributes - bad attribute} -constraints unix -body { destroy .t toplevel .t - list [catch {wm attributes .t -foo} msg] $msg -} {1 {wrong # args: should be "wm attributes window"}} + wm attributes .t -foo +} -returnCodes 1 -match glob -result {bad attribute "-foo":*} test unixWm-61.1 {Tk_WmCmd procedure, "iconphoto" option} unix { list [catch {wm iconph .} msg] $msg diff --git a/unix/tkUnixWm.c b/unix/tkUnixWm.c index d85a9db..e79b5b1 100644 --- a/unix/tkUnixWm.c +++ b/unix/tkUnixWm.c @@ -12,7 +12,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkUnixWm.c,v 1.49 2005/10/21 01:51:45 dkf Exp $ + * RCS: @(#) $Id: tkUnixWm.c,v 1.50 2005/11/16 02:51:38 jenglish Exp $ */ #include "tkPort.h" @@ -43,6 +43,26 @@ typedef struct ProtocolHandler { ((unsigned) (sizeof(ProtocolHandler) - 3 + cmdLength)) /* + * Data for [wm attributes] command: + */ +typedef struct { + double alpha; /* Transparency; 0.0=transparent, 1.0=opaque */ + int topmost; /* Flag: true=>stay-on-top */ + int zoomed; /* Flag: true=>maximized */ + int fullscreen; /* Flag: true=>fullscreen */ +} WmAttributes; + +typedef enum { + WMATT_ALPHA, WMATT_TOPMOST, WMATT_ZOOMED, WMATT_FULLSCREEN, + _WMATT_LAST_ATTRIBUTE +} WmAttribute; + +static const char *WmAttributeNames[] = { + "-alpha", "-topmost", "-zoomed", "-fullscreen", + NULL +}; + +/* * A data structure of the following type holds window-manager-related * information for each top-level window in an application. */ @@ -184,6 +204,8 @@ typedef struct TkWmInfo { * Miscellaneous information. */ + WmAttributes attributes; /* Current state of [wm attributes] */ + WmAttributes reqState; /* Requested state of [wm attributes] */ ProtocolHandler *protPtr; /* First in list of protocol handlers for this * window (NULL means none). */ int cmdArgc; /* Number of elements in cmdArgv below. */ @@ -312,6 +334,7 @@ static void MenubarDestroyProc(ClientData clientData, static int ParseGeometry(Tcl_Interp *interp, char *string, TkWindow *winPtr); static void ReparentEvent(WmInfo *wmPtr, XReparentEvent *eventPtr); +static void PropertyEvent(WmInfo *wmPtr, XPropertyEvent *eventPtr); static void TkWmStackorderToplevelWrapperMap(TkWindow *winPtr, Display *display, Tcl_HashTable *reparentTable); static void TopLevelReqProc(ClientData dummy, Tk_Window tkwin); @@ -324,6 +347,9 @@ static void UpdateTitle(TkWindow *winPtr); static void UpdatePhotoIcon(TkWindow *winPtr); static void UpdateVRootGeometry(WmInfo *wmPtr); static void UpdateWmProtocols(WmInfo *wmPtr); +static void SetNetWmState(TkWindow*, const char *atomName, int on); +static void CheckNetWmState(WmInfo *, Atom *atoms, int numAtoms); +static void UpdateNetWmState(WmInfo *); static void WaitForConfigureNotify(TkWindow *winPtr, unsigned long serial); static int WaitForEvent(Display *display, @@ -535,6 +561,15 @@ TkWmNewWindow( wmPtr->hints.window_group = None; /* + * Initialize attributes. + */ + wmPtr->attributes.alpha = 1.0; + wmPtr->attributes.topmost = 0; + wmPtr->attributes.zoomed = 0; + wmPtr->attributes.fullscreen = 0; + wmPtr->reqState = wmPtr->attributes; + + /* * Default the maximum dimensions to the size of the display, minus a * guess about how space is needed for window manager decorations. */ @@ -681,6 +716,11 @@ TkWmMapWindow( wmPtr->flags &= ~WM_ABOUT_TO_MAP; /* + * Update _NET_WM_STATE hints: + */ + UpdateNetWmState(wmPtr); + + /* * Map the window, then wait to be sure that the window manager has * processed the map operation. */ @@ -927,14 +967,7 @@ TkWmSetClass( * * Tk_WmObjCmd -- * - * This function is invoked to process the "wm" Tcl command. See the user - * documentation for details on what it does. - * - * Results: - * A standard Tcl result. - * - * Side effects: - * See the user documentation. + * This function is invoked to process the "wm" Tcl command. * *---------------------------------------------------------------------- */ @@ -1164,16 +1197,139 @@ WmAspectCmd( /* *---------------------------------------------------------------------- * + * WmSetAttribute -- + * + * Helper routine for WmAttributesCmd. Sets the value + * of the specified attribute. + * + * Returns: + * + * TCL_OK if successful, TCL_ERROR otherwise. In case of an + * error, leaves a message in the interpreter's result. + * + *---------------------------------------------------------------------- + */ +static int WmSetAttribute( + TkWindow *winPtr, /* Toplevel to work with */ + Tcl_Interp *interp, /* Current interpreter */ + WmAttribute attribute, /* Code of attribute to set */ + Tcl_Obj *value) /* New value */ +{ + WmInfo *wmPtr = winPtr->wmInfoPtr; + switch (attribute) { + case WMATT_ALPHA: + { + unsigned long opacity; /* 0=transparent, 0xFFFFFFFF=opaque */ + + if (TCL_OK != Tcl_GetDoubleFromObj( + interp, value, &wmPtr->reqState.alpha)) { + return TCL_ERROR; + } + if (wmPtr->reqState.alpha < 0.0) { + wmPtr->reqState.alpha = 0.0; + } + if (wmPtr->reqState.alpha > 1.0) { + wmPtr->reqState.alpha = 1.0; + } + + if (!wmPtr->wrapperPtr) { + break; + } + + opacity = 0xFFFFFFFFul * wmPtr->reqState.alpha; + XChangeProperty(winPtr->display, wmPtr->wrapperPtr->window, + Tk_InternAtom((Tk_Window)winPtr, "_NET_WM_WINDOW_OPACITY"), + XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)&opacity, 1L); + wmPtr->attributes.alpha = wmPtr->reqState.alpha; + + break; + } + case WMATT_TOPMOST: + if (TCL_OK != Tcl_GetBooleanFromObj( + interp, value, &wmPtr->reqState.topmost)) { + return TCL_ERROR; + } + SetNetWmState(winPtr, + "_NET_WM_STATE_ABOVE", wmPtr->reqState.topmost); + break; + case WMATT_ZOOMED: + if (TCL_OK != Tcl_GetBooleanFromObj( + interp, value, &wmPtr->reqState.zoomed)) { + return TCL_ERROR; + } + SetNetWmState(winPtr, + "_NET_WM_STATE_MAXIMIZED_VERT", wmPtr->reqState.zoomed); + SetNetWmState(winPtr, + "_NET_WM_STATE_MAXIMIZED_HORZ", wmPtr->reqState.zoomed); + break; + case WMATT_FULLSCREEN: + if (TCL_OK != Tcl_GetBooleanFromObj( + interp, value, &wmPtr->reqState.fullscreen)) { + return TCL_ERROR; + } + SetNetWmState(winPtr, + "_NET_WM_STATE_FULLSCREEN", wmPtr->reqState.fullscreen); + break; + case _WMATT_LAST_ATTRIBUTE: /* NOTREACHED */ + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * WmGetAttribute -- + * + * Helper routine for WmAttributesCmd. Returns the current value + * of the specified attribute. + * + * See also: CheckNetWmState(). + * + *---------------------------------------------------------------------- + */ +static Tcl_Obj *WmGetAttribute( + TkWindow *winPtr, /* Toplevel to work with */ + WmAttribute attribute) /* Code of attribute to get */ +{ + WmInfo *wmPtr = winPtr->wmInfoPtr; + switch (attribute) { + case WMATT_ALPHA: + return Tcl_NewDoubleObj(wmPtr->attributes.alpha); + case WMATT_TOPMOST: + return Tcl_NewBooleanObj(wmPtr->attributes.topmost); + case WMATT_ZOOMED: + return Tcl_NewBooleanObj(wmPtr->attributes.zoomed); + case WMATT_FULLSCREEN: + return Tcl_NewBooleanObj(wmPtr->attributes.fullscreen); + case _WMATT_LAST_ATTRIBUTE: /*NOTREACHED*/ + break; + } + /*NOTREACHED*/ + return NULL; +} + +/* + *---------------------------------------------------------------------- + * * WmAttributesCmd -- * * This function is invoked to process the "wm attributes" Tcl command. - * See the user documentation for details on what it does. * - * Results: - * A standard Tcl result. + * Syntax: * - * Side effects: - * See the user documentation. + * wm attributes $win ?-attribute ?value attribute value...?? + * + * Notes: + * + * Attributes of mapped windows are set by sending a _NET_WM_STATE + * ClientMessage to the root window (see SetNetWmState). + * For withdrawn windows, we keep track of the requested attribute + * state, and set the _NET_WM_STATE property ourselves immediately + * prior to mapping the window. + * + * See also: TIP#231, EWMH. * *---------------------------------------------------------------------- */ @@ -1186,8 +1342,39 @@ WmAttributesCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - if (objc != 3) { - Tcl_WrongNumArgs(interp, 2, objv, "window"); + int attribute = 0; + if (objc == 3) { /* wm attributes $win */ + Tcl_Obj *result = Tcl_NewListObj(0,0); + for (attribute = 0; attribute < _WMATT_LAST_ATTRIBUTE; ++attribute) { + Tcl_ListObjAppendElement(interp, result, + Tcl_NewStringObj(WmAttributeNames[attribute], -1)); + Tcl_ListObjAppendElement(interp, result, + WmGetAttribute(winPtr, attribute)); + } + Tcl_SetObjResult(interp, result); + return TCL_OK; + } else if (objc == 4) { /* wm attributes $win -attribute */ + if (Tcl_GetIndexFromObj(interp, objv[3], WmAttributeNames, + "attribute", 0, &attribute) != TCL_OK) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, + WmGetAttribute(winPtr, attribute)); + return TCL_OK; + } else if ((objc - 3) % 2 == 0) { /* wm attributes $win -att value... */ + int i; + for (i = 3; i < objc; i += 2) { + if (Tcl_GetIndexFromObj(interp, objv[i], WmAttributeNames, + "attribute", 0, &attribute) != TCL_OK) { + return TCL_ERROR; + } + if (WmSetAttribute(winPtr,interp,attribute,objv[i+1]) != TCL_OK) { + return TCL_ERROR; + } + } + return TCL_OK; + } else { + Tcl_WrongNumArgs(interp, 2, objv, "window ?-attribute ?value ...??"); return TCL_ERROR; } return TCL_OK; @@ -3909,6 +4096,48 @@ ComputeReparentGeometry( /* *---------------------------------------------------------------------- * + * PropertyEvent -- + * + * Handle PropertyNotify events on wrapper windows. + * The following properties are of interest: + * + * _NET_WM_STATE: + * Used to keep wmPtr->attributes up to date. + * + *---------------------------------------------------------------------- + */ + +static void +PropertyEvent( + WmInfo *wmPtr, /* Information about toplevel window. */ + XPropertyEvent *eventPtr) /* PropertyNotify event structure */ +{ + TkWindow *wrapperPtr = wmPtr->wrapperPtr; + Atom _NET_WM_STATE = + Tk_InternAtom((Tk_Window)wmPtr->winPtr, "_NET_WM_STATE"); + + if (eventPtr->atom == _NET_WM_STATE) { + Atom actualType; + int actualFormat; + unsigned long numItems, bytesAfter; + unsigned char *propertyValue = 0; + long maxLength = 1024; + + if (XGetWindowProperty( + wrapperPtr->display, wrapperPtr->window, _NET_WM_STATE, + 0l, maxLength, False, XA_ATOM, + &actualType, &actualFormat, &numItems, &bytesAfter, + &propertyValue) == Success + ) { + CheckNetWmState(wmPtr, (Atom*)propertyValue, (int)numItems); + XFree(propertyValue); + } + } +} + +/* + *---------------------------------------------------------------------- + * * WrapperEventProc -- * * This function is invoked by the event loop when a wrapper window is @@ -3924,6 +4153,9 @@ ComputeReparentGeometry( *---------------------------------------------------------------------- */ +static const unsigned int WrapperEventMask = + (StructureNotifyMask | PropertyChangeMask); + static void WrapperEventProc( ClientData clientData, /* Information about toplevel window. */ @@ -3979,6 +4211,8 @@ WrapperEventProc( goto doMapEvent; } else if (eventPtr->type == ReparentNotify) { ReparentEvent(wmPtr, &eventPtr->xreparent); + } else if (eventPtr->type == PropertyNotify) { + PropertyEvent(wmPtr, &eventPtr->xproperty); } return; @@ -4512,6 +4746,128 @@ UpdatePhotoIcon( /* *---------------------------------------------------------------------- * + * SetNetWmState -- + * + * Sets the specified state property by sending a _NET_WM_STATE + * ClientMessage to the root window. + * + * Preconditions: + * + * Wrapper window must be created. + * + * See also: + * UpdateNetWmState; EWMH spec, section _NET_WM_STATE. + * + *---------------------------------------------------------------------- + */ + +#define _NET_WM_STATE_REMOVE 0l +#define _NET_WM_STATE_ADD 1l +#define _NET_WM_STATE_TOGGLE 2l + +static void SetNetWmState(TkWindow *winPtr, const char *atomName, int on) +{ + Tk_Window tkwin = (Tk_Window)winPtr; + Atom messageType = Tk_InternAtom(tkwin, "_NET_WM_STATE"); + Atom action = on ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + Atom property = Tk_InternAtom(tkwin, atomName); + XEvent e; + + if (!winPtr->wmInfoPtr->wrapperPtr) { + return; + } + + e.xany.type = ClientMessage; + e.xany.window = winPtr->wmInfoPtr->wrapperPtr->window; + e.xclient.message_type = messageType; + e.xclient.format = 32; + e.xclient.data.l[0] = action; + e.xclient.data.l[1] = property; + e.xclient.data.l[2] = e.xclient.data.l[3] = e.xclient.data.l[4] = 0l; + + XSendEvent(winPtr->display, + RootWindow(winPtr->display, winPtr->screenNum), 0, + SubstructureNotifyMask|SubstructureRedirectMask, &e); +} + +/* + * CheckNetWmState -- + * + * Updates the window attributes whenever the _NET_WM_STATE + * property changes. + * + * Notes: + * + * Tk uses a single -zoomed state, while the EWMH spec supports + * separate vertical and horizontal maximization. We consider + * the window to be "zoomed" if _NET_WM_STATE_MAXIMIZED_VERT + * and _NET_WM_STATE_MAXIMIZED_HORZ are both set. + */ +static void CheckNetWmState(WmInfo *wmPtr, Atom *atoms, int numAtoms) +{ + Tk_Window tkwin = (Tk_Window)wmPtr->wrapperPtr; + int i; + Atom _NET_WM_STATE_ABOVE + = Tk_InternAtom(tkwin, "_NET_WM_STATE_ABOVE"), + _NET_WM_STATE_MAXIMIZED_VERT + = Tk_InternAtom(tkwin, "_NET_WM_STATE_MAXIMIZED_VERT"), + _NET_WM_STATE_MAXIMIZED_HORZ + = Tk_InternAtom(tkwin, "_NET_WM_STATE_MAXIMIZED_HORZ"), + _NET_WM_STATE_FULLSCREEN + = Tk_InternAtom(tkwin, "_NET_WM_STATE_FULLSCREEN"); + + wmPtr->attributes.topmost = 0; + wmPtr->attributes.zoomed = 0; + wmPtr->attributes.fullscreen = 0; + for (i = 0; i < numAtoms; ++i) { + if (atoms[i] == _NET_WM_STATE_ABOVE) { + wmPtr->attributes.topmost = 1; + } else if (atoms[i] == _NET_WM_STATE_MAXIMIZED_VERT) { + wmPtr->attributes.zoomed |= 1; + } else if (atoms[i] == _NET_WM_STATE_MAXIMIZED_HORZ) { + wmPtr->attributes.zoomed |= 2; + } else if (atoms[i] == _NET_WM_STATE_FULLSCREEN) { + wmPtr->attributes.fullscreen = 1; + } + } + + wmPtr->attributes.zoomed = (wmPtr->attributes.zoomed == 3); + + return; +} + +/* + * UpdateNetWmState -- + * + * Sets the _NET_WM_STATE property to match the requested attribute state + * just prior to mapping a withdrawn window. + */ +#define NET_WM_STATE_MAX_ATOMS 4 +static void UpdateNetWmState(WmInfo *wmPtr) +{ + Tk_Window tkwin = (Tk_Window)wmPtr->wrapperPtr; + Atom atoms[NET_WM_STATE_MAX_ATOMS]; + long numAtoms = 0; + + if (wmPtr->reqState.topmost) { + atoms[numAtoms++] = Tk_InternAtom(tkwin,"_NET_WM_STATE_ABOVE"); + } + if (wmPtr->reqState.zoomed) { + atoms[numAtoms++] = Tk_InternAtom(tkwin,"_NET_WM_STATE_MAXIMIZED_VERT"); + atoms[numAtoms++] = Tk_InternAtom(tkwin,"_NET_WM_STATE_MAXIMIZED_HORZ"); + } + if (wmPtr->reqState.fullscreen) { + atoms[numAtoms++] = Tk_InternAtom(tkwin, "_NET_WM_STATE_FULLSCREEN"); + } + + XChangeProperty(Tk_Display(tkwin), wmPtr->wrapperPtr->window, + Tk_InternAtom(tkwin, "_NET_WM_STATE"), XA_ATOM, 32, + PropModeReplace, (unsigned char *)atoms, numAtoms); +} + +/* + *---------------------------------------------------------------------- + * * WaitForConfigureNotify -- * * This function is invoked in order to synchronize with the window @@ -6209,8 +6565,8 @@ CreateWrapper( * etc.. */ - Tk_CreateEventHandler((Tk_Window) wmPtr->wrapperPtr, StructureNotifyMask, - WrapperEventProc, (ClientData) wmPtr); + Tk_CreateEventHandler((Tk_Window) wmPtr->wrapperPtr, + WrapperEventMask, WrapperEventProc, (ClientData) wmPtr); } /* @@ -6567,11 +6923,3 @@ TkpWmSetState( return 1; } - -/* - * Local Variables: - * mode: c - * c-basic-offset: 4 - * fill-column: 78 - * End: - */ -- cgit v0.12