349 lines
13 KiB
Go
349 lines
13 KiB
Go
|
package xgbutil
|
||
|
|
||
|
import (
|
||
|
"log"
|
||
|
"os"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/jezek/xgb"
|
||
|
"github.com/jezek/xgb/xinerama"
|
||
|
"github.com/jezek/xgb/xproto"
|
||
|
)
|
||
|
|
||
|
// Logger is used through xgbutil when messages need to be emitted to stderr.
|
||
|
var Logger = log.New(os.Stderr, "[xgbutil] ", log.Lshortfile)
|
||
|
|
||
|
// The current maximum request size. I think we can expand this with
|
||
|
// BigReq, but it probably isn't worth it at the moment.
|
||
|
const MaxReqSize = (1 << 16) * 4
|
||
|
|
||
|
// An XUtil represents the state of xgbutil. It keeps track of the current
|
||
|
// X connection, the root window, event callbacks, key/mouse bindings, etc.
|
||
|
// Regrettably, many of the members are exported, even though they should not
|
||
|
// be used directly by the user. They are exported for use in sub-packages.
|
||
|
// (Namely, xevent, keybind and mousebind.) In fact, there should *never*
|
||
|
// be a reason to access any members of an XUtil value directly. Any
|
||
|
// interaction with an XUtil value should be through its methods.
|
||
|
type XUtil struct {
|
||
|
// conn is the XGB connection object used to issue protocol requests.
|
||
|
conn *xgb.Conn
|
||
|
|
||
|
// Quit can be set to true, and the main event loop will finish processing
|
||
|
// the current event, and gracefully quit afterwards.
|
||
|
// This is exported for use in the xevent package. Please us xevent.Quit
|
||
|
// to set this value.
|
||
|
Quit bool // when true, the main event loop will stop gracefully
|
||
|
|
||
|
// setup contains all the setup information retrieved at connection time.
|
||
|
setup *xproto.SetupInfo
|
||
|
|
||
|
// screen is a simple alias to the default screen info.
|
||
|
screen *xproto.ScreenInfo
|
||
|
|
||
|
// root is an alias to the default root window.
|
||
|
root xproto.Window
|
||
|
|
||
|
// Atoms is a cache of atom names to resource identifiers. This minimizes
|
||
|
// round trips to the X server, since atom identifiers never change.
|
||
|
// It is exported for use in the xprop package. It should not be used.
|
||
|
Atoms map[string]xproto.Atom
|
||
|
AtomsLck *sync.RWMutex
|
||
|
|
||
|
// AtomNames is a cache just like 'atoms', but in the reverse direction.
|
||
|
// It is exported for use in the xprop package. It should not be used.
|
||
|
AtomNames map[xproto.Atom]string
|
||
|
AtomNamesLck *sync.RWMutex
|
||
|
|
||
|
// Evqueue is the queue that stores the results of xgb.WaitForEvent.
|
||
|
// Namely, each value is either an Event *or* an Error.
|
||
|
// It is exported for use in the xevent package. Do not use it.
|
||
|
// If you need to interact with the event queue, please use the functions
|
||
|
// available in the xevent package: Dequeue, DequeueAt, QueueEmpty
|
||
|
// and QueuePeek.
|
||
|
Evqueue []EventOrError
|
||
|
EvqueueLck *sync.RWMutex
|
||
|
|
||
|
// Callbacks is a map of event numbers to a map of window identifiers
|
||
|
// to callback functions.
|
||
|
// This is the data structure that stores all callback functions, where
|
||
|
// a callback function is always attached to a (event, window) tuple.
|
||
|
// It is exported for use in the xevent package. Do not use it.
|
||
|
Callbacks map[int]map[xproto.Window][]Callback
|
||
|
CallbacksLck *sync.RWMutex
|
||
|
|
||
|
// Hooks are called by the XEvent main loop before processing the event
|
||
|
// itself. These are meant for instances when it's not possible / easy
|
||
|
// to use the normal Hook system. You should not modify this yourself.
|
||
|
Hooks []CallbackHook
|
||
|
HooksLck *sync.RWMutex
|
||
|
|
||
|
// eventTime is the last time recorded by an event. It is automatically
|
||
|
// updated if xgbutil's main event loop is used.
|
||
|
eventTime xproto.Timestamp
|
||
|
|
||
|
// Keymap corresponds to xgbutil's current conception of the keyboard
|
||
|
// mapping. It is automatically kept up-to-date if xgbutil's event loop
|
||
|
// is used.
|
||
|
// It is exported for use in the keybind package. It should not be
|
||
|
// accessed directly. Instead, use keybind.KeyMapGet.
|
||
|
Keymap *KeyboardMapping
|
||
|
|
||
|
// Modmap corresponds to xgbutil's current conception of the modifier key
|
||
|
// mapping. It is automatically kept up-to-date if xgbutil's event loop
|
||
|
// is used.
|
||
|
// It is exported for use in the keybind package. It should not be
|
||
|
// accessed directly. Instead, use keybind.ModMapGet.
|
||
|
Modmap *ModifierMapping
|
||
|
|
||
|
// KeyRedirect corresponds to a window identifier that, when set,
|
||
|
// automatically receives *all* keyboard events. This is a sort-of
|
||
|
// synthetic grab and is helpful in avoiding race conditions.
|
||
|
// It is exported for use in the xevent and keybind packages. Do not use
|
||
|
// it directly. To redirect key events, please use xevent.RedirectKeyEvents.
|
||
|
KeyRedirect xproto.Window
|
||
|
|
||
|
// Keybinds is the data structure storing all callbacks for key bindings.
|
||
|
// This is extremely similar to the general notion of event callbacks,
|
||
|
// but adds extra support to make handling key bindings easier. (Like
|
||
|
// specifying human readable key sequences to bind to.)
|
||
|
// KeyBindKey is a struct representing the 4-tuple
|
||
|
// (event-type, window-id, modifiers, keycode).
|
||
|
// It is exported for use in the keybind package. Do not access it directly.
|
||
|
Keybinds map[KeyKey][]CallbackKey
|
||
|
KeybindsLck *sync.RWMutex
|
||
|
|
||
|
// Keygrabs is a frequency count of the number of callbacks associated
|
||
|
// with a particular KeyBindKey. This is necessary because we can only
|
||
|
// grab a particular key *once*, but we may want to attach several callbacks
|
||
|
// to a single keypress.
|
||
|
// It is exported for use in the keybind package. Do not access it directly.
|
||
|
Keygrabs map[KeyKey]int
|
||
|
|
||
|
// Keystrings is a list of all key strings used to connect keybindings.
|
||
|
// They are used to rebuild key grabs when the keyboard mapping is updated.
|
||
|
// It is exported for use in the keybind package. Do not access it directly.
|
||
|
Keystrings []KeyString
|
||
|
|
||
|
// Mousebinds is the data structure storing all callbacks for mouse
|
||
|
// bindings.This is extremely similar to the general notion of event
|
||
|
// callbacks,but adds extra support to make handling mouse bindings easier.
|
||
|
// (Like specifying human readable mouse sequences to bind to.)
|
||
|
// MouseBindKey is a struct representing the 4-tuple
|
||
|
// (event-type, window-id, modifiers, button).
|
||
|
// It is exported for use in the mousebind package. Do not use it.
|
||
|
Mousebinds map[MouseKey][]CallbackMouse
|
||
|
MousebindsLck *sync.RWMutex
|
||
|
|
||
|
// Mousegrabs is a frequency count of the number of callbacks associated
|
||
|
// with a particular MouseBindKey. This is necessary because we can only
|
||
|
// grab a particular mouse button *once*, but we may want to attach
|
||
|
// several callbacks to a single button press.
|
||
|
// It is exported for use in the mousebind package. Do not use it.
|
||
|
Mousegrabs map[MouseKey]int
|
||
|
|
||
|
// InMouseDrag is true if a drag is currently in progress.
|
||
|
// It is exported for use in the mousebind package. Do not use it.
|
||
|
InMouseDrag bool
|
||
|
|
||
|
// MouseDragStep is the function executed for each step (i.e., pointer
|
||
|
// movement) in the current mouse drag. Note that this is nil when a drag
|
||
|
// is not in progress.
|
||
|
// It is exported for use in the mousebind package. Do not use it.
|
||
|
MouseDragStepFun MouseDragFun
|
||
|
|
||
|
// MouseDragEnd is the function executed at the end of the current
|
||
|
// mouse drag. This is nil when a drag is not in progress.
|
||
|
// It is exported for use in the mousebind package. Do not use it.
|
||
|
MouseDragEndFun MouseDragFun
|
||
|
|
||
|
// gc is a general purpose graphics context; used to paint images.
|
||
|
// Since we don't do any real X drawing, we don't really care about the
|
||
|
// particulars of our graphics context.
|
||
|
gc xproto.Gcontext
|
||
|
|
||
|
// dummy is a dummy window used for mouse/key GRABs.
|
||
|
// Basically, whenever a grab is instituted, mouse and key events are
|
||
|
// redirected to the dummy the window.
|
||
|
dummy xproto.Window
|
||
|
|
||
|
// ErrorHandler is the function that handles errors *in the event loop*.
|
||
|
// By default, it simply emits them to stderr.
|
||
|
// It is exported for use in the xevent package. To set the default error
|
||
|
// handler, please use xevent.ErrorHandlerSet.
|
||
|
ErrorHandler ErrorHandlerFun
|
||
|
}
|
||
|
|
||
|
// NewConn connects to the X server using the DISPLAY environment variable
|
||
|
// and creates a new XUtil. Most environments have the DISPLAY environment
|
||
|
// variable set, so this is probably what you want to use to connect to X.
|
||
|
func NewConn() (*XUtil, error) {
|
||
|
return NewConnDisplay("")
|
||
|
}
|
||
|
|
||
|
// NewConnDisplay connects to the X server and creates a new XUtil.
|
||
|
// If 'display' is empty, the DISPLAY environment variable is used. Otherwise
|
||
|
// there are several different display formats supported:
|
||
|
//
|
||
|
// NewConn(":1") -> net.Dial("unix", "", "/tmp/.X11-unix/X1")
|
||
|
// NewConn("/tmp/launch-12/:0") -> net.Dial("unix", "", "/tmp/launch-12/:0")
|
||
|
// NewConn("hostname:2.1") -> net.Dial("tcp", "", "hostname:6002")
|
||
|
// NewConn("tcp/hostname:1.0") -> net.Dial("tcp", "", "hostname:6001")
|
||
|
func NewConnDisplay(display string) (*XUtil, error) {
|
||
|
c, err := xgb.NewConnDisplay(display)
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return NewConnXgb(c)
|
||
|
|
||
|
}
|
||
|
|
||
|
// NewConnXgb use the specific xgb.Conn to create a new XUtil.
|
||
|
//
|
||
|
// NewConn, NewConnDisplay are wrapper of this function.
|
||
|
func NewConnXgb(c *xgb.Conn) (*XUtil, error) {
|
||
|
setup := xproto.Setup(c)
|
||
|
screen := setup.DefaultScreen(c)
|
||
|
|
||
|
// Initialize our central struct that stores everything.
|
||
|
xu := &XUtil{
|
||
|
conn: c,
|
||
|
Quit: false,
|
||
|
Evqueue: make([]EventOrError, 0, 1000),
|
||
|
EvqueueLck: &sync.RWMutex{},
|
||
|
setup: setup,
|
||
|
screen: screen,
|
||
|
root: screen.Root,
|
||
|
eventTime: xproto.Timestamp(0), // last event time
|
||
|
Atoms: make(map[string]xproto.Atom, 50),
|
||
|
AtomsLck: &sync.RWMutex{},
|
||
|
AtomNames: make(map[xproto.Atom]string, 50),
|
||
|
AtomNamesLck: &sync.RWMutex{},
|
||
|
Callbacks: make(map[int]map[xproto.Window][]Callback, 33),
|
||
|
CallbacksLck: &sync.RWMutex{},
|
||
|
Hooks: make([]CallbackHook, 0),
|
||
|
HooksLck: &sync.RWMutex{},
|
||
|
Keymap: nil, // we don't have anything yet
|
||
|
Modmap: nil,
|
||
|
KeyRedirect: 0,
|
||
|
Keybinds: make(map[KeyKey][]CallbackKey, 10),
|
||
|
KeybindsLck: &sync.RWMutex{},
|
||
|
Keygrabs: make(map[KeyKey]int, 10),
|
||
|
Keystrings: make([]KeyString, 0, 10),
|
||
|
Mousebinds: make(map[MouseKey][]CallbackMouse, 10),
|
||
|
MousebindsLck: &sync.RWMutex{},
|
||
|
Mousegrabs: make(map[MouseKey]int, 10),
|
||
|
InMouseDrag: false,
|
||
|
MouseDragStepFun: nil,
|
||
|
MouseDragEndFun: nil,
|
||
|
ErrorHandler: func(err xgb.Error) { Logger.Println(err) },
|
||
|
}
|
||
|
|
||
|
var err error = nil
|
||
|
// Create a general purpose graphics context
|
||
|
xu.gc, err = xproto.NewGcontextId(xu.conn)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
xproto.CreateGC(xu.conn, xu.gc, xproto.Drawable(xu.root),
|
||
|
xproto.GcForeground, []uint32{xu.screen.WhitePixel})
|
||
|
|
||
|
// Create a dummy window
|
||
|
xu.dummy, err = xproto.NewWindowId(xu.conn)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
xproto.CreateWindow(xu.conn, xu.Screen().RootDepth, xu.dummy, xu.RootWin(),
|
||
|
-1000, -1000, 1, 1, 0,
|
||
|
xproto.WindowClassInputOutput, xu.Screen().RootVisual,
|
||
|
xproto.CwEventMask|xproto.CwOverrideRedirect,
|
||
|
[]uint32{1, xproto.EventMaskPropertyChange})
|
||
|
xproto.MapWindow(xu.conn, xu.dummy)
|
||
|
|
||
|
// Register the Xinerama extension... because it doesn't cost much.
|
||
|
err = xinerama.Init(xu.conn)
|
||
|
|
||
|
// If we can't register Xinerama, that's okay. Output something
|
||
|
// and move on.
|
||
|
if err != nil {
|
||
|
Logger.Printf("WARNING: %s\n", err)
|
||
|
Logger.Printf("MESSAGE: The 'xinerama' package cannot be used " +
|
||
|
"because the XINERAMA extension could not be loaded.")
|
||
|
}
|
||
|
|
||
|
return xu, nil
|
||
|
}
|
||
|
|
||
|
// Conn returns the xgb connection object.
|
||
|
func (xu *XUtil) Conn() *xgb.Conn {
|
||
|
return xu.conn
|
||
|
}
|
||
|
|
||
|
// ExtInitialized returns true if an extension has been initialized.
|
||
|
// This is useful for determining whether an extension is available or not.
|
||
|
func (xu *XUtil) ExtInitialized(extName string) bool {
|
||
|
_, ok := xu.Conn().Extensions[extName]
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
// Sync forces XGB to catch up with all events/requests and synchronize.
|
||
|
// This is done by issuing a benign round trip request to X.
|
||
|
func (xu *XUtil) Sync() {
|
||
|
xproto.GetInputFocus(xu.Conn()).Reply()
|
||
|
}
|
||
|
|
||
|
// Setup returns the setup information retrieved during connection time.
|
||
|
func (xu *XUtil) Setup() *xproto.SetupInfo {
|
||
|
return xu.setup
|
||
|
}
|
||
|
|
||
|
// Screen returns the default screen
|
||
|
func (xu *XUtil) Screen() *xproto.ScreenInfo {
|
||
|
return xu.screen
|
||
|
}
|
||
|
|
||
|
// RootWin returns the current root window.
|
||
|
func (xu *XUtil) RootWin() xproto.Window {
|
||
|
return xu.root
|
||
|
}
|
||
|
|
||
|
// RootWinSet will change the current root window to the one provided.
|
||
|
// N.B. This probably shouldn't be used unless you're desperately trying
|
||
|
// to support multiple X screens. (This is *not* the same as Xinerama/RandR or
|
||
|
// TwinView. All of those have a single root window.)
|
||
|
func (xu *XUtil) RootWinSet(root xproto.Window) {
|
||
|
xu.root = root
|
||
|
}
|
||
|
|
||
|
// TimeGet gets the most recent time seen by an event.
|
||
|
func (xu *XUtil) TimeGet() xproto.Timestamp {
|
||
|
return xu.eventTime
|
||
|
}
|
||
|
|
||
|
// TimeSet sets the most recent time seen by an event.
|
||
|
func (xu *XUtil) TimeSet(t xproto.Timestamp) {
|
||
|
xu.eventTime = t
|
||
|
}
|
||
|
|
||
|
// GC gets a general purpose graphics context that is typically used to simply
|
||
|
// paint images.
|
||
|
func (xu *XUtil) GC() xproto.Gcontext {
|
||
|
return xu.gc
|
||
|
}
|
||
|
|
||
|
// Dummy gets the id of the dummy window.
|
||
|
func (xu *XUtil) Dummy() xproto.Window {
|
||
|
return xu.dummy
|
||
|
}
|
||
|
|
||
|
// Grabs the server. Everything becomes synchronous.
|
||
|
func (xu *XUtil) Grab() {
|
||
|
xproto.GrabServer(xu.Conn())
|
||
|
}
|
||
|
|
||
|
// Ungrabs the server.
|
||
|
func (xu *XUtil) Ungrab() {
|
||
|
xproto.UngrabServer(xu.Conn())
|
||
|
}
|