162 lines
5.1 KiB
Go
162 lines
5.1 KiB
Go
package keybind
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jezek/xgb/xproto"
|
|
|
|
"github.com/jezek/xgbutil"
|
|
"github.com/jezek/xgbutil/xevent"
|
|
)
|
|
|
|
// connect is essentially 'Connect' for either KeyPress or KeyRelease events.
|
|
// Namely, it parses the key string, issues a grab request if necessary,
|
|
// sets up the appropriate event handlers for the main event loop, and attaches
|
|
// the callback to the keybinding state.
|
|
func connect(xu *xgbutil.XUtil, callback xgbutil.CallbackKey,
|
|
evtype int, win xproto.Window, keyStr string, grab, reconnect bool) error {
|
|
|
|
// Get the mods/key first
|
|
mods, keycodes, err := ParseString(xu, keyStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Only do the grab if we haven't yet on this window.
|
|
for _, keycode := range keycodes {
|
|
if grab && keyBindGrabs(xu, evtype, win, mods, keycode) == 0 {
|
|
if err := GrabChecked(xu, win, mods, keycode); 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 keybinding.", keyStr)
|
|
default:
|
|
return fmt.Errorf("Could not bind '%s' because: %s",
|
|
keyStr, 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.
|
|
// Never do this if we're reconnecting.
|
|
if !reconnect {
|
|
var allCb xgbutil.Callback
|
|
if evtype == xevent.KeyPress {
|
|
allCb = xevent.KeyPressFun(runKeyPressCallbacks)
|
|
} else {
|
|
allCb = xevent.KeyReleaseFun(runKeyReleaseCallbacks)
|
|
}
|
|
|
|
// If this is the first Key{Press|Release}Event on this window,
|
|
// then we need to listen to Key{Press|Release} events in the main
|
|
// loop.
|
|
if !connectedKeyBind(xu, evtype, win) {
|
|
allCb.Connect(xu, win)
|
|
}
|
|
}
|
|
|
|
// Finally, attach the callback.
|
|
attachKeyBindCallback(xu, evtype, win, mods, keycode, callback)
|
|
}
|
|
|
|
// Keep track of all unique key connections.
|
|
if !reconnect {
|
|
addKeyString(xu, callback, evtype, win, keyStr, grab)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeduceKeyInfo AND's the "ignored modifiers" out of the state returned by
|
|
// a Key{Press,Release} event. This is useful to connect a (state, keycode)
|
|
// tuple from an event with a tuple specified by the user.
|
|
func DeduceKeyInfo(state uint16,
|
|
detail xproto.Keycode) (uint16, xproto.Keycode) {
|
|
|
|
mods, kc := state, detail
|
|
for _, m := range xevent.IgnoreMods {
|
|
mods &= ^m
|
|
}
|
|
return mods, kc
|
|
}
|
|
|
|
// KeyPressFun represents a function that is called when a particular key
|
|
// binding is fired.
|
|
type KeyPressFun xevent.KeyPressFun
|
|
|
|
func (callback KeyPressFun) Connect(xu *xgbutil.XUtil, win xproto.Window,
|
|
keyStr string, grab bool) error {
|
|
|
|
return connect(xu, callback, xevent.KeyPress, win, keyStr, grab, false)
|
|
}
|
|
|
|
func (callback KeyPressFun) Run(xu *xgbutil.XUtil, event interface{}) {
|
|
callback(xu, event.(xevent.KeyPressEvent))
|
|
}
|
|
|
|
// KeyReleaseFun represents a function that is called when a particular key
|
|
// binding is fired.
|
|
type KeyReleaseFun xevent.KeyReleaseFun
|
|
|
|
func (callback KeyReleaseFun) Connect(xu *xgbutil.XUtil, win xproto.Window,
|
|
keyStr string, grab bool) error {
|
|
|
|
return connect(xu, callback, xevent.KeyRelease, win, keyStr, grab, false)
|
|
}
|
|
|
|
func (callback KeyReleaseFun) Run(xu *xgbutil.XUtil, event interface{}) {
|
|
callback(xu, event.(xevent.KeyReleaseEvent))
|
|
}
|
|
|
|
// runKeyPressCallbacks infers the window, keycode and modifiers from a
|
|
// KeyPressEvent and runs the corresponding callbacks.
|
|
func runKeyPressCallbacks(xu *xgbutil.XUtil, ev xevent.KeyPressEvent) {
|
|
mods, kc := DeduceKeyInfo(ev.State, ev.Detail)
|
|
|
|
runKeyBindCallbacks(xu, ev, xevent.KeyPress, ev.Event, mods, kc)
|
|
}
|
|
|
|
// runKeyReleaseCallbacks infers the window, keycode and modifiers from a
|
|
// KeyPressEvent and runs the corresponding callbacks.
|
|
func runKeyReleaseCallbacks(xu *xgbutil.XUtil, ev xevent.KeyReleaseEvent) {
|
|
mods, kc := DeduceKeyInfo(ev.State, ev.Detail)
|
|
|
|
runKeyBindCallbacks(xu, ev, xevent.KeyRelease, ev.Event, mods, kc)
|
|
}
|
|
|
|
// Detach removes all handlers for all key 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.KeyPress, win)
|
|
detach(xu, xevent.KeyRelease, win)
|
|
}
|
|
|
|
// DetachPress is the same as Detach, except it only removes handlers for
|
|
// key *press* events.
|
|
func DetachPress(xu *xgbutil.XUtil, win xproto.Window) {
|
|
detach(xu, xevent.KeyPress, win)
|
|
}
|
|
|
|
// DetachRelease is the same as Detach, except it only removes handlers for
|
|
// key *release* events.
|
|
func DetachRelease(xu *xgbutil.XUtil, win xproto.Window) {
|
|
detach(xu, xevent.KeyRelease, 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 := keyKeys(xu)
|
|
detachKeyBindWindow(xu, evtype, win)
|
|
for _, key := range mkeys {
|
|
if keyBindGrabs(xu, key.Evtype, key.Win, key.Mod, key.Code) == 0 {
|
|
Ungrab(xu, key.Win, key.Mod, key.Code)
|
|
}
|
|
}
|
|
}
|