internal/gamepad: implement for Windows

Updates #1452
Updates #1764
This commit is contained in:
Hajime Hoshi 2022-01-23 04:20:54 +09:00
parent cbf13630a9
commit f5b68e5dc1
9 changed files with 1313 additions and 34 deletions

View File

@ -0,0 +1,496 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gamepad
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
_DI_OK = 0
_DI_NOEFFECT = _SI_FALSE
_DI_PROPNOEFFECT = _SI_FALSE
_DI_DEGREES = 100
_DI8DEVCLASS_GAMECTRL = 4
_DIDFT_ABSAXIS = 0x00000002
_DIDFT_AXIS = 0x00000003
_DIDFT_BUTTON = 0x0000000C
_DIDFT_POV = 0x00000010
_DIDFT_OPTIONAL = 0x80000000
_DIDFT_ANYINSTANCE = 0x00FFFF00
_DIDOI_ASPECTPOSITION = 0x00000100
_DIEDFL_ALLDEVICES = 0x00000000
_DIENUM_STOP = 0
_DIENUM_CONTINUE = 1
_DIERR_INPUTLOST = windows.SEVERITY_ERROR<<31 | windows.FACILITY_WIN32<<16 | windows.ERROR_READ_FAULT
_DIERR_NOTACQUIRED = windows.SEVERITY_ERROR<<31 | windows.FACILITY_WIN32<<16 | windows.ERROR_INVALID_ACCESS
_DIJOFS_X = uint32(unsafe.Offsetof(diJoyState{}.lX))
_DIJOFS_Y = uint32(unsafe.Offsetof(diJoyState{}.lY))
_DIJOFS_Z = uint32(unsafe.Offsetof(diJoyState{}.lZ))
_DIJOFS_RX = uint32(unsafe.Offsetof(diJoyState{}.lRx))
_DIJOFS_RY = uint32(unsafe.Offsetof(diJoyState{}.lRy))
_DIJOFS_RZ = uint32(unsafe.Offsetof(diJoyState{}.lRz))
_DIPH_DEVICE = 0
_DIPH_BYID = 2
_DIPROP_AXISMODE = 2
_DIPROP_RANGE = 4
_DIPROPAXISMODE_ABS = 0
_DIRECTINPUT_VERSION = 0x0800
_GWL_WNDPROC = -4
_MAX_PATH = 260
_RIDI_DEVICEINFO = 0x2000000b
_RIDI_DEVICENAME = 0x20000007
_RIM_TYPEHID = 2
_SI_FALSE = 1
_WM_DEVICECHANGE = 0x0219
_XINPUT_CAPS_WIRELESS = 0x0002
_XINPUT_DEVSUBTYPE_GAMEPAD = 0x01
_XINPUT_DEVSUBTYPE_WHEEL = 0x02
_XINPUT_DEVSUBTYPE_ARCADE_STICK = 0x03
_XINPUT_DEVSUBTYPE_FLIGHT_STICK = 0x04
_XINPUT_DEVSUBTYPE_DANCE_PAD = 0x05
_XINPUT_DEVSUBTYPE_GUITAR = 0x06
_XINPUT_DEVSUBTYPE_DRUM_KIT = 0x08
_XINPUT_GAMEPAD_DPAD_UP = 0x0001
_XINPUT_GAMEPAD_DPAD_DOWN = 0x0002
_XINPUT_GAMEPAD_DPAD_LEFT = 0x0004
_XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008
_XINPUT_GAMEPAD_START = 0x0010
_XINPUT_GAMEPAD_BACK = 0x0020
_XINPUT_GAMEPAD_LEFT_THUMB = 0x0040
_XINPUT_GAMEPAD_RIGHT_THUMB = 0x0080
_XINPUT_GAMEPAD_LEFT_SHOULDER = 0x0100
_XINPUT_GAMEPAD_RIGHT_SHOULDER = 0x0200
_XINPUT_GAMEPAD_A = 0x1000
_XINPUT_GAMEPAD_B = 0x2000
_XINPUT_GAMEPAD_X = 0x4000
_XINPUT_GAMEPAD_Y = 0x8000
)
func diDftGetType(n uint32) byte {
return byte(n)
}
func diJofsSlider(n int) uint32 {
return uint32(unsafe.Offsetof(diJoyState{}.rglSlider) + uintptr(n)*unsafe.Sizeof(int32(0)))
}
func diJofsPOV(n int) uint32 {
return uint32(unsafe.Offsetof(diJoyState{}.rgdwPOV) + uintptr(n)*unsafe.Sizeof(uint32(0)))
}
func diJofsButton(n int) uint32 {
return uint32(unsafe.Offsetof(diJoyState{}.rgbButtons) + uintptr(n))
}
var (
iidIDirectInput8W = windows.GUID{0xbf798031, 0x483a, 0x4da2, [...]byte{0xaa, 0x99, 0x5d, 0x64, 0xed, 0x36, 0x97, 0x00}}
guidXAxis = windows.GUID{0xa36d02e0, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
guidYAxis = windows.GUID{0xa36d02e1, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
guidZAxis = windows.GUID{0xa36d02e2, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
guidRxAxis = windows.GUID{0xa36d02f4, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
guidRyAxis = windows.GUID{0xa36d02f5, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
guidRzAxis = windows.GUID{0xa36d02e3, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
guidSlider = windows.GUID{0xa36d02e4, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
guidPOV = windows.GUID{0xa36d02f2, 0xc9f3, 0x11cf, [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
)
var (
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
user32 = windows.NewLazySystemDLL("user32.dll")
procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
procCallWindowProcW = user32.NewProc("CallWindowProcW")
procGetActiveWindow = user32.NewProc("GetActiveWindow")
procGetRawInputDeviceInfoW = user32.NewProc("GetRawInputDeviceInfoW")
procGetRawInputDeviceList = user32.NewProc("GetRawInputDeviceList")
procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW")
)
func getCurrentThreadId() uint32 {
t, _, _ := procGetCurrentThreadId.Call()
return uint32(t)
}
func getModuleHandleW() (uintptr, error) {
m, _, e := procGetModuleHandleW.Call(0)
if m == 0 {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: GetModuleHandleW failed: %w", e)
}
return 0, fmt.Errorf("gamepad: GetModuleHandleW returned 0")
}
return m, nil
}
func callWindowProcW(lpPrevWndFunc uintptr, hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr {
r, _, _ := procCallWindowProcW.Call(lpPrevWndFunc, hWnd, uintptr(msg), wParam, lParam)
return r
}
func getActiveWindow() uintptr {
h, _, _ := procGetActiveWindow.Call()
return h
}
func getRawInputDeviceInfoW(hDevice windows.Handle, uiCommand uint32, pData unsafe.Pointer, pcb *uint32) (uint32, error) {
r, _, e := procGetRawInputDeviceInfoW.Call(uintptr(hDevice), uintptr(uiCommand), uintptr(pData), uintptr(unsafe.Pointer(pcb)))
if uint32(r) == ^uint32(0) {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: GetRawInputDeviceInfoW failed: %w", e)
}
return 0, fmt.Errorf("gamepad: GetRawInputDeviceInfoW returned -1")
}
return uint32(r), nil
}
func getRawInputDeviceList(pRawInputDeviceList *rawInputDeviceList, puiNumDevices *uint32) (uint32, error) {
r, _, e := procGetRawInputDeviceList.Call(uintptr(unsafe.Pointer(pRawInputDeviceList)), uintptr(unsafe.Pointer(puiNumDevices)), unsafe.Sizeof(rawInputDeviceList{}))
if uint32(r) == ^uint32(0) {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: GetRawInputDeviceList failed: %w", e)
}
return 0, fmt.Errorf("gamepad: GetRawInputDeviceList returned -1")
}
return uint32(r), nil
}
func setWindowLongPtrW(hWnd uintptr, nIndex int32, dwNewLong uintptr) (uintptr, error) {
h, _, e := procSetWindowLongPtrW.Call(hWnd, uintptr(nIndex), dwNewLong)
if h == 0 {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: SetWindowLongPtrW failed: %w", e)
}
return 0, fmt.Errorf("gamepad: SetWindowLongPtrW returned 0")
}
return h, nil
}
type directInputError uint32
func (d directInputError) Error() string {
return fmt.Sprintf("DirectInput error: %d", d)
}
type diDataFormat struct {
dwSize uint32
dwObjSize uint32
dwFlags uint32
dwDataSize uint32
dwNumObjs uint32
rgodf *diObjectDataFormat
}
type diDevCaps struct {
dwSize uint32
dwFlags uint32
dwDevType uint32
dwAxes uint32
dwButtons uint32
dwPOVs uint32
dwFFSamplePeriod uint32
dwFFMinTimeResolution uint32
dwFirmwareRevision uint32
dwHardwareRevision uint32
dwFFDriverVersion uint32
}
type diDeviceInstanceW struct {
dwSize uint32
guidInstance windows.GUID
guidProduct windows.GUID
dwDevType uint32
tszInstanceName [_MAX_PATH]uint16
tszProductName [_MAX_PATH]uint16
guidFFDriver windows.GUID
wUsagePage uint16
wUsage uint16
}
type diDeviceObjectInstanceW struct {
dwSize uint32
guidType windows.GUID
dwOfs uint32
dwType uint32
dwFlags uint32
tszName [_MAX_PATH]uint16
dwFFMaxForce uint32
dwFFForceResolution uint32
wCollectionNumber uint16
wDesignatorIndex uint16
wUsagePage uint16
wUsage uint16
dwDimension uint32
wExponent uint16
wReserved uint16
}
type diJoyState struct {
lX int32
lY int32
lZ int32
lRx int32
lRy int32
lRz int32
rglSlider [2]int32
rgdwPOV [4]uint32
rgbButtons [32]byte
}
type diObjectDataFormat struct {
pguid *windows.GUID
dwOfs uint32
dwType uint32
dwFlags uint32
}
type diPropDword struct {
diph diPropHeader
dwData uint32
}
type diPropHeader struct {
dwSize uint32
dwHeaderSize uint32
dwObj uint32
dwHow uint32
}
type diPropRange struct {
diph diPropHeader
lMin int32
lMax int32
}
type iDirectInput8W struct {
vtbl *iDirectInput8W_Vtbl
}
type iDirectInput8W_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
CreateDevice uintptr
EnumDevices uintptr
GetDeviceStatus uintptr
RunControlPanel uintptr
Initialize uintptr
FindDevice uintptr
EnumDevicesBySemantics uintptr
ConfigureDevices uintptr
}
func (d *iDirectInput8W) CreateDevice(rguid *windows.GUID, lplpDirectInputDevice **iDirectInputDevice8W, pUnkOuter unsafe.Pointer) error {
r, _, _ := syscall.Syscall6(d.vtbl.CreateDevice, 4,
uintptr(unsafe.Pointer(d)),
uintptr(unsafe.Pointer(rguid)), uintptr(unsafe.Pointer(lplpDirectInputDevice)), uintptr(pUnkOuter),
0, 0)
if r != _DI_OK {
return fmt.Errorf("gamepad: IDirectInput8::CreateDevice failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (d *iDirectInput8W) EnumDevices(dwDevType uint32, lpCallback uintptr, pvRef unsafe.Pointer, dwFlags uint32) error {
r, _, _ := syscall.Syscall6(d.vtbl.EnumDevices, 5,
uintptr(unsafe.Pointer(d)),
uintptr(dwDevType), lpCallback, uintptr(pvRef), uintptr(dwFlags),
0)
if r != _DI_OK {
return fmt.Errorf("gamepad: IDirectInput8::EnumDevices failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
type iDirectInputDevice8W struct {
vtbl *iDirectInputDevice8W_Vtbl
}
type iDirectInputDevice8W_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
GetCapabilities uintptr
EnumObjects uintptr
GetProperty uintptr
SetProperty uintptr
Acquire uintptr
Unacquire uintptr
GetDeviceState uintptr
GetDeviceData uintptr
SetDataFormat uintptr
SetEventNotification uintptr
SetCooperativeLevel uintptr
GetObjectInfo uintptr
GetDeviceInfo uintptr
RunControlPanel uintptr
Initialize uintptr
CreateEffect uintptr
EnumEffects uintptr
GetEffectInfo uintptr
GetForceFeedbackState uintptr
SendForceFeedbackCommand uintptr
EnumCreatedEffectObjects uintptr
Escape uintptr
Poll uintptr
SendDeviceData uintptr
EnumEffectsInFile uintptr
WriteEffectToFile uintptr
BuildActionMap uintptr
SetActionMap uintptr
GetImageInfo uintptr
}
func (d *iDirectInputDevice8W) Acquire() error {
r, _, _ := syscall.Syscall(d.vtbl.Acquire, 1, uintptr(unsafe.Pointer(d)), 0, 0)
if r != _DI_OK && r != _SI_FALSE {
return fmt.Errorf("gamepad: IDirectInputDevice8::Acquire failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (d *iDirectInputDevice8W) EnumObjects(lpCallback uintptr, pvRef unsafe.Pointer, dwFlags uint32) error {
r, _, _ := syscall.Syscall6(d.vtbl.EnumObjects, 4,
uintptr(unsafe.Pointer(d)),
lpCallback, uintptr(pvRef), uintptr(dwFlags),
0, 0)
if r != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::EnumObjects failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (d *iDirectInputDevice8W) GetCapabilities(lpDIDevCaps *diDevCaps) error {
r, _, _ := syscall.Syscall(d.vtbl.GetCapabilities, 2, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(lpDIDevCaps)), 0)
if r != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::GetCapabilities failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (d *iDirectInputDevice8W) GetDeviceState(cbData uint32, lpvData unsafe.Pointer) error {
r, _, _ := syscall.Syscall(d.vtbl.GetDeviceState, 3, uintptr(unsafe.Pointer(d)), uintptr(cbData), uintptr(lpvData))
if r != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::GetDeviceState failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (d *iDirectInputDevice8W) Poll() error {
r, _, _ := syscall.Syscall(d.vtbl.Poll, 1, uintptr(unsafe.Pointer(d)), 0, 0)
if r != _DI_OK && r != _DI_NOEFFECT {
return fmt.Errorf("gamepad: IDirectInputDevice8::Poll failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (d *iDirectInputDevice8W) Release() uint32 {
r, _, _ := syscall.Syscall(d.vtbl.Release, 1, uintptr(unsafe.Pointer(d)), 0, 0)
return uint32(r)
}
func (d *iDirectInputDevice8W) SetDataFormat(lpdf *diDataFormat) error {
r, _, _ := syscall.Syscall(d.vtbl.SetDataFormat, 2, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(lpdf)), 0)
if r != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::SetDataFormat failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (d *iDirectInputDevice8W) SetProperty(rguidProp uintptr, pdiph *diPropHeader) error {
r, _, _ := syscall.Syscall(d.vtbl.SetProperty, 3, uintptr(unsafe.Pointer(d)), rguidProp, uintptr(unsafe.Pointer(pdiph)))
if r != _DI_OK && r != _DI_PROPNOEFFECT {
return fmt.Errorf("gamepad: IDirectInputDevice8::SetProperty failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
type ridDeviceInfo struct {
cbSize uint32
dwType uint32
hid ridDeviceInfoHID // Originally, this member is a union.
}
type ridDeviceInfoHID struct {
dwVendorId uint32
dwProductId uint32
dwVersionNumber uint32
usUsagePage uint16
usUsage uint16
_ uint32 // A padding adjusting with the size of RID_DEVICE_INFO_KEYBOARD
_ uint32 // A padding adjusting with the size of RID_DEVICE_INFO_KEYBOARD
}
type rawInputDeviceList struct {
hDevice windows.Handle
dwType uint32
}
type xinputCapabilities struct {
typ byte
subType byte
flags uint16
gamepad xinputGamepad
vibration xinputVibration
}
type xinputGamepad struct {
wButtons uint16
bLeftTrigger byte
bRightTrigger byte
sThumbLX int16
sThumbLY int16
sThumbRX int16
sThumbRY int16
}
type xinputState struct {
dwPacketNumber uint32
Gamepad xinputGamepad
}
type xinputVibration struct {
wLeftMotorSpeed uint16
wRightMotorSpeed uint16
}

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build (darwin && !ios) || js //go:build (darwin && !ios) || js || windows
// +build darwin,!ios js // +build darwin,!ios js windows
package gamepad package gamepad
@ -57,8 +57,8 @@ func AppendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
} }
// Update is concurrent-safe. // Update is concurrent-safe.
func Update() { func Update() error {
theGamepads.update() return theGamepads.update()
} }
// Get is concurrent-safe. // Get is concurrent-safe.
@ -78,21 +78,30 @@ func (g *gamepads) appendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
return ids return ids
} }
func (g *gamepads) update() { func (g *gamepads) update() error {
g.m.Lock() g.m.Lock()
defer g.m.Unlock() defer g.m.Unlock()
if !g.inited { if !g.inited {
g.nativeGamepads.init() if err := g.nativeGamepads.init(); err != nil {
return err
}
g.inited = true g.inited = true
} }
g.nativeGamepads.update() if err := g.nativeGamepads.update(); err != nil {
return err
}
for _, gp := range g.gamepads { for _, gp := range g.gamepads {
if gp != nil { if gp == nil {
gp.update() continue
}
if err := gp.update(g); err != nil {
return err
} }
} }
return nil
} }
func (g *gamepads) get(id driver.GamepadID) *Gamepad { func (g *gamepads) get(id driver.GamepadID) *Gamepad {
@ -156,11 +165,11 @@ type Gamepad struct {
nativeGamepad nativeGamepad
} }
func (g *Gamepad) update() { func (g *Gamepad) update(gamepads *gamepads) error {
g.m.Lock() g.m.Lock()
defer g.m.Unlock() defer g.m.Unlock()
g.nativeGamepad.update() return g.nativeGamepad.update(gamepads)
} }
// Name is concurrent-safe. // Name is concurrent-safe.

View File

@ -18,6 +18,7 @@
package gamepad package gamepad
import ( import (
"errors"
"fmt" "fmt"
"sort" "sort"
"sync" "sync"
@ -115,7 +116,7 @@ func (g *nativeGamepad) elementValue(e *element) int {
return 0 return 0
} }
func (g *nativeGamepad) update() { func (g *nativeGamepad) update(gamepads *gamepads) error {
if cap(g.axisValues) < len(g.axes) { if cap(g.axisValues) < len(g.axes) {
g.axisValues = make([]float64, len(g.axes)) g.axisValues = make([]float64, len(g.axes))
} }
@ -167,6 +168,8 @@ func (g *nativeGamepad) update() {
g.hatValues[i] = hatStates[state] g.hatValues[i] = hatStates[state]
} }
} }
return nil
} }
func (g *nativeGamepad) hasOwnStandardLayoutMapping() bool { func (g *nativeGamepad) hasOwnStandardLayoutMapping() bool {
@ -214,7 +217,7 @@ func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64,
// TODO: Implement this (#1452) // TODO: Implement this (#1452)
} }
func (g *nativeGamepads) init() { func (g *nativeGamepads) init() error {
var dicts []C.CFDictionaryRef var dicts []C.CFDictionaryRef
page := C.kHIDPage_GenericDesktop page := C.kHIDPage_GenericDesktop
@ -225,13 +228,13 @@ func (g *nativeGamepads) init() {
} { } {
pageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&page)) pageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&page))
if pageRef == 0 { if pageRef == 0 {
panic("gamepad: CFNumberCreate returned nil") return errors.New("gamepad: CFNumberCreate returned nil")
} }
defer C.CFRelease(C.CFTypeRef(pageRef)) defer C.CFRelease(C.CFTypeRef(pageRef))
usageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&usage)) usageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&usage))
if usageRef == 0 { if usageRef == 0 {
panic("gamepad: CFNumberCreate returned nil") return errors.New("gamepad: CFNumberCreate returned nil")
} }
defer C.CFRelease(C.CFTypeRef(usageRef)) defer C.CFRelease(C.CFTypeRef(usageRef))
@ -249,7 +252,7 @@ func (g *nativeGamepads) init() {
(*unsafe.Pointer)(unsafe.Pointer(&values[0])), (*unsafe.Pointer)(unsafe.Pointer(&values[0])),
C.CFIndex(len(keys)), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) C.CFIndex(len(keys)), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks)
if dict == 0 { if dict == 0 {
panic("gamepad: CFDictionaryCreate returned nil") return errors.New("gamepad: CFDictionaryCreate returned nil")
} }
defer C.CFRelease(C.CFTypeRef(dict)) defer C.CFRelease(C.CFTypeRef(dict))
@ -260,13 +263,13 @@ func (g *nativeGamepads) init() {
(*unsafe.Pointer)(unsafe.Pointer(&dicts[0])), (*unsafe.Pointer)(unsafe.Pointer(&dicts[0])),
C.CFIndex(len(dicts)), &C.kCFTypeArrayCallBacks) C.CFIndex(len(dicts)), &C.kCFTypeArrayCallBacks)
if matching == 0 { if matching == 0 {
panic("gamepad: CFArrayCreateMutable returned nil") return errors.New("gamepad: CFArrayCreateMutable returned nil")
} }
defer C.CFRelease(C.CFTypeRef(matching)) defer C.CFRelease(C.CFTypeRef(matching))
g.hidManager = C.IOHIDManagerCreate(C.kCFAllocatorDefault, C.kIOHIDOptionsTypeNone) g.hidManager = C.IOHIDManagerCreate(C.kCFAllocatorDefault, C.kIOHIDOptionsTypeNone)
if C.IOHIDManagerOpen(g.hidManager, C.kIOHIDOptionsTypeNone) != C.kIOReturnSuccess { if C.IOHIDManagerOpen(g.hidManager, C.kIOHIDOptionsTypeNone) != C.kIOReturnSuccess {
panic("gamepad: IOHIDManagerOpen failed") return errors.New("gamepad: IOHIDManagerOpen failed")
} }
C.IOHIDManagerSetDeviceMatchingMultiple(g.hidManager, matching) C.IOHIDManagerSetDeviceMatchingMultiple(g.hidManager, matching)
@ -277,6 +280,8 @@ func (g *nativeGamepads) init() {
// Execute the run loop once in order to register any initially-attached gamepads. // Execute the run loop once in order to register any initially-attached gamepads.
C.CFRunLoopRunInMode(C.kCFRunLoopDefaultMode, 0, 0 /* false */) C.CFRunLoopRunInMode(C.kCFRunLoopDefaultMode, 0, 0 /* false */)
return nil
} }
//export ebitenGamepadMatchingCallback //export ebitenGamepadMatchingCallback
@ -293,7 +298,7 @@ func ebitenGamepadRemovalCallback(ctx unsafe.Pointer, res C.IOReturn, sender uns
theGamepads.devicesToRemove = append(theGamepads.devicesToRemove, device) theGamepads.devicesToRemove = append(theGamepads.devicesToRemove, device)
} }
func (g *nativeGamepads) update() { func (g *nativeGamepads) update() error {
theGamepads.devicesM.Lock() theGamepads.devicesM.Lock()
defer theGamepads.devicesM.Unlock() defer theGamepads.devicesM.Unlock()
@ -307,6 +312,7 @@ func (g *nativeGamepads) update() {
} }
g.devicesToAdd = g.devicesToAdd[:0] g.devicesToAdd = g.devicesToAdd[:0]
g.devicesToRemove = g.devicesToRemove[:0] g.devicesToRemove = g.devicesToRemove[:0]
return nil
} }
func (g *nativeGamepads) addDevice(device C.IOHIDDeviceRef) { func (g *nativeGamepads) addDevice(device C.IOHIDDeviceRef) {

View File

@ -31,10 +31,11 @@ type nativeGamepads struct {
indices map[int]struct{} indices map[int]struct{}
} }
func (g *nativeGamepads) init() { func (g *nativeGamepads) init() error {
return nil
} }
func (g *nativeGamepads) update() { func (g *nativeGamepads) update() error {
// TODO: Use the gamepad events instead of navigator.getGamepads after go2cpp is removed. // TODO: Use the gamepad events instead of navigator.getGamepads after go2cpp is removed.
defer func() { defer func() {
@ -45,12 +46,12 @@ func (g *nativeGamepads) update() {
nav := js.Global().Get("navigator") nav := js.Global().Get("navigator")
if !nav.Truthy() { if !nav.Truthy() {
return return nil
} }
gps := nav.Call("getGamepads") gps := nav.Call("getGamepads")
if !gps.Truthy() { if !gps.Truthy() {
return return nil
} }
l := gps.Length() l := gps.Length()
@ -90,6 +91,8 @@ func (g *nativeGamepads) update() {
_, ok := g.indices[gamepad.index] _, ok := g.indices[gamepad.index]
return !ok return !ok
}) })
return nil
} }
type nativeGamepad struct { type nativeGamepad struct {
@ -106,7 +109,8 @@ func (g *nativeGamepad) hasOwnStandardLayoutMapping() bool {
return g.mapping == "standard" return g.mapping == "standard"
} }
func (g *nativeGamepad) update() { func (g *nativeGamepad) update(gamepads *gamepads) error {
return nil
} }
func (g *nativeGamepad) axisNum() int { func (g *nativeGamepad) axisNum() int {

View File

@ -0,0 +1,760 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gamepad
import (
"errors"
"fmt"
"sort"
"strings"
"sync/atomic"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
type dinputObjectType int
const (
dinputObjectTypeAxis dinputObjectType = iota
dinputObjectTypeSlider
dinputObjectTypeButton
dinputObjectTypePOV
)
var dinputObjectDataFormats = []diObjectDataFormat{
{&guidXAxis, _DIJOFS_X, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidYAxis, _DIJOFS_Y, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidZAxis, _DIJOFS_Z, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidRxAxis, _DIJOFS_RX, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidRyAxis, _DIJOFS_RY, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidRzAxis, _DIJOFS_RZ, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidSlider, diJofsSlider(0), _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidSlider, diJofsSlider(1), _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&guidPOV, diJofsPOV(0), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{&guidPOV, diJofsPOV(1), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{&guidPOV, diJofsPOV(2), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{&guidPOV, diJofsPOV(3), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(0), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(1), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(2), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(3), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(4), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(5), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(6), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(7), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(8), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(9), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(10), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(11), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(12), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(13), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(14), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(15), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(16), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(17), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(18), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(19), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(20), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(21), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(22), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(23), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(24), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(25), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(26), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(27), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(28), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(29), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(30), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, diJofsButton(31), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
}
var xinputButtons = []uint16{
_XINPUT_GAMEPAD_A,
_XINPUT_GAMEPAD_B,
_XINPUT_GAMEPAD_X,
_XINPUT_GAMEPAD_Y,
_XINPUT_GAMEPAD_LEFT_SHOULDER,
_XINPUT_GAMEPAD_RIGHT_SHOULDER,
_XINPUT_GAMEPAD_BACK,
_XINPUT_GAMEPAD_START,
_XINPUT_GAMEPAD_LEFT_THUMB,
_XINPUT_GAMEPAD_RIGHT_THUMB,
}
type nativeGamepads struct {
gamepads *gamepads
dinput8 windows.Handle
dinput8API *iDirectInput8W
xinput windows.Handle
procDirectInput8Create uintptr
procXInputGetCapabilities uintptr
procXInputGetState uintptr
origWndProc uintptr
wndProcCallback uintptr
enumDevicesCallback uintptr
enumObjectsCallback uintptr
deviceChanged int32
err error
}
type nativeGamepad struct {
dinputDevice *iDirectInputDevice8W
dinputObjects []dinputObject
dinputGUID windows.GUID
dinputAxes []float64
dinputButtons []bool
dinputHats []int
xinputIndex int
xinputState xinputState
}
type dinputObject struct {
objectType dinputObjectType
index int
}
type enumObjectsContext struct {
device *iDirectInputDevice8W
objects []dinputObject
axisCount int
sliderCount int
buttonCount int
povCount int
}
func (g *nativeGamepads) init() error {
// As there is no guarantee that the DLL exists, NewLazySystemDLL is not available.
// TODO: Is there a 'system' version of LoadLibrary?
if h, err := windows.LoadLibrary("dinput8.dll"); err == nil {
g.dinput8 = h
p, err := windows.GetProcAddress(h, "DirectInput8Create")
if err != nil {
return err
}
g.procDirectInput8Create = p
}
// TODO: Loading xinput1_4.dll or xinput9_1_0.dll should be enough.
// See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/xinput_data_fetcher_win.cc;l=75-84;drc=643cdf61903e99f27c3d80daee67e217e9d280e0
for _, dll := range []string{
"xinput1_4.dll",
"xinput1_3.dll",
"xinput9_1_0.dll",
"xinput1_2.dll",
"xinput1_1.dll",
} {
if h, err := windows.LoadLibrary(dll); err == nil {
g.xinput = h
{
p, err := windows.GetProcAddress(h, "XInputGetCapabilities")
if err != nil {
return err
}
g.procXInputGetCapabilities = p
}
{
p, err := windows.GetProcAddress(h, "XInputGetState")
if err != nil {
return err
}
g.procXInputGetState = p
}
break
}
}
if g.dinput8 != 0 {
m, err := getModuleHandleW()
if err != nil {
return err
}
var api *iDirectInput8W
if err := g.directInput8Create(m, _DIRECTINPUT_VERSION, unsafe.Pointer(&iidIDirectInput8W), unsafe.Pointer(&api), nil); err != nil {
return err
}
g.dinput8API = api
if err := g.detectConnection(); err != nil {
return err
}
}
return nil
}
func (g *nativeGamepads) directInput8Create(hinst uintptr, dwVersion uint32, riidltf unsafe.Pointer, ppvOut unsafe.Pointer, punkOuter unsafe.Pointer) error {
r, _, _ := syscall.Syscall6(g.procDirectInput8Create, 5,
hinst, uintptr(dwVersion), uintptr(riidltf), uintptr(ppvOut), uintptr(punkOuter),
0)
if r != _DI_OK {
return fmt.Errorf("gamepad: DirectInput8Create failed: %w", directInputError(syscall.Errno(r)))
}
return nil
}
func (g *nativeGamepads) xinputGetCapabilities(dwUserIndex uint32, dwFlags uint32, pCapabilities *xinputCapabilities) error {
r, _, _ := syscall.Syscall(g.procXInputGetCapabilities, 3,
uintptr(dwUserIndex), uintptr(dwFlags), uintptr(unsafe.Pointer(pCapabilities)))
if e := syscall.Errno(r); e != windows.ERROR_SUCCESS {
return fmt.Errorf("gamepad: XInputGetCapabilities failed: %w", e)
}
return nil
}
func (g *nativeGamepads) xinputGetState(dwUserIndex uint32, pState *xinputState) error {
r, _, _ := syscall.Syscall(g.procXInputGetState, 2,
uintptr(dwUserIndex), uintptr(unsafe.Pointer(pState)), 0)
if e := syscall.Errno(r); e != windows.ERROR_SUCCESS {
return fmt.Errorf("gamepad: XInputGetCapabilities failed: %w", e)
}
return nil
}
func (g *nativeGamepads) detectConnection() error {
if g.dinput8 != 0 {
if g.enumDevicesCallback == 0 {
g.enumDevicesCallback = windows.NewCallback(g.dinput8EnumDevicesCallback)
}
if err := g.dinput8API.EnumDevices(_DI8DEVCLASS_GAMECTRL, g.enumDevicesCallback, nil, _DIEDFL_ALLDEVICES); err != nil {
return err
}
if g.err != nil {
return g.err
}
}
if g.xinput != 0 {
const xuserMaxCount = 4
for i := 0; i < xuserMaxCount; i++ {
if g.gamepads.find(func(g *Gamepad) bool {
return g.dinputDevice == nil && g.xinputIndex == i
}) != nil {
continue
}
var xic xinputCapabilities
if err := g.xinputGetCapabilities(uint32(i), 0, &xic); err != nil {
if !errors.Is(err, windows.ERROR_DEVICE_NOT_CONNECTED) {
return err
}
continue
}
sdlID := fmt.Sprintf("78696e707574%02x000000000000000000", xic.subType&0xff)
name := "Unknown XInput Device"
switch xic.subType {
case _XINPUT_DEVSUBTYPE_GAMEPAD:
if xic.flags&_XINPUT_CAPS_WIRELESS != 0 {
name = "Wireless Xbox Controller"
} else {
name = "Xbox Controller"
}
case _XINPUT_DEVSUBTYPE_WHEEL:
name = "XInput Wheel"
case _XINPUT_DEVSUBTYPE_ARCADE_STICK:
name = "XInput Arcade Stick"
case _XINPUT_DEVSUBTYPE_FLIGHT_STICK:
name = "XInput Flight Stick"
case _XINPUT_DEVSUBTYPE_DANCE_PAD:
name = "XInput Dance Pad"
case _XINPUT_DEVSUBTYPE_GUITAR:
name = "XInput Guitar"
case _XINPUT_DEVSUBTYPE_DRUM_KIT:
name = "XInput Drum Kit"
}
gp := g.gamepads.add(name, sdlID)
gp.xinputIndex = i
}
}
return nil
}
func (g *nativeGamepads) dinput8EnumDevicesCallback(lpddi *diDeviceInstanceW, pvRef unsafe.Pointer) uintptr {
if g.err != nil {
return _DIENUM_STOP
}
if g.gamepads.find(func(g *Gamepad) bool {
return g.dinputGUID == lpddi.guidInstance
}) != nil {
return _DIENUM_CONTINUE
}
s, err := supportsXInput(lpddi.guidProduct)
if err != nil {
g.err = err
return _DIENUM_STOP
}
if s {
return _DIENUM_CONTINUE
}
var device *iDirectInputDevice8W
if err := g.dinput8API.CreateDevice(&lpddi.guidInstance, &device, nil); err != nil {
g.err = err
return _DIENUM_STOP
}
dataFormat := diDataFormat{
dwSize: uint32(unsafe.Sizeof(diDataFormat{})),
dwObjSize: uint32(unsafe.Sizeof(diObjectDataFormat{})),
dwFlags: _DIDFT_ABSAXIS,
dwDataSize: uint32(unsafe.Sizeof(diJoyState{})),
dwNumObjs: uint32(len(dinputObjectDataFormats)),
rgodf: &dinputObjectDataFormats[0],
}
if err := device.SetDataFormat(&dataFormat); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
dc := diDevCaps{
dwSize: uint32(unsafe.Sizeof(diDevCaps{})),
}
if err := device.GetCapabilities(&dc); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
dipd := diPropDword{
diph: diPropHeader{
dwSize: uint32(unsafe.Sizeof(diPropDword{})),
dwHeaderSize: uint32(unsafe.Sizeof(diPropHeader{})),
dwHow: _DIPH_DEVICE,
},
dwData: _DIPROPAXISMODE_ABS,
}
if err := device.SetProperty(_DIPROP_AXISMODE, &dipd.diph); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
ctx := enumObjectsContext{
device: device,
}
if g.enumObjectsCallback == 0 {
g.enumObjectsCallback = windows.NewCallback(g.dinputDevice8EnumObjectsCallback)
}
if err := device.EnumObjects(g.enumObjectsCallback, unsafe.Pointer(&ctx), _DIDFT_AXIS|_DIDFT_BUTTON|_DIDFT_POV); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
sort.Slice(ctx.objects, func(i, j int) bool {
if ctx.objects[i].objectType != ctx.objects[j].objectType {
return ctx.objects[i].objectType < ctx.objects[j].objectType
}
return ctx.objects[i].index < ctx.objects[j].index
})
name := windows.UTF16ToString(lpddi.tszInstanceName[:])
var sdlID string
if string(lpddi.guidProduct.Data4[2:8]) == "PIDVID" {
// This seems different from the current SDL implementation.
// Probably guidProduct includes the vendor and the product information, but this works.
// From the game controller database, the 'version' part seems always 0.
sdlID = fmt.Sprintf("03000000%02x%02x0000%02x%02x000000000000",
byte(lpddi.guidProduct.Data1),
byte(lpddi.guidProduct.Data1>>8),
byte(lpddi.guidProduct.Data1>>16),
byte(lpddi.guidProduct.Data1>>24))
} else {
bs := []byte(name)
if len(bs) < 12 {
bs = append(bs, make([]byte, 12-len(bs))...)
}
sdlID = fmt.Sprintf("05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
}
gp := g.gamepads.add(name, sdlID)
gp.dinputDevice = device
gp.dinputObjects = ctx.objects
gp.dinputGUID = lpddi.guidInstance
gp.dinputAxes = make([]float64, ctx.axisCount+ctx.sliderCount)
gp.dinputButtons = make([]bool, ctx.buttonCount)
gp.dinputHats = make([]int, ctx.povCount)
return _DIENUM_CONTINUE
}
func supportsXInput(guid windows.GUID) (bool, error) {
var count uint32
if r, err := getRawInputDeviceList(nil, &count); err != nil {
return false, err
} else if r != 0 {
return false, nil
}
ridl := make([]rawInputDeviceList, count)
if _, err := getRawInputDeviceList(&ridl[0], &count); err != nil {
return false, err
}
for i := 0; i < int(count); i++ {
if ridl[i].dwType != _RIM_TYPEHID {
continue
}
rdi := ridDeviceInfo{
cbSize: uint32(unsafe.Sizeof(ridDeviceInfo{})),
}
size := uint32(unsafe.Sizeof(rdi))
if _, err := getRawInputDeviceInfoW(ridl[i].hDevice, _RIDI_DEVICEINFO, unsafe.Pointer(&rdi), &size); err != nil {
return false, err
}
if uint32(rdi.hid.dwVendorId)|(uint32(rdi.hid.dwProductId)<<16) != guid.Data1 {
continue
}
var name [256]uint16
size = uint32(unsafe.Sizeof(name))
if _, err := getRawInputDeviceInfoW(ridl[i].hDevice, _RIDI_DEVICENAME, unsafe.Pointer(&name[0]), &size); err != nil {
return false, err
}
if strings.Contains(windows.UTF16ToString(name[:]), "IG_") {
return true, nil
}
}
return false, nil
}
func (g *nativeGamepads) dinputDevice8EnumObjectsCallback(lpddoi *diDeviceObjectInstanceW, pvRef unsafe.Pointer) uintptr {
ctx := (*enumObjectsContext)(pvRef)
switch {
case diDftGetType(lpddoi.dwType)&_DIDFT_AXIS != 0:
var index int
switch lpddoi.guidType {
case guidSlider:
index = ctx.sliderCount
case guidXAxis:
index = 0
case guidYAxis:
index = 1
case guidZAxis:
index = 2
case guidRxAxis:
index = 3
case guidRyAxis:
index = 4
case guidRzAxis:
index = 5
default:
return _DIENUM_CONTINUE
}
dipr := diPropRange{
diph: diPropHeader{
dwSize: uint32(unsafe.Sizeof(diPropRange{})),
dwHeaderSize: uint32(unsafe.Sizeof(diPropHeader{})),
dwObj: lpddoi.dwType,
dwHow: _DIPH_BYID,
},
lMin: -32768,
lMax: 32767,
}
if err := ctx.device.SetProperty(_DIPROP_RANGE, &dipr.diph); err != nil {
return _DIENUM_CONTINUE
}
var objectType dinputObjectType
if lpddoi.guidType == guidSlider {
objectType = dinputObjectTypeSlider
ctx.sliderCount++
} else {
objectType = dinputObjectTypeAxis
ctx.axisCount++
}
ctx.objects = append(ctx.objects, dinputObject{
objectType: objectType,
index: index,
})
case diDftGetType(lpddoi.dwType)&_DIDFT_BUTTON != 0:
ctx.objects = append(ctx.objects, dinputObject{
objectType: dinputObjectTypeButton,
index: ctx.buttonCount,
})
ctx.buttonCount++
case diDftGetType(lpddoi.dwType)&_DIDFT_POV != 0:
ctx.objects = append(ctx.objects, dinputObject{
objectType: dinputObjectTypePOV,
index: ctx.povCount,
})
ctx.povCount++
}
return _DIENUM_CONTINUE
}
func (g *nativeGamepads) update() error {
if g.err != nil {
return g.err
}
if g.origWndProc == 0 {
if g.wndProcCallback == 0 {
g.wndProcCallback = windows.NewCallback(g.wndProc)
}
h, err := setWindowLongPtrW(getActiveWindow(), _GWL_WNDPROC, g.wndProcCallback)
if err != nil {
return err
}
g.origWndProc = h
}
if atomic.LoadInt32(&g.deviceChanged) != 0 {
if err := g.detectConnection(); err != nil {
g.err = err
}
atomic.StoreInt32(&g.deviceChanged, 0)
}
return nil
}
func (g *nativeGamepads) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) uintptr {
switch uMsg {
case _WM_DEVICECHANGE:
atomic.StoreInt32(&g.deviceChanged, 1)
}
return callWindowProcW(g.origWndProc, hWnd, uMsg, wParam, lParam)
}
func (*nativeGamepad) hasOwnStandardLayoutMapping() bool {
return false
}
func (g *nativeGamepad) usesDInput() bool {
return g.dinputDevice != nil
}
func (g *nativeGamepad) update(gamepads *gamepads) (err error) {
var disconnected bool
defer func() {
if !disconnected && err == nil {
return
}
gamepads.remove(func(gamepad *Gamepad) bool {
return &gamepad.nativeGamepad == g
})
}()
if g.usesDInput() {
if err := g.dinputDevice.Poll(); err != nil {
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
return err
}
}
var state diJoyState
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
return err
}
// Acquire can return an error just after a gamepad is disconnected. Ignore the error.
g.dinputDevice.Acquire()
if err := g.dinputDevice.Poll(); err != nil {
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
return err
}
}
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
return err
}
disconnected = true
return nil
}
}
var ai, bi, hi int
for _, obj := range g.dinputObjects {
switch obj.objectType {
case dinputObjectTypeAxis:
var v int32
switch obj.index {
case 0:
v = state.lX
case 1:
v = state.lY
case 2:
v = state.lZ
case 3:
v = state.lRx
case 4:
v = state.lRy
case 5:
v = state.lRz
}
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
ai++
case dinputObjectTypeSlider:
v := state.rglSlider[obj.index]
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
ai++
case dinputObjectTypeButton:
v := (state.rgbButtons[obj.index] & 0x80) != 0
g.dinputButtons[bi] = v
bi++
case dinputObjectTypePOV:
stateIndex := state.rgdwPOV[obj.index] / (45 * _DI_DEGREES)
v := hatCentered
switch stateIndex {
case 0:
v = hatUp
case 1:
v = hatRightUp
case 2:
v = hatRight
case 3:
v = hatRightDown
case 4:
v = hatDown
case 5:
v = hatLeftDown
case 6:
v = hatLeft
case 7:
v = hatLeftUp
}
g.dinputHats[hi] = v
hi++
}
}
return nil
}
var state xinputState
if err := gamepads.xinputGetState(uint32(g.xinputIndex), &state); err != nil {
if !errors.Is(err, windows.ERROR_DEVICE_NOT_CONNECTED) {
return err
}
disconnected = true
return nil
}
g.xinputState = state
return nil
}
func (g *nativeGamepad) axisNum() int {
if g.usesDInput() {
return len(g.dinputAxes)
}
return 6
}
func (g *nativeGamepad) buttonNum() int {
if g.usesDInput() {
return len(g.dinputButtons)
}
return len(xinputButtons)
}
func (g *nativeGamepad) hatNum() int {
if g.usesDInput() {
return len(g.dinputHats)
}
return 1
}
func (g *nativeGamepad) axisValue(axis int) float64 {
if g.usesDInput() {
if axis < 0 || axis >= len(g.dinputAxes) {
return 0
}
return g.dinputAxes[axis]
}
var v float64
switch axis {
case 0:
v = (float64(g.xinputState.Gamepad.sThumbLX) + 0.5) / 32767.5
case 1:
v = -(float64(g.xinputState.Gamepad.sThumbLY) + 0.5) / 32767.5
case 2:
v = (float64(g.xinputState.Gamepad.sThumbRX) + 0.5) / 32767.5
case 3:
v = -(float64(g.xinputState.Gamepad.sThumbRY) + 0.5) / 32767.5
case 4:
v = float64(g.xinputState.Gamepad.bLeftTrigger)/127.5 - 1.0
case 5:
v = float64(g.xinputState.Gamepad.bRightTrigger)/127.5 - 1.0
}
return v
}
func (g *nativeGamepad) isButtonPressed(button int) bool {
if g.usesDInput() {
if button < 0 || button >= len(g.dinputButtons) {
return false
}
return g.dinputButtons[button]
}
if button < 0 || button >= len(xinputButtons) {
return false
}
return g.xinputState.Gamepad.wButtons&xinputButtons[button] != 0
}
func (g *nativeGamepad) buttonValue(button int) float64 {
panic("gamepad: buttonValue is not implemented")
}
func (g *nativeGamepad) hatState(hat int) int {
if g.usesDInput() {
return g.dinputHats[hat]
}
if hat != 0 {
return 0
}
var v int
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_UP != 0 {
v |= hatUp
}
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_RIGHT != 0 {
v |= hatRight
}
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_DOWN != 0 {
v |= hatDown
}
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_LEFT != 0 {
v |= hatLeft
}
return v
}
func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !android && !js && !darwin //go:build !android && !js && !darwin && !windows
// +build !android,!js,!darwin // +build !android,!js,!darwin,!windows
package glfw package glfw
@ -28,7 +28,7 @@ import (
type nativeGamepads struct { type nativeGamepads struct {
} }
func (i *Input) updateGamepads() { func (i *Input) updateGamepads() error {
for id := glfw.Joystick(0); id < glfw.Joystick(len(i.gamepads)); id++ { for id := glfw.Joystick(0); id < glfw.Joystick(len(i.gamepads)); id++ {
i.gamepads[id].valid = false i.gamepads[id].valid = false
if !id.Present() { if !id.Present() {
@ -79,6 +79,8 @@ func (i *Input) updateGamepads() {
i.gamepads[id].guid = id.GetGUID() i.gamepads[id].guid = id.GetGUID()
i.gamepads[id].name = id.GetName() i.gamepads[id].name = id.GetName()
} }
return nil
} }
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID { func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build darwin && !ios //go:build (darwin && !ios) || windows
// +build darwin,!ios // +build darwin,!ios windows
package glfw package glfw
@ -27,8 +27,8 @@ import (
type nativeGamepads struct{} type nativeGamepads struct{}
// updateGamepads must be called on the main thread. // updateGamepads must be called on the main thread.
func (i *Input) updateGamepads() { func (i *Input) updateGamepads() error {
gamepadpkg.Update() return gamepadpkg.Update()
} }
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID { func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {

View File

@ -170,7 +170,7 @@ var glfwMouseButtonToMouseButton = map[glfw.MouseButton]driver.MouseButton{
} }
// update must be called from the main thread. // update must be called from the main thread.
func (i *Input) update(window *glfw.Window, context driver.UIContext) { func (i *Input) update(window *glfw.Window, context driver.UIContext) error {
i.ui.m.Lock() i.ui.m.Lock()
defer i.ui.m.Unlock() defer i.ui.m.Unlock()
@ -218,5 +218,5 @@ func (i *Input) update(window *glfw.Window, context driver.UIContext) {
i.cursorX, i.cursorY = int(cx), int(cy) i.cursorX, i.cursorY = int(cx), int(cy)
} }
i.updateGamepads() return i.updateGamepads()
} }

View File

@ -1014,7 +1014,9 @@ func (u *UserInterface) update() (float64, float64, error) {
} else { } else {
glfw.WaitEvents() glfw.WaitEvents()
} }
u.input.update(u.window, u.context) if err := u.input.update(u.window, u.context); err != nil {
return 0, 0, err
}
for !u.isRunnableOnUnfocused() && u.window.GetAttrib(glfw.Focused) == 0 && !u.window.ShouldClose() { for !u.isRunnableOnUnfocused() && u.window.GetAttrib(glfw.Focused) == 0 && !u.window.ShouldClose() {
if err := hooks.SuspendAudio(); err != nil { if err := hooks.SuspendAudio(); err != nil {