238 lines
7.8 KiB
Go
238 lines
7.8 KiB
Go
|
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)
|
||
|
}
|