wm/vend/xgbutil/mousebind/callback.go

179 lines
5.8 KiB
Go
Raw Permalink Normal View History

2023-06-11 14:21:08 +00:00
package mousebind
import (
"fmt"
"github.com/jezek/xgb/xproto"
"github.com/jezek/xgbutil"
"github.com/jezek/xgbutil/xevent"
)
// connect is essentially 'Connect' for either ButtonPress or
// ButtonRelease events.
func connect(xu *xgbutil.XUtil, callback xgbutil.CallbackMouse, evtype int,
win xproto.Window, buttonStr string, sync bool, grab bool) error {
// Get the mods/button first
mods, button, err := ParseString(xu, buttonStr)
if err != nil {
return err
}
// Only do the grab if we haven't yet on this window.
// And if we WANT a grab...
if grab && mouseBindGrabs(xu, evtype, win, mods, button) == 0 {
err := GrabChecked(xu, win, mods, button, sync)
if err != nil {
// If a bad access, let's be nice and give a good error message.
switch err.(type) {
case xproto.AccessError:
return fmt.Errorf("Got a bad access error when trying to bind "+
"'%s'. This usually means another client has already "+
"grabbed this mouse binding.", buttonStr)
default:
return fmt.Errorf("Could not bind '%s' because: %s",
buttonStr, err)
}
}
}
// If we've never grabbed anything on this window before, we need to
// make sure we can respond to it in the main event loop.
var allCb xgbutil.Callback
if evtype == xevent.ButtonPress {
allCb = xevent.ButtonPressFun(runButtonPressCallbacks)
} else {
allCb = xevent.ButtonReleaseFun(runButtonReleaseCallbacks)
}
// If this is the first Button{Press|Release}Event on this window,
// then we need to listen to Button{Press|Release} events in the main loop.
if !connectedMouseBind(xu, evtype, win) {
allCb.Connect(xu, win)
}
// Finally, attach the callback.
attachMouseBindCallback(xu, evtype, win, mods, button, callback)
return nil
}
// DeduceButtonInfo takes a (modifiers, button) tuple and returns the relevant
// modifiers that were activated. This accounts for modifiers in
// xevent.IgnoreMods and the the button mask of the button that is pressed.
func DeduceButtonInfo(state uint16,
detail xproto.Button) (uint16, xproto.Button) {
mods, button := state, detail
for _, m := range xevent.IgnoreMods {
mods &= ^m
}
// We also need to mask out the button that has been pressed/released,
// since it is also typically a modifier.
modsTemp := int32(mods)
switch button {
case 1:
modsTemp &= ^xproto.ButtonMask1
case 2:
modsTemp &= ^xproto.ButtonMask2
case 3:
modsTemp &= ^xproto.ButtonMask3
case 4:
modsTemp &= ^xproto.ButtonMask4
case 5:
modsTemp &= ^xproto.ButtonMask5
}
return uint16(modsTemp), button
}
// ButtonPressFun represents a function that is called when a particular mouse
// binding is fired.
type ButtonPressFun xevent.ButtonPressFun
// If 'sync' is True, then no further events can be processed until the
// grabbing client allows them to be. (Which is done via AllowEvents. Thus,
// if sync is True, you *must* make some call to AllowEvents at some
// point, or else your client will lock.)
func (callback ButtonPressFun) Connect(xu *xgbutil.XUtil, win xproto.Window,
buttonStr string, sync bool, grab bool) error {
return connect(xu, callback, xevent.ButtonPress, win, buttonStr, sync, grab)
}
func (callback ButtonPressFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(xevent.ButtonPressEvent))
}
// ButtonReleaseFun represents a function that is called when a particular mouse
// binding is fired.
type ButtonReleaseFun xevent.ButtonReleaseFun
// If 'sync' is True, then no further events can be processed until the
// grabbing client allows them to be. (Which is done via AllowEvents. Thus,
// if sync is True, you *must* make some call to AllowEvents at some
// point, or else your client will lock.)
func (callback ButtonReleaseFun) Connect(xu *xgbutil.XUtil, win xproto.Window,
buttonStr string, sync bool, grab bool) error {
return connect(xu, callback, xevent.ButtonRelease, win, buttonStr,
sync, grab)
}
func (callback ButtonReleaseFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(xevent.ButtonReleaseEvent))
}
// runButtonPressCallbacks infers the window, button and modifiers from a
// ButtonPressEvent and runs the corresponding callbacks.
func runButtonPressCallbacks(xu *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
mods, button := DeduceButtonInfo(ev.State, ev.Detail)
runMouseBindCallbacks(xu, ev, xevent.ButtonPress, ev.Event, mods, button)
}
// runButtonReleaseCallbacks infers the window, keycode and modifiers from a
// ButtonPressEvent and runs the corresponding callbacks.
func runButtonReleaseCallbacks(xu *xgbutil.XUtil,
ev xevent.ButtonReleaseEvent) {
mods, button := DeduceButtonInfo(ev.State, ev.Detail)
runMouseBindCallbacks(xu, ev, xevent.ButtonRelease, ev.Event, mods, button)
}
// Detach removes all handlers for all mouse events for the provided window id.
// This should be called whenever a window is no longer receiving events to make
// sure the garbage collector can release memory used to store the handler info.
func Detach(xu *xgbutil.XUtil, win xproto.Window) {
detach(xu, xevent.ButtonPress, win)
detach(xu, xevent.ButtonRelease, win)
}
// DetachPress is the same as Detach, except it only removes handlers for
// button *press* events.
func DetachPress(xu *xgbutil.XUtil, win xproto.Window) {
detach(xu, xevent.ButtonPress, win)
}
// DetachRelease is the same as Detach, except it only removes handlers for
// mouse *release* events.
func DetachRelease(xu *xgbutil.XUtil, win xproto.Window) {
detach(xu, xevent.ButtonRelease, win)
}
// detach removes all handlers for the provided window and event type
// combination. This will also issue an ungrab request for each grab that
// drops to zero.
func detach(xu *xgbutil.XUtil, evtype int, win xproto.Window) {
mkeys := mouseKeys(xu)
detachMouseBindWindow(xu, evtype, win)
for _, key := range mkeys {
if mouseBindGrabs(xu, key.Evtype, key.Win, key.Mod, key.Button) == 0 {
Ungrab(xu, key.Win, key.Mod, key.Button)
}
}
}