From 22e20464418ea164d46a83db01c6ac2eceb87777 Mon Sep 17 00:00:00 2001 From: Anna Rose Wiggins Date: Tue, 5 Aug 2025 15:51:59 -0400 Subject: [PATCH] Support specifying physical devices via device file instead of device name. --- cmd/evinfo/main.go | 35 +++++++++++++++++++++++++++++++++-- docs/readme.md | 12 +++++++++++- internal/config/devices.go | 19 +++++++++++++++---- internal/config/schema.go | 6 +++--- readme.md | 2 ++ 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/cmd/evinfo/main.go b/cmd/evinfo/main.go index d9e600b..c2cc8f0 100644 --- a/cmd/evinfo/main.go +++ b/cmd/evinfo/main.go @@ -43,6 +43,32 @@ func printDevice(devPath evdev.InputPath) { return } + // Get metadata + // metadata := struct { + // uuid string + // vendor string + // product string + // version string + // }{} + + // uuid, err := device.UniqueID() + // if err != nil { + // metadata.uuid = "unknown" + // } else { + // metadata.uuid = uuid + // } + + // inputId, err := device.InputID() + // if err != nil { + // metadata.vendor = "unknown" + // metadata.product = "unknown" + // metadata.version = "unknown" + // } else { + // metadata.vendor = "0x" + strconv.FormatUint(uint64(inputId.Vendor), 16) + // metadata.product = "0x" + strconv.FormatUint(uint64(inputId.Product), 16) + // metadata.version = strconv.FormatUint(uint64(inputId.Version), 10) + // } + // Get axis info var axisOutputs []string absInfos, err := device.AbsInfos() @@ -56,8 +82,13 @@ func printDevice(devPath evdev.InputPath) { } } + // Print everything fmt.Printf("%s:\n", devPath.Path) - fmt.Printf("\tName: '%s'\n", devPath.Name) + fmt.Printf("\tName:\t\t'%s'\n", devPath.Name) + // fmt.Printf("\tUUID:\t\t'%s'\n", metadata.uuid) + // fmt.Printf("\tVendor:\t\t'%s'\n", metadata.vendor) + // fmt.Printf("\tProduct:\t'%s'\n", metadata.product) + // fmt.Printf("\tVersion:\t'%s'\n", metadata.version) if len(axisOutputs) > 0 { fmt.Println("\tAxes:") for _, str := range axisOutputs { @@ -76,7 +107,7 @@ func printDeviceQuiet(devPath evdev.InputPath) { return } - fmt.Printf("'%s'\n", devPath.Name) + fmt.Printf("'%s': '%s'\n", devPath.Path, devPath.Name) } // TODO: it would be nice to be able to specify a device by name or device file and get axis info diff --git a/docs/readme.md b/docs/readme.md index 773451e..f6e7f37 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -9,11 +9,21 @@ Each entry in `devices` must have these parameters: * `name` - This is an identifier that your rules will use to refer to the device. It is recommended to avoid spaces or special characters. * `type` - 'physical' for an input device, 'virtual' for an output device. +### Physical Devices + `physical` devices have these additional parameters: -* `device_name` (required) - The name of the device as reported by the included `evinfo` command. If your device name ends with a space, use quotation marks (`""`) around the name. +* `device_name` - The name of the device as reported by the included `evinfo` command. If your device name ends with a space, use quotation marks (`""`) around the name. +* `device_path` - If you have multiple devices that report the same name, you can use `device_path` instead of `device_name`. Setting this will cause the device to be opened directly via the device file. + * It is recommended to use the `by-path` symlinks, e.g., `/dev/input/by-path/pci-0000:0d:00.0-usbv2-0:3:1.0-event-joystick`. + * Note that this method may be slightly unreliable since these identifiers may change if they are plugged into different USB ports or in the rare case that the USB topology changes (e.g., you add a new USB hub). + * On the other hand, this method causes the device to be opened considerably faster, lowering Joyful's startup time substantially. If this is important to you this method may be preferable. * `lock` - If set to 'true', the device will be locked for exclusive access. This means that your game will not see any events from the device, so you'll need to make sure you map every button you want to use. Setting this to 'false' might be useful if you're just mapping a few joystick buttons to keyboard buttons. This value defaults to 'true'. +`device_path` is given higher priority than `device_name`; if both are specified, `device_path` will be used. + +### Virtual Devices + `virtual` devices have these additional parameters: * `preset` - Can be 'joystick', 'gamepad', 'mouse', or 'keyboard', and will configure the virtual device to look like and emit an appropriate set of outputs based on the name. For exactly which axes and buttons are defined for each type, see the `Capabilities` values in [internal/config/variables.go](internal/config/variables.go). diff --git a/internal/config/devices.go b/internal/config/devices.go index f878fde..9802bff 100644 --- a/internal/config/devices.go +++ b/internal/config/devices.go @@ -88,21 +88,32 @@ func (parser *ConfigParser) ConnectPhysicalDevices() map[string]*evdev.InputDevi continue } - device, err := evdev.OpenByName(deviceConfig.DeviceName) + var infoName string + var device *evdev.InputDevice + var err error + + if deviceConfig.DevicePath != "" { + infoName = deviceConfig.DevicePath + device, err = evdev.Open(deviceConfig.DevicePath) + } else { + infoName = deviceConfig.DeviceName + device, err = evdev.OpenByName(deviceConfig.DeviceName) + } + if err != nil { - logger.LogError(err, "Failed to open physical device, skipping. Confirm the device name with 'evlist'. Watch out for spaces.") + logger.LogError(err, "Failed to open physical device, skipping. Confirm the device name or path with 'evinfo'") continue } if deviceConfig.Lock { - logger.LogDebugf("Locking device '%s'", deviceConfig.DeviceName) + logger.LogDebugf("Locking device '%s'", infoName) err := device.Grab() if err != nil { logger.LogError(err, "Failed to grab device for exclusive access") } } - logger.Log(fmt.Sprintf("Connected to '%s' as '%s'", deviceConfig.DeviceName, deviceConfig.Name)) + logger.Log(fmt.Sprintf("Connected to '%s' as '%s'", infoName, deviceConfig.Name)) deviceMap[deviceConfig.Name] = device } diff --git a/internal/config/schema.go b/internal/config/schema.go index b4675e0..1ea3527 100644 --- a/internal/config/schema.go +++ b/internal/config/schema.go @@ -19,7 +19,7 @@ type DeviceConfig struct { Name string `yaml:"name"` Type string `yaml:"type"` DeviceName string `yaml:"device_name,omitempty"` - Uuid string `yaml:"uuid,omitempty"` + DevicePath string `yaml:"device_path,omitempty"` Preset string `yaml:"preset,omitempty"` NumButtons int `yaml:"num_buttons,omitempty"` NumAxes int `yaml:"num_axes,omitempty"` @@ -64,7 +64,7 @@ func (dc *DeviceConfig) UnmarshalYAML(unmarshal func(data interface{}) error) er Name string Type string DeviceName string `yaml:"device_name"` - Uuid string + DevicePath string `yaml:"device_path"` Preset string NumButtons int `yaml:"num_buttons"` NumAxes int `yaml:"num_axes"` @@ -85,7 +85,7 @@ func (dc *DeviceConfig) UnmarshalYAML(unmarshal func(data interface{}) error) er Name: raw.Name, Type: raw.Type, DeviceName: raw.DeviceName, - Uuid: raw.Uuid, + DevicePath: raw.DevicePath, Preset: raw.Preset, NumButtons: raw.NumButtons, NumAxes: raw.NumAxes, diff --git a/readme.md b/readme.md index 937bf13..5872c24 100644 --- a/readme.md +++ b/readme.md @@ -48,6 +48,8 @@ If you are on Arch or an Arch-based distro, you can get the latest Joyful releas yay -S joyful ``` +You may also need to add the user(s) who will be running joyful to the `input` group. + ### Manual Install To build joyful manually, first use your distribution's package manager to install the following dependencies: