wm/vend/xgbutil/mousebind/mousebind.go

172 lines
5.3 KiB
Go
Raw Normal View History

2023-06-11 14:21:08 +00:00
package mousebind
import (
"fmt"
"strconv"
"strings"
"github.com/jezek/xgb/xproto"
"github.com/jezek/xgbutil"
"github.com/jezek/xgbutil/xevent"
)
var modifiers []uint16 = []uint16{ // order matters!
xproto.ModMaskShift, xproto.ModMaskLock, xproto.ModMaskControl,
xproto.ModMask1, xproto.ModMask2, xproto.ModMask3,
xproto.ModMask4, xproto.ModMask5,
xproto.ButtonMask1, xproto.ButtonMask2, xproto.ButtonMask3,
xproto.ButtonMask4, xproto.ButtonMask5,
xproto.ButtonMaskAny,
}
var pointerMasks uint16 = xproto.EventMaskPointerMotion |
xproto.EventMaskButtonRelease |
xproto.EventMaskButtonPress
// Initialize attaches the appropriate callbacks to make mouse bindings easier.
// i.e., prep the dummy window to handle mouse dragging events
func Initialize(xu *xgbutil.XUtil) {
xevent.MotionNotifyFun(dragStep).Connect(xu, xu.Dummy())
xevent.ButtonReleaseFun(DragEnd).Connect(xu, xu.Dummy())
}
// ParseString takes a string of the format '[Mod[-Mod[...]]]-BUTTONNUMBER',
// i.e., 'Mod4-1', and returns a modifiers/button combination.
// "Mod" could also be one of {button1, button2, button3, button4, button5}.
// An error is returned if the string is malformed or if no BUTTONNUMBER
// could be found.
func ParseString(xu *xgbutil.XUtil, str string) (uint16, xproto.Button, error) {
mods, button := uint16(0), xproto.Button(0)
for _, part := range strings.Split(str, "-") {
switch strings.ToLower(part) {
case "shift":
mods |= xproto.ModMaskShift
case "lock":
mods |= xproto.ModMaskLock
case "control":
mods |= xproto.ModMaskControl
case "mod1":
mods |= xproto.ModMask1
case "mod2":
mods |= xproto.ModMask2
case "mod3":
mods |= xproto.ModMask3
case "mod4":
mods |= xproto.ModMask4
case "mod5":
mods |= xproto.ModMask5
case "button1":
mods |= xproto.ButtonMask1
case "button2":
mods |= xproto.ButtonMask2
case "button3":
mods |= xproto.ButtonMask3
case "button4":
mods |= xproto.ButtonMask4
case "button5":
mods |= xproto.ButtonMask5
case "any":
mods |= xproto.ButtonMaskAny
default: // a button!
if button == 0 { // only accept the first button we see
possible, err := strconv.ParseUint(part, 10, 8)
if err == nil {
button = xproto.Button(possible)
} else {
return 0, 0, fmt.Errorf("Could not convert '%s' to a "+
"valid 8-bit integer.", part)
}
}
}
}
if button == 0 {
return 0, 0, fmt.Errorf("Could not find a valid button in the "+
"string '%s'. Mouse binding failed.", str)
}
return mods, button, nil
}
// Grab grabs a button with mods on a particular window.
// Will also grab all combinations of modifiers found in xevent.IgnoreMods
// 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 Grab(xu *xgbutil.XUtil, win xproto.Window, mods uint16,
button xproto.Button, sync bool) {
var pSync byte = xproto.GrabModeAsync
if sync {
pSync = xproto.GrabModeSync
}
for _, m := range xevent.IgnoreMods {
xproto.GrabButton(xu.Conn(), true, win, pointerMasks, pSync,
xproto.GrabModeAsync, 0, 0, byte(button), mods|m)
}
}
// GrabChecked grabs a button with mods on a particular window. It does the
// same thing as Grab, but issues a checked request and returns an error
// on failure.
// Will also grab all combinations of modifiers found in xevent.IgnoreMods
// 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 GrabChecked(xu *xgbutil.XUtil, win xproto.Window, mods uint16,
button xproto.Button, sync bool) error {
var pSync byte = xproto.GrabModeAsync
if sync {
pSync = xproto.GrabModeSync
}
var err error
for _, m := range xevent.IgnoreMods {
err = xproto.GrabButtonChecked(xu.Conn(), true, win, pointerMasks,
pSync, xproto.GrabModeAsync, 0, 0, byte(button), mods|m).Check()
if err != nil {
return err
}
}
return nil
}
// Ungrab undoes Grab. It will handle all combinations of modifiers found
// in xevent.IgnoreMods.
func Ungrab(xu *xgbutil.XUtil, win xproto.Window, mods uint16,
button xproto.Button) {
for _, m := range xevent.IgnoreMods {
xproto.UngrabButtonChecked(xu.Conn(), byte(button), win, mods|m).Check()
}
}
// GrabPointer grabs the entire pointer.
// Returns whether GrabStatus is successful and an error if one is reported by
// XGB. It is possible to not get an error and the grab to be unsuccessful.
// The purpose of 'win' is that after a grab is successful, ALL Button*Events
// will be sent to that window. Make sure you have a callback attached :-)
func GrabPointer(xu *xgbutil.XUtil, win xproto.Window, confine xproto.Window,
cursor xproto.Cursor) (bool, error) {
reply, err := xproto.GrabPointer(xu.Conn(), false, win, pointerMasks,
xproto.GrabModeAsync, xproto.GrabModeAsync,
confine, cursor, 0).Reply()
if err != nil {
return false, fmt.Errorf("GrabPointer: Error grabbing pointer on "+
"window '%x': %s", win, err)
}
return reply.Status == xproto.GrabStatusSuccess, nil
}
// UngrabPointer undoes GrabPointer.
func UngrabPointer(xu *xgbutil.XUtil) {
xproto.UngrabPointer(xu.Conn(), 0)
}