internal/gamepad: bug fix: GUID was not reliable as a unique identifier

Use a HID path as a gamepad unique identifier instead.

Closes #3046
This commit is contained in:
Hajime Hoshi 2024-07-20 17:23:03 +09:00
parent 3a6aaac5ac
commit 122877c265
2 changed files with 55 additions and 10 deletions

View File

@ -58,8 +58,9 @@ const (
_DIPH_DEVICE = 0
_DIPH_BYID = 2
_DIPROP_AXISMODE = 2
_DIPROP_RANGE = 4
_DIPROP_AXISMODE = 2
_DIPROP_GUIDANDPATH = 12
_DIPROP_RANGE = 4
_DIPROPAXISMODE_ABS = 0
@ -280,6 +281,12 @@ type _DIPROPDWORD struct {
dwData uint32
}
type _DIPROPGUIDANDPATH struct {
diph _DIPROPHEADER
guidClass windows.GUID
wszPath [_MAX_PATH]uint16
}
type _DIPROPHEADER struct {
dwSize uint32
dwHeaderSize uint32
@ -409,6 +416,14 @@ func (d *_IDirectInputDevice8W) GetDeviceState(cbData uint32, lpvData unsafe.Poi
return nil
}
func (d *_IDirectInputDevice8W) GetProperty(rguidProp uintptr, pdiph *_DIPROPHEADER) error {
r, _, _ := syscall.Syscall(d.vtbl.GetProperty, 3, uintptr(unsafe.Pointer(d)), rguidProp, uintptr(unsafe.Pointer(pdiph)))
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::GetProperty failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) Poll() error {
r, _, _ := syscall.Syscall(d.vtbl.Poll, 1, uintptr(unsafe.Pointer(d)), 0, 0)
if uint32(r) != _DI_OK && uint32(r) != _DI_NOEFFECT {

View File

@ -294,12 +294,6 @@ func (g *nativeGamepadsDesktop) dinput8EnumDevicesCallback(lpddi *_DIDEVICEINSTA
return _DIENUM_STOP
}
if gamepads.find(func(g *Gamepad) bool {
return g.native.(*nativeGamepadDesktop).dinputGUID == lpddi.guidInstance
}) != nil {
return _DIENUM_CONTINUE
}
s, err := supportsXInput(lpddi.guidProduct)
if err != nil {
g.err = err
@ -315,6 +309,42 @@ func (g *nativeGamepadsDesktop) dinput8EnumDevicesCallback(lpddi *_DIDEVICEINSTA
return _DIENUM_STOP
}
// lpddi.guidInstance is not relialable as a unique identity when the same multiple devices are connected (#3046).
// Use HID Path instead.
getDInputPath := func(device *_IDirectInputDevice8W) (string, error) {
var prop _DIPROPGUIDANDPATH
prop.diph.dwHeaderSize = uint32(unsafe.Sizeof(_DIPROPHEADER{}))
prop.diph.dwSize = uint32(unsafe.Sizeof(_DIPROPGUIDANDPATH{}))
if err := device.GetProperty(_DIPROP_GUIDANDPATH, &prop.diph); err != nil {
return "", err
}
return windows.UTF16ToString(prop.wszPath[:]), nil
}
dinputPath, err := getDInputPath(device)
if err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
var findErr error
if gamepads.find(func(g *Gamepad) bool {
path, err := getDInputPath(g.native.(*nativeGamepadDesktop).dinputDevice)
if err != nil {
findErr = err
return true
}
return path == dinputPath
}) != nil {
if findErr != nil {
g.err = findErr
device.Release()
return _DIENUM_STOP
}
device.Release()
return _DIENUM_CONTINUE
}
dataFormat := _DIDATAFORMAT{
dwSize: uint32(unsafe.Sizeof(_DIDATAFORMAT{})),
dwObjSize: uint32(unsafe.Sizeof(_DIOBJECTDATAFORMAT{})),
@ -395,7 +425,7 @@ func (g *nativeGamepadsDesktop) dinput8EnumDevicesCallback(lpddi *_DIDEVICEINSTA
gp.native = &nativeGamepadDesktop{
dinputDevice: device,
dinputObjects: ctx.objects,
dinputGUID: lpddi.guidInstance,
dinputPath: dinputPath,
dinputAxes: make([]float64, ctx.axisCount+ctx.sliderCount),
dinputButtons: make([]bool, ctx.buttonCount),
dinputHats: make([]int, ctx.povCount),
@ -562,7 +592,7 @@ func (g *nativeGamepadsDesktop) setNativeWindow(nativeWindow uintptr) {
type nativeGamepadDesktop struct {
dinputDevice *_IDirectInputDevice8W
dinputObjects []dinputObject
dinputGUID windows.GUID
dinputPath string
dinputAxes []float64
dinputButtons []bool
dinputHats []int