179 lines
5.8 KiB
Go
179 lines
5.8 KiB
Go
|
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)
|
||
|
}
|
||
|
}
|
||
|
}
|