Implement MappingRuleAxisToButton.

This commit is contained in:
Anna Rose Wiggins 2025-07-10 23:18:34 -04:00
parent a6ad1b609a
commit 47fac539da
3 changed files with 81 additions and 27 deletions

View file

@ -33,3 +33,8 @@ type RuleTarget interface {
type RuleTargetDevice interface {
AbsInfos() (map[evdev.EvCode]evdev.AbsInfo, error)
}
const (
AxisValueMin = int32(-32768)
AxisValueMax = int32(32767)
)

View file

@ -6,45 +6,85 @@ import (
"github.com/holoplot/go-evdev"
)
// TODO: This whole file is still WIP
// MappingRuleAxisToButton represents a rule that converts an axis input into a (potentially repeating)
// button output.
//
// TODO: Add Tests
type MappingRuleAxisToButton struct {
MappingRuleBase
Input *RuleTargetAxis
Output *RuleTargetButton
RepeatSpeedMin int32
RepeatSpeedMax int32
lastValue int32
RepeatRateMin int
RepeatRateMax int
nextEvent time.Duration
lastEvent time.Time
pressed bool
}
const (
NoNextEvent = time.Duration(-1)
)
func NewMappingRuleAxisToButton(base MappingRuleBase, input *RuleTargetAxis, output *RuleTargetButton, repeatRateMin, repeatRateMax int) *MappingRuleAxisToButton {
return &MappingRuleAxisToButton{
MappingRuleBase: base,
Input: input,
Output: output,
RepeatRateMin: repeatRateMin,
RepeatRateMax: repeatRateMax,
lastEvent: time.Now(),
nextEvent: NoNextEvent,
pressed: false,
}
}
func (rule *MappingRuleAxisToButton) MatchEvent(device *evdev.InputDevice, event *evdev.InputEvent, mode *string) (*evdev.InputDevice, *evdev.InputEvent) {
// TODO: we're using this instead of the RuleTarget's MatchEvent because we need to check inside the deadzone
// We should find a cleaner way to do this...
if !rule.MappingRuleBase.modeCheck(mode) ||
!rule.Input.MatchEvent(device, event) {
!rule.Input.MatchEventDeviceAndCode(device, event) {
return nil, nil
}
// set the last value to the normalized input value
rule.lastValue = rule.Input.NormalizeValue(event.Value)
// If we're inside the deadzone, unset the next event
if rule.Input.InDeadZone(event.Value) {
rule.nextEvent = NoNextEvent
return nil, nil
}
// If we aren't repeating, we trigger the event immediately
if rule.RepeatRateMin == 0 || rule.RepeatRateMax == 0 {
rule.nextEvent = time.Millisecond
return nil, nil
}
// use the axis value and the repeat rate to set a target time until the next event
strength := 1.0 - rule.Input.GetAxisStrength(event.Value)
rate := int64(LerpInt(rule.RepeatRateMax, rule.RepeatRateMin, strength))
rule.nextEvent = time.Duration(rate * int64(time.Millisecond))
return nil, nil
}
// TimerEvent returns an event when enough time has passed (compared to the last recorded axis value)
// to emit an event.
func (rule *MappingRuleAxisToButton) TimerEvent() *evdev.InputEvent {
// This is tighter coupling than we'd like, but it will do for now.
// TODO: maybe it would be better to just be more declarative about event types and their inputs and outputs.
if rule.lastValue < rule.Input.DeadzoneStart {
// If we pressed the button last tick, release it
if rule.pressed {
rule.pressed = false
return rule.Output.CreateEvent(0, nil)
}
// This indicates that we should not emit another event
if rule.nextEvent == -1 {
rule.lastEvent = time.Now()
return nil
}
// calculate target time until next event press
// nextEvent := rule.LastEvent + (rule.LastValue)
// TODO: figure out what the condition should be
if false {
// TODO: emit event
if time.Now().Compare(rule.lastEvent.Add(rule.nextEvent)) > -1 {
rule.lastEvent = time.Now()
rule.pressed = true
return rule.Output.CreateEvent(1, nil)
}
return nil

View file

@ -18,11 +18,6 @@ type RuleTargetAxis struct {
deadzoneSize int32
}
const (
AxisValueMin = int32(-32768)
AxisValueMax = int32(32767)
)
func NewRuleTargetAxis(device_name string,
device RuleTargetDevice,
axis evdev.EvCode,
@ -81,7 +76,7 @@ func NewRuleTargetAxis(device_name string,
// Typically this function is called after RuleTargetAxis.MatchEvent, which checks whether we are
// in the deadzone, among other things.
func (target *RuleTargetAxis) NormalizeValue(value int32) int32 {
axisStrength := float64(value-target.deadzoneSize) / float64(target.axisSize)
axisStrength := target.GetAxisStrength(value)
if target.Inverted {
axisStrength = 1.0 - axisStrength
@ -100,8 +95,22 @@ func (target *RuleTargetAxis) CreateEvent(value int32, mode *string) *evdev.Inpu
}
func (target *RuleTargetAxis) MatchEvent(device RuleTargetDevice, event *evdev.InputEvent) bool {
return target.MatchEventDeviceAndCode(device, event) &&
!target.InDeadZone(event.Value)
}
// TODO: Add tests
func (target *RuleTargetAxis) MatchEventDeviceAndCode(device RuleTargetDevice, event *evdev.InputEvent) bool {
return device == target.Device &&
event.Type == evdev.EV_ABS &&
event.Code == target.Axis &&
(event.Value < target.DeadzoneStart || event.Value > target.DeadzoneEnd)
event.Code == target.Axis
}
// TODO: Add tests
func (target *RuleTargetAxis) InDeadZone(value int32) bool {
return value >= target.DeadzoneStart && value <= target.DeadzoneEnd
}
func (target *RuleTargetAxis) GetAxisStrength(value int32) float64 {
return float64(value-target.deadzoneSize) / float64(target.axisSize)
}