diff --git a/internal/gamepad/api_desktop_windows.go b/internal/gamepad/api_desktop_windows.go index 01f790fd0..ad2c3cc52 100644 --- a/internal/gamepad/api_desktop_windows.go +++ b/internal/gamepad/api_desktop_windows.go @@ -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 { diff --git a/internal/gamepad/gamepad_desktop_windows.go b/internal/gamepad/gamepad_desktop_windows.go index 024b99d88..893e4253b 100644 --- a/internal/gamepad/gamepad_desktop_windows.go +++ b/internal/gamepad/gamepad_desktop_windows.go @@ -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