394 lines
13 KiB
Go
394 lines
13 KiB
Go
|
package xwindow
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/jezek/xgb/xproto"
|
||
|
|
||
|
"github.com/jezek/xgbutil"
|
||
|
"github.com/jezek/xgbutil/keybind"
|
||
|
"github.com/jezek/xgbutil/mousebind"
|
||
|
"github.com/jezek/xgbutil/xevent"
|
||
|
"github.com/jezek/xgbutil/xrect"
|
||
|
)
|
||
|
|
||
|
// Window represents an X window. It contains an XUtilValue to simplfy the
|
||
|
// parameter lists for methods declared on the Window type.
|
||
|
// Geom is updated whenever Geometry is called, or when Move, Resize or
|
||
|
// MoveResize are called.
|
||
|
type Window struct {
|
||
|
X *xgbutil.XUtil
|
||
|
Id xproto.Window
|
||
|
Geom xrect.Rect
|
||
|
Destroyed bool
|
||
|
}
|
||
|
|
||
|
// New creates a new window value from a window id and an XUtil type.
|
||
|
// Geom is initialized to zero values. Use Window.Geometry to load it.
|
||
|
// Note that the geometry is the size of this particular window and nothing
|
||
|
// else. If you want the geometry of a client window including decorations,
|
||
|
// please use Window.DecorGeometry.
|
||
|
func New(xu *xgbutil.XUtil, win xproto.Window) *Window {
|
||
|
return &Window{
|
||
|
X: xu,
|
||
|
Id: win,
|
||
|
Geom: xrect.New(0, 0, 1, 1),
|
||
|
Destroyed: false,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Generate is just like New, but generates a new X resource id for you.
|
||
|
// Geom is initialized to (0, 0) 1x1.
|
||
|
// It is possible for id generation to return an error, in which case, an
|
||
|
// error is returned here.
|
||
|
func Generate(xu *xgbutil.XUtil) (*Window, error) {
|
||
|
wid, err := xproto.NewWindowId(xu.Conn())
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return New(xu, wid), nil
|
||
|
}
|
||
|
|
||
|
// Create is a convenience constructor that will generate a new window id (with
|
||
|
// the Generate constructor) and make a bare-bones call to CreateChecked (with
|
||
|
// geometry (0, 0) 1x1). An error can be generated from Generate or
|
||
|
// CreateChecked.
|
||
|
func Create(xu *xgbutil.XUtil, parent xproto.Window) (*Window, error) {
|
||
|
win, err := Generate(xu)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
err = win.CreateChecked(parent, 0, 0, 1, 1, 0)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return win, nil
|
||
|
}
|
||
|
|
||
|
// Must panics if err is non-nil or if win is nil. Otherwise, win is returned.
|
||
|
func Must(win *Window, err error) *Window {
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
if win == nil {
|
||
|
panic("win and err are nil")
|
||
|
}
|
||
|
return win
|
||
|
}
|
||
|
|
||
|
// Create issues a CreateWindow request for Window.
|
||
|
// Its purpose is to omit several boiler-plate parameters to CreateWindow
|
||
|
// and expose the commonly useful ones.
|
||
|
// The value mask describes which values are present in valueList.
|
||
|
// Value masks can be found in xgb/xproto with the prefix 'Cw'.
|
||
|
// The value list must contain values in the same order as the constants
|
||
|
// are defined in xgb/xproto.
|
||
|
//
|
||
|
// For example, the following creates a window positioned at (20, 50) with
|
||
|
// width 500 and height 700 with a background color of white.
|
||
|
//
|
||
|
// w, err := xwindow.Generate(X)
|
||
|
// if err != nil {
|
||
|
// log.Fatalf("Could not generate a new resource identifier: %s", err)
|
||
|
// }
|
||
|
// w.Create(X.RootWin(), 20, 50, 500, 700,
|
||
|
// xproto.CwBackPixel, 0xffffff)
|
||
|
func (w *Window) Create(parent xproto.Window, x, y, width, height,
|
||
|
valueMask int, valueList ...uint32) {
|
||
|
|
||
|
s := w.X.Screen()
|
||
|
xproto.CreateWindow(w.X.Conn(),
|
||
|
xproto.WindowClassCopyFromParent, w.Id, parent,
|
||
|
int16(x), int16(y), uint16(width), uint16(height), 0,
|
||
|
xproto.WindowClassInputOutput, s.RootVisual,
|
||
|
uint32(valueMask), valueList)
|
||
|
}
|
||
|
|
||
|
// CreateChecked issues a CreateWindow checked request for Window.
|
||
|
// A checked request is a synchronous request. Meaning that if the request
|
||
|
// fails, you can get the error returned to you. However, it also forced your
|
||
|
// program to block for a round trip to the X server, so it is slower.
|
||
|
// See the docs for Create for more info.
|
||
|
func (w *Window) CreateChecked(parent xproto.Window, x, y, width, height,
|
||
|
valueMask int, valueList ...uint32) error {
|
||
|
|
||
|
s := w.X.Screen()
|
||
|
return xproto.CreateWindowChecked(w.X.Conn(),
|
||
|
s.RootDepth, w.Id, parent,
|
||
|
int16(x), int16(y), uint16(width), uint16(height), 0,
|
||
|
xproto.WindowClassInputOutput, s.RootVisual,
|
||
|
uint32(valueMask), valueList).Check()
|
||
|
}
|
||
|
|
||
|
// Change issues a ChangeWindowAttributes request with the provided mask
|
||
|
// and value list. Please see Window.Create for an example on how to use
|
||
|
// the mask and value list.
|
||
|
func (w *Window) Change(valueMask int, valueList ...uint32) {
|
||
|
xproto.ChangeWindowAttributes(w.X.Conn(), w.Id,
|
||
|
uint32(valueMask), valueList)
|
||
|
}
|
||
|
|
||
|
// Listen will tell X to report events corresponding to the event masks
|
||
|
// provided for the given window. If a call to Listen is omitted, you will
|
||
|
// not receive the events you desire.
|
||
|
// Event masks are constants declare in the xgb/xproto package starting with the
|
||
|
// EventMask prefix.
|
||
|
func (w *Window) Listen(evMasks ...int) error {
|
||
|
evMask := 0
|
||
|
for _, mask := range evMasks {
|
||
|
evMask |= mask
|
||
|
}
|
||
|
return xproto.ChangeWindowAttributesChecked(w.X.Conn(), w.Id,
|
||
|
xproto.CwEventMask, []uint32{uint32(evMask)}).Check()
|
||
|
}
|
||
|
|
||
|
// Geometry retrieves an up-to-date version of the this window's geometry.
|
||
|
// It also loads the geometry into the Geom member of Window.
|
||
|
func (w *Window) Geometry() (xrect.Rect, error) {
|
||
|
geom, err := RawGeometry(w.X, xproto.Drawable(w.Id))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
w.Geom = geom
|
||
|
return geom, err
|
||
|
}
|
||
|
|
||
|
// RawGeometry isn't smart. It just queries the window given for geometry.
|
||
|
func RawGeometry(xu *xgbutil.XUtil, win xproto.Drawable) (xrect.Rect, error) {
|
||
|
xgeom, err := xproto.GetGeometry(xu.Conn(), win).Reply()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return xrect.New(int(xgeom.X), int(xgeom.Y),
|
||
|
int(xgeom.Width), int(xgeom.Height)), nil
|
||
|
}
|
||
|
|
||
|
// RootGeometry gets the geometry of the root window. It will panic on failure.
|
||
|
func RootGeometry(xu *xgbutil.XUtil) xrect.Rect {
|
||
|
geom, err := RawGeometry(xu, xproto.Drawable(xu.RootWin()))
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return geom
|
||
|
}
|
||
|
|
||
|
// Configure issues a raw Configure request with the parameters given and
|
||
|
// updates the geometry of the window.
|
||
|
// This should probably only be used when passing along ConfigureNotify events
|
||
|
// (from the perspective of the window manager). In other cases, one should
|
||
|
// opt for [WM][Move][Resize] or Stack[Sibling].
|
||
|
func (win *Window) Configure(flags, x, y, w, h int,
|
||
|
sibling xproto.Window, stackMode byte) {
|
||
|
|
||
|
if win == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
vals := []uint32{}
|
||
|
|
||
|
if xproto.ConfigWindowX&flags > 0 {
|
||
|
vals = append(vals, uint32(x))
|
||
|
win.Geom.XSet(x)
|
||
|
}
|
||
|
if xproto.ConfigWindowY&flags > 0 {
|
||
|
vals = append(vals, uint32(y))
|
||
|
win.Geom.YSet(y)
|
||
|
}
|
||
|
if xproto.ConfigWindowWidth&flags > 0 {
|
||
|
if int16(w) <= 0 {
|
||
|
w = 1
|
||
|
}
|
||
|
vals = append(vals, uint32(w))
|
||
|
win.Geom.WidthSet(w)
|
||
|
}
|
||
|
if xproto.ConfigWindowHeight&flags > 0 {
|
||
|
if int16(h) <= 0 {
|
||
|
h = 1
|
||
|
}
|
||
|
vals = append(vals, uint32(h))
|
||
|
win.Geom.HeightSet(h)
|
||
|
}
|
||
|
if xproto.ConfigWindowSibling&flags > 0 {
|
||
|
vals = append(vals, uint32(sibling))
|
||
|
}
|
||
|
if xproto.ConfigWindowStackMode&flags > 0 {
|
||
|
vals = append(vals, uint32(stackMode))
|
||
|
}
|
||
|
|
||
|
// Nobody should be setting border widths any more.
|
||
|
// We toss it out since `vals` must have length equal to the number
|
||
|
// of bits set in `flags`.
|
||
|
flags &= ^xproto.ConfigWindowBorderWidth
|
||
|
xproto.ConfigureWindow(win.X.Conn(), win.Id, uint16(flags), vals)
|
||
|
}
|
||
|
|
||
|
// MROpt is like MoveResize, but exposes the X value mask so that any
|
||
|
// combination of x/y/width/height can be set. It's a strictly convenience
|
||
|
// function. (i.e., when you need to set 'y' and 'height' but not 'x' or
|
||
|
// 'width'.)
|
||
|
func (w *Window) MROpt(flags, x, y, width, height int) {
|
||
|
// Make sure only x/y/width/height are used.
|
||
|
flags &= xproto.ConfigWindowX | xproto.ConfigWindowY |
|
||
|
xproto.ConfigWindowWidth | xproto.ConfigWindowHeight
|
||
|
w.Configure(flags, x, y, width, height, 0, 0)
|
||
|
}
|
||
|
|
||
|
// MoveResize issues a ConfigureRequest for this window with the provided
|
||
|
// x, y, width and height. Note that if width or height is 0, X will stomp
|
||
|
// all over you. Really hard. Don't do it.
|
||
|
// If you're trying to move/resize a top-level window in a window manager that
|
||
|
// supports EWMH, please use WMMoveResize instead.
|
||
|
func (w *Window) MoveResize(x, y, width, height int) {
|
||
|
w.Configure(xproto.ConfigWindowX|xproto.ConfigWindowY|
|
||
|
xproto.ConfigWindowWidth|xproto.ConfigWindowHeight, x, y, width, height,
|
||
|
0, 0)
|
||
|
}
|
||
|
|
||
|
// Move issues a ConfigureRequest for this window with the provided
|
||
|
// x and y positions.
|
||
|
// If you're trying to move a top-level window in a window manager that
|
||
|
// supports EWMH, please use WMMove instead.
|
||
|
func (w *Window) Move(x, y int) {
|
||
|
w.Configure(xproto.ConfigWindowX|xproto.ConfigWindowY, x, y, 0, 0, 0, 0)
|
||
|
}
|
||
|
|
||
|
// Resize issues a ConfigureRequest for this window with the provided
|
||
|
// width and height. Note that if width or height is 0, X will stomp
|
||
|
// all over you. Really hard. Don't do it.
|
||
|
// If you're trying to resize a top-level window in a window manager that
|
||
|
// supports EWMH, please use WMResize instead.
|
||
|
func (w *Window) Resize(width, height int) {
|
||
|
w.Configure(xproto.ConfigWindowWidth|xproto.ConfigWindowHeight, 0, 0,
|
||
|
width, height, 0, 0)
|
||
|
}
|
||
|
|
||
|
// Stack issues a configure request to change the stack mode of Window.
|
||
|
// If you're using a window manager that supports EWMH, you may want to try
|
||
|
// and use ewmh.RestackWindow instead. Although this should still work.
|
||
|
// 'mode' values can be found as constants in xgb/xproto with the prefix
|
||
|
// StackMode.
|
||
|
// A value of xproto.StackModeAbove will put the window to the top of the stack,
|
||
|
// while a value of xproto.StackMoveBelow will put the window to the
|
||
|
// bottom of the stack.
|
||
|
// Remember that stacking is at the discretion of the window manager, and
|
||
|
// therefore may not always work as one would expect.
|
||
|
func (w *Window) Stack(mode byte) {
|
||
|
xproto.ConfigureWindow(w.X.Conn(), w.Id,
|
||
|
xproto.ConfigWindowStackMode, []uint32{uint32(mode)})
|
||
|
}
|
||
|
|
||
|
// StackSibling issues a configure request to change the sibling and stack mode
|
||
|
// of Window.
|
||
|
// If you're using a window manager that supports EWMH, you may want to try
|
||
|
// and use ewmh.RestackWindowExtra instead. Although this should still work.
|
||
|
// 'mode' values can be found as constants in xgb/xproto with the prefix
|
||
|
// StackMode.
|
||
|
// 'sibling' refers to the sibling window in the stacking order through which
|
||
|
// 'mode' is interpreted. Note that 'sibling' should be taken literally. A
|
||
|
// window can only be stacked with respect to a *sibling* in the window tree.
|
||
|
// This means that a client window that has been wrapped in decorations cannot
|
||
|
// be stacked with respect to another client window. (This is why you should
|
||
|
// use ewmh.RestackWindowExtra instead.)
|
||
|
func (w *Window) StackSibling(sibling xproto.Window, mode byte) {
|
||
|
xproto.ConfigureWindow(w.X.Conn(), w.Id,
|
||
|
xproto.ConfigWindowSibling|xproto.ConfigWindowStackMode,
|
||
|
[]uint32{uint32(sibling), uint32(mode)})
|
||
|
}
|
||
|
|
||
|
// Map is a simple alias to map the window.
|
||
|
func (w *Window) Map() {
|
||
|
if w == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
xproto.MapWindow(w.X.Conn(), w.Id)
|
||
|
}
|
||
|
|
||
|
// Unmap is a simple alias to unmap the window.
|
||
|
func (w *Window) Unmap() {
|
||
|
if w == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
xproto.UnmapWindow(w.X.Conn(), w.Id)
|
||
|
}
|
||
|
|
||
|
// Destroy is a simple alias to destroy a window. You should use this when
|
||
|
// you no longer intend to use this window. (It will free the X resource
|
||
|
// identifier for use in other places.)
|
||
|
func (w *Window) Destroy() {
|
||
|
if !w.Destroyed {
|
||
|
w.Detach()
|
||
|
err := xproto.DestroyWindowChecked(w.X.Conn(), w.Id).Check()
|
||
|
if err != nil {
|
||
|
xgbutil.Logger.Println(err)
|
||
|
}
|
||
|
|
||
|
w.Destroyed = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Detach will detach this window's event handlers from all xevent, keybind
|
||
|
// and mousebind callbacks.
|
||
|
func (w *Window) Detach() {
|
||
|
keybind.Detach(w.X, w.Id)
|
||
|
mousebind.Detach(w.X, w.Id)
|
||
|
xevent.Detach(w.X, w.Id)
|
||
|
}
|
||
|
|
||
|
// Focus tries to issue a SetInputFocus to get the focus.
|
||
|
// If you're trying to change the top-level active window, please use
|
||
|
// ewmh.ActiveWindowReq instead.
|
||
|
func (w *Window) Focus() {
|
||
|
mode := byte(xproto.InputFocusPointerRoot)
|
||
|
err := xproto.SetInputFocusChecked(w.X.Conn(), mode, w.Id, 0).Check()
|
||
|
if err != nil {
|
||
|
xgbutil.Logger.Println(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FocusParent is just like Focus, except it sets the "revert-to" mode to
|
||
|
// Parent. This should be used when setting focus to a sub-window.
|
||
|
func (w *Window) FocusParent(tstamp xproto.Timestamp) {
|
||
|
mode := byte(xproto.InputFocusParent)
|
||
|
err := xproto.SetInputFocusChecked(w.X.Conn(), mode, w.Id, tstamp).Check()
|
||
|
if err != nil {
|
||
|
xgbutil.Logger.Println(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Kill forcefully destroys a client. It is almost never what you want, and if
|
||
|
// you do it to one your clients, you'll lose your connection.
|
||
|
// (This is typically used in a special client like `xkill` or in a window
|
||
|
// manager.)
|
||
|
func (w *Window) Kill() {
|
||
|
xproto.KillClient(w.X.Conn(), uint32(w.Id))
|
||
|
}
|
||
|
|
||
|
// Clear paints the region of the window specified with the corresponding
|
||
|
// background pixmap. If the window doesn't have a background pixmap,
|
||
|
// this has no effect.
|
||
|
// If width/height is 0, then it is set to the width/height of the background
|
||
|
// pixmap minus x/y.
|
||
|
func (w *Window) Clear(x, y, width, height int) {
|
||
|
xproto.ClearArea(w.X.Conn(), false, w.Id,
|
||
|
int16(x), int16(y), uint16(width), uint16(height))
|
||
|
}
|
||
|
|
||
|
// ClearAll is the same as Clear, but does it for the entire background pixmap.
|
||
|
func (w *Window) ClearAll() {
|
||
|
xproto.ClearArea(w.X.Conn(), false, w.Id, 0, 0, 0, 0)
|
||
|
}
|
||
|
|
||
|
// Parent queries the QueryTree and finds the parent window.
|
||
|
func (w *Window) Parent() (*Window, error) {
|
||
|
tree, err := xproto.QueryTree(w.X.Conn(), w.Id).Reply()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("ParentWindow: Error retrieving parent window "+
|
||
|
"for %x: %s", w.Id, err)
|
||
|
}
|
||
|
return New(w.X, tree.Parent), nil
|
||
|
}
|