Skip to content

Commit

Permalink
HIDI 1.3.0 (#11)
Browse files Browse the repository at this point in the history
- fix/added proper support for multihandler devices like ps4 dualshock controller (gyro/accelerometer subhandler)
- **due to above fix, small change to device configuration format was introduced, user-defined configurations may require manual intervention, change is subtle and may be hard to spot so I suggest to create new configuration by copying factory one**
- removed custom gyro related code
- add configurable velocity value in device configuration
- add device blacklist capability 
- add -listdevices parameter
  • Loading branch information
gethiox authored Dec 15, 2024
1 parent 78fac36 commit 5acc05a
Show file tree
Hide file tree
Showing 32 changed files with 1,456 additions and 1,635 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ the best user experience possible.
- **All devices are loaded/unloaded completely dynamically**
- Application will reload configuration when new one will appear or existing one was changed.
Very useful when user want to craft their own configuration
- Gyroscope sensor support (arm platforms) for pitch-bend and CC controls
- OpenRGB support ([Demo](https://youtu.be/QF_z6LHcSkE)) (Check `"Direct" Mode` [here](https://openrgb.org/devices.html) for
supported devices.)

Expand Down
4 changes: 4 additions & 0 deletions cmd/hidi/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Entry struct {
Device string `json:"device_name"`
HandlerEvent string `json:"handler_event"`
HandlerName string `json:"handler_name"`
Subhandler string `json:"handler_subhandler"`
Config string `json:"config"`
DeviceType string `json:"device_type"`
}
Expand Down Expand Up @@ -199,6 +200,9 @@ func prepareString(msg Entry, au aurora.Aurora, width, logLevel int) string {
if msg.HandlerName != "" {
fields += fmt.Sprintf(" [handler=%s]", colorForString(au, msg.HandlerName).String())
}
if msg.Subhandler != "" {
fields += fmt.Sprintf(" [subhandler=%s]", colorForString(au, msg.Subhandler).String())
}
if msg.HandlerEvent != "" {
fields += fmt.Sprintf(" [%s]", colorForString(au, msg.HandlerEvent).String())
}
Expand Down
122 changes: 105 additions & 17 deletions cmd/hidi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import (
"io"
"io/fs"
"os"
"regexp"
"strconv"
"strings"
"time"

"github.com/gethiox/HIDI/internal/pkg/input"
"github.com/gethiox/HIDI/internal/pkg/logger"
"github.com/pelletier/go-toml/v2"
)
Expand All @@ -20,15 +24,8 @@ type HIDI struct {
StabilizationPeriod time.Duration
}

type Gyro struct {
Enabled bool
Address byte
Bus byte
}

type HIDIConfig struct {
HIDI HIDI
Gyro Gyro
}

type HIDIConfigRaw struct {
Expand All @@ -39,12 +36,6 @@ type HIDIConfigRaw struct {
LogViewRate int `toml:"log_view_rate"`
LogBufferSize int `toml:"log_buffer_size"`
} `toml:"HIDI"`

Gyro struct {
Enabled bool `toml:"enabled"`
Address int `toml:"address"`
Bus int `toml:"bus"`
} `toml:"gyro"`
}

func LoadHIDIConfig(path string) (HIDIConfig, error) {
Expand All @@ -65,14 +56,11 @@ func LoadHIDIConfig(path string) (HIDIConfig, error) {
config.HIDI.DiscoveryRate = time.Second / time.Duration(rawConfig.HIDI.DiscoveryRate)
config.HIDI.StabilizationPeriod = time.Millisecond * time.Duration(rawConfig.HIDI.StabilizationPeriod)

config.Gyro.Enabled = rawConfig.Gyro.Enabled
config.Gyro.Address = byte(rawConfig.Gyro.Address)
config.Gyro.Bus = byte(rawConfig.Gyro.Bus)

return config, err
}

//go:embed hidi-config/hidi.toml
//go:embed "hidi-config/device blacklist.txt"
//go:embed hidi-config/*/*/*
//go:embed hidi-config/factory/README
//go:embed hidi-config/user/README.md
Expand Down Expand Up @@ -205,5 +193,105 @@ func updateHIDIConfiguration() error {
if err != nil {
return fmt.Errorf("update factory configs failed: %w", err)
}

// create device blacklist.txt if does not exist.
blacklistPath := configDir + "/device blacklist.txt"
fd, err := os.OpenFile(blacklistPath, os.O_RDONLY, 0)
if os.IsNotExist(err) {
dst, err := os.OpenFile(blacklistPath, os.O_CREATE|os.O_WRONLY, 0o666)
if err != nil {
return fmt.Errorf("cannot open \"device blacklist.txt\": %w", err)
}
defer dst.Close()

data, err := fs.ReadFile(templateConfig, blacklistPath)
if err != nil {
return fmt.Errorf("cannot read \"%s\" template file: %w", blacklistPath, err)
}

_, err = dst.Write(data)
if err != nil {
return fmt.Errorf("cannot write data into \"%s\" file: %w", blacklistPath, err)
}

log.Info(fmt.Sprintf("Created \"%s\" file", blacklistPath), logger.Debug)
return nil
}
fd.Close()

return nil
}

var regex = regexp.MustCompile(
`(?i)Bus:\s*0x([0-9a-f]{4}),\s*Vendor:\s*0x([0-9a-f]{4}),\s*Product:\s*0x([0-9a-f]{4}),\s*Version:\s*0x([0-9a-f]{4})`,
)

func parseDeviceBlacklist() ([]input.InputID, error) {
data, err := os.ReadFile(configDir + "/device blacklist.txt")
if err != nil {
return nil, fmt.Errorf("cannot read \"%s\" file: %w", configDir+"/device blacklist.txt", err)
}
text := string(data)

var ids = make([]input.InputID, 0)
for lineno, line := range strings.Split(text, "\n") {
if strings.HasPrefix(line, "#") {
log.Info(fmt.Sprintf("[blacklist] ignoring commend under line %d", lineno+1), logger.Debug)
continue
}
if line == "" {
continue
}

result := regex.FindAllStringSubmatch(line, 1)

if len(result) == 0 {
log.Info(fmt.Sprintf("[blacklist] parsing failed for line %d", lineno+1), logger.Warning)
continue
}

var busRaw, vendorRaw, productRaw, versionRaw = result[0][1], result[0][2], result[0][3], result[0][4]

bus, err1 := strconv.ParseUint(busRaw, 16, 16)
vendor, err2 := strconv.ParseUint(vendorRaw, 16, 16)
product, err3 := strconv.ParseUint(productRaw, 16, 16)
version, err4 := strconv.ParseUint(versionRaw, 16, 16)
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
log.Info(fmt.Sprintf("[blacklist] converting hex values failed for line %d", lineno+1), logger.Warning)
continue
}

ids = append(ids, input.InputID{
Bus: uint16(bus),
Vendor: uint16(vendor),
Product: uint16(product),
Version: uint16(version),
})
}
return ids, nil
}

func loadDeviceBlacklist() ([]input.PhysicalID, error) {
ids, err := parseDeviceBlacklist()
if err != nil {
return nil, fmt.Errorf("parsing \"device blacklist.txt\" failed: %w", err)
}

for _, id := range ids {
log.Info(fmt.Sprintf("[blacklist] loaded device: %s", id.String()), logger.Info)
}

devs := collectDevices(time.Second)

var pids = make([]input.PhysicalID, 0)

for _, dev := range devs {
for _, id := range ids {
if dev.ID == id {
pids = append(pids, dev.PhysicalUUID())
}
}
}

return pids, nil
}
6 changes: 6 additions & 0 deletions cmd/hidi/hidi-config/device blacklist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# list of devices to be ignored by HIDI
# hashtag defines a comment and will be ignored
# the simplest way to obtain a device identifier: ./HIDI -listdevices
#
# example entry:
# Bus: 0x0003, Vendor: 0x054c, Product: 0x09cc, Version: 0x8111 # [Sony Interactive Entertainment Wireless Controller] (Joystick)
65 changes: 35 additions & 30 deletions cmd/hidi/hidi-config/factory/gamepad/0_default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,48 @@ exit_sequence = []
semitone = 0
channel = 1
mapping = "Default"
velocity = 64

[action_mapping]
BTN_SELECT = "channel_down"
BTN_START = "channel_up"
BTN_MODE = "panic"
BTN_TL = "cc_learning"

[deadzone]
default = 0.1
[deadzone.deadzones]
ABS_Z = 0.0
ABS_RZ = 0.0
ABS_HAT0X = 0.0
ABS_HAT0Y = 0.0

[[mapping]]
name = "Default"
[mapping.keys]
BTN_A = "0"
BTN_B = "1"
BTN_X = "2"
BTN_Y = "3"
BTN_C = "4"
BTN_Z = "6"
BTN_TL2 = "7"
BTN_TR2 = "8"
BTN_THUMBL = "9"
BTN_THUMBR = "10"
BTN_TL = "11"
BTN_TR = "12"
[[mapping.keys]]
subhandler = ""
[mapping.keys.map]
BTN_A = "0"
BTN_B = "1"
BTN_X = "2"
BTN_Y = "3"
BTN_C = "4"
BTN_Z = "6"
BTN_TL2 = "7"
BTN_TR2 = "8"
BTN_THUMBL = "9"
BTN_THUMBR = "10"
BTN_TL = "11"
BTN_TR = "12"

[[mapping.analog]]
subhandler = ""
default_deadzone = 0.1

[mapping.analog.map]
ABS_X = { type = "cc", cc = 0 }
ABS_Y = { type = "pitch_bend", flip_axis = true }
ABS_RX = { type = "cc", cc = 1, cc_negative = 2 }
ABS_RY = { type = "cc", cc = 3, cc_negative = 4, flip_axis = true }
ABS_Z = { type = "cc", cc = 5 }
ABS_RZ = { type = "cc", cc = 6 }
ABS_HAT0X = { type = "action", action = "octave_up", action_negative = "octave_down" }
ABS_HAT0Y = { type = "action", action = "mapping_up", action_negative = "mapping_down", flip_axis = true }

[mapping.analog]
ABS_X = { type = "cc", cc = 0 }
ABS_Y = { type = "pitch_bend", flip_axis = true }
ABS_RX = { type = "cc", cc = 1, cc_negative = 2 }
ABS_RY = { type = "cc", cc = 3, cc_negative = 4, flip_axis = true }
ABS_Z = { type = "cc", cc = 5 }
ABS_RZ = { type = "cc", cc = 6 }
ABS_HAT0X = { type = "action", action = "octave_up", action_negative = "octave_down" }
ABS_HAT0Y = { type = "action", action = "mapping_up", action_negative = "mapping_down", flip_axis = true }
[mapping.analog.deadzones]
ABS_Z = 0.0
ABS_RZ = 0.0
ABS_HAT0X = 0.0
ABS_HAT0Y = 0.0
Loading

0 comments on commit 5acc05a

Please sign in to comment.