wm/vend/xgbutil/xevent/xevent.go

238 lines
7.8 KiB
Go
Raw Normal View History

2023-06-11 14:21:08 +00:00
package xevent
import (
"github.com/jezek/xgb"
"github.com/jezek/xgb/xproto"
"github.com/jezek/xgbutil"
)
// Sometimes we need to specify NO WINDOW when a window is typically
// expected. (Like connecting to MappingNotify or KeymapNotify events.)
// Use this value to do that.
var NoWindow xproto.Window = 0
// IgnoreMods is a list of X modifiers that we don't want interfering
// with our mouse or key bindings. In particular, for each mouse or key binding
// issued, there is a seperate mouse or key binding made for each of the
// modifiers specified.
//
// You may modify this slice to add (or remove) modifiers, but it should be
// done before *any* key or mouse bindings are attached with the keybind and
// mousebind packages. It should not be modified afterwards.
//
// TODO: We're assuming numlock is in the 'mod2' modifier, which is a pretty
// common setup, but by no means guaranteed. This should be modified to actually
// inspect the modifiers table and look for the special Num_Lock keysym.
var IgnoreMods []uint16 = []uint16{
0,
xproto.ModMaskLock, // Caps lock
xproto.ModMask2, // Num lock
xproto.ModMaskLock | xproto.ModMask2, // Caps and Num lock
}
// Enqueue queues up an event read from X.
// Note that an event read may return an error, in which case, this queue
// entry will be an error and not an event.
//
// ev, err := XUtilValue.Conn().WaitForEvent()
// xevent.Enqueue(XUtilValue, ev, err)
//
// You probably shouldn't have to enqueue events yourself. This is done
// automatically if you're using xevent.Main{Ping} and/or xevent.Read.
func Enqueue(xu *xgbutil.XUtil, ev xgb.Event, err xgb.Error) {
xu.EvqueueLck.Lock()
defer xu.EvqueueLck.Unlock()
xu.Evqueue = append(xu.Evqueue, xgbutil.EventOrError{
Event: ev,
Err: err,
})
}
// Dequeue pops an event/error from the queue and returns it.
// The queue item is unwrapped and returned as multiple return values.
// Only one of the return values can be nil.
func Dequeue(xu *xgbutil.XUtil) (xgb.Event, xgb.Error) {
xu.EvqueueLck.Lock()
defer xu.EvqueueLck.Unlock()
everr := xu.Evqueue[0]
xu.Evqueue = xu.Evqueue[1:]
return everr.Event, everr.Err
}
// DequeueAt removes a particular item from the queue.
// This is primarily useful when attempting to compress events.
func DequeueAt(xu *xgbutil.XUtil, i int) {
xu.EvqueueLck.Lock()
defer xu.EvqueueLck.Unlock()
xu.Evqueue = append(xu.Evqueue[:i], xu.Evqueue[i+1:]...)
}
// Empty returns whether the event queue is empty or not.
func Empty(xu *xgbutil.XUtil) bool {
xu.EvqueueLck.RLock()
defer xu.EvqueueLck.RUnlock()
return len(xu.Evqueue) == 0
}
// Peek returns a *copy* of the current queue so we can examine it.
// This can be useful when trying to determine if a particular kind of
// event will be processed in the future.
func Peek(xu *xgbutil.XUtil) []xgbutil.EventOrError {
xu.EvqueueLck.RLock()
defer xu.EvqueueLck.RUnlock()
cpy := make([]xgbutil.EventOrError, len(xu.Evqueue))
copy(cpy, xu.Evqueue)
return cpy
}
// ErrorHandlerSet sets the default error handler for errors that come
// into the main event loop. (This may be removed in the future in favor
// of a particular callback interface like events, but these sorts of errors
// aren't handled often in practice, so maybe not.)
// This is only called for errors returned from unchecked (asynchronous error
// handling) requests.
// The default error handler just emits them to stderr.
func ErrorHandlerSet(xu *xgbutil.XUtil, fun xgbutil.ErrorHandlerFun) {
xu.ErrorHandler = fun
}
// ErrorHandlerGet retrieves the default error handler.
func ErrorHandlerGet(xu *xgbutil.XUtil) xgbutil.ErrorHandlerFun {
return xu.ErrorHandler
}
type HookFun func(xu *xgbutil.XUtil, event interface{}) bool
func (callback HookFun) Connect(xu *xgbutil.XUtil) {
xu.HooksLck.Lock()
defer xu.HooksLck.Unlock()
// COW
newHooks := make([]xgbutil.CallbackHook, len(xu.Hooks))
copy(newHooks, xu.Hooks)
newHooks = append(newHooks, callback)
xu.Hooks = newHooks
}
func (callback HookFun) Run(xu *xgbutil.XUtil, event interface{}) bool {
return callback(xu, event)
}
func getHooks(xu *xgbutil.XUtil) []xgbutil.CallbackHook {
xu.HooksLck.RLock()
defer xu.HooksLck.RUnlock()
return xu.Hooks
}
// RedirectKeyEvents, when set to a window id (greater than 0), will force
// *all* Key{Press,Release} to callbacks attached to the specified window.
// This is close to emulating a Keyboard grab without the racing.
// To stop redirecting key events, use window identifier '0'.
func RedirectKeyEvents(xu *xgbutil.XUtil, wid xproto.Window) {
xu.KeyRedirect = wid
}
// RedirectKeyGet gets the window that key events are being redirected to.
// If 0, then no redirection occurs.
func RedirectKeyGet(xu *xgbutil.XUtil) xproto.Window {
return xu.KeyRedirect
}
// Quit elegantly exits out of the main event loop.
// "Elegantly" in this case means that it finishes processing the current
// event, and breaks out of the loop afterwards.
// There is no particular reason to use this instead of something like os.Exit
// other than you might have code to run after the main event loop exits to
// "clean up."
func Quit(xu *xgbutil.XUtil) {
xu.Quit = true
}
// Quitting returns whether it's time to quit.
// This is only used in the main event loop in xevent.
func Quitting(xu *xgbutil.XUtil) bool {
return xu.Quit
}
// attachCallback associates a (event, window) tuple with an event.
// Use copy on write since we run callbacks *a lot* more than attaching them.
// (The copy on write only applies to the slice of callbacks rather than
// the map itself, since the initial allocation is guaranteed to come before
// any use of it.)
func attachCallback(xu *xgbutil.XUtil, evtype int, win xproto.Window,
fun xgbutil.Callback) {
xu.CallbacksLck.Lock()
defer xu.CallbacksLck.Unlock()
if _, ok := xu.Callbacks[evtype]; !ok {
xu.Callbacks[evtype] = make(map[xproto.Window][]xgbutil.Callback, 20)
}
if _, ok := xu.Callbacks[evtype][win]; !ok {
xu.Callbacks[evtype][win] = make([]xgbutil.Callback, 0)
}
// COW
newCallbacks := make([]xgbutil.Callback, len(xu.Callbacks[evtype][win]))
copy(newCallbacks, xu.Callbacks[evtype][win])
newCallbacks = append(newCallbacks, fun)
xu.Callbacks[evtype][win] = newCallbacks
}
// runCallbacks executes every callback corresponding to a
// particular event/window tuple.
func runCallbacks(xu *xgbutil.XUtil, event interface{}, evtype int,
win xproto.Window) {
// The callback slice for a particular (event type, window) tuple uses
// copy on write. So just take a pointer to whatever is there and use that.
// We can be sure that the slice won't change from underneathe us.
xu.CallbacksLck.RLock()
cbs := xu.Callbacks[evtype][win]
xu.CallbacksLck.RUnlock()
for _, cb := range cbs {
cb.Run(xu, event)
}
}
// Detach removes all callbacks associated with a particular window.
// Note that if you're also using the keybind and mousebind packages, a complete
// detachment should look like:
//
// keybind.Detach(XUtilValue, window-id)
// mousebind.Detach(XUtilValue, window-id)
// xevent.Detach(XUtilValue, window-id)
//
// If a window is no longer receiving events, these methods should be called.
// Otherwise, the memory used to store the handler info for that window will
// never be released.
func Detach(xu *xgbutil.XUtil, win xproto.Window) {
xu.CallbacksLck.Lock()
defer xu.CallbacksLck.Unlock()
for evtype, _ := range xu.Callbacks {
delete(xu.Callbacks[evtype], win)
}
}
// SendRootEvent takes a type implementing the xgb.Event interface, converts it
// to raw X bytes, and sends it to the root window using the SendEvent request.
func SendRootEvent(xu *xgbutil.XUtil, ev xgb.Event, evMask uint32) error {
return xproto.SendEventChecked(xu.Conn(), false, xu.RootWin(), evMask,
string(ev.Bytes())).Check()
}
// ReplayPointer is a quick alias to AllowEvents with 'ReplayPointer' mode.
func ReplayPointer(xu *xgbutil.XUtil) {
xproto.AllowEvents(xu.Conn(), xproto.AllowReplayPointer, 0)
}