mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 11:18:54 +01:00
parent
cbf13630a9
commit
f5b68e5dc1
496
internal/gamepad/api_windows.go
Normal file
496
internal/gamepad/api_windows.go
Normal 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
|
||||
}
|
@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (darwin && !ios) || js
|
||||
// +build darwin,!ios js
|
||||
//go:build (darwin && !ios) || js || windows
|
||||
// +build darwin,!ios js windows
|
||||
|
||||
package gamepad
|
||||
|
||||
@ -57,8 +57,8 @@ func AppendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
|
||||
}
|
||||
|
||||
// Update is concurrent-safe.
|
||||
func Update() {
|
||||
theGamepads.update()
|
||||
func Update() error {
|
||||
return theGamepads.update()
|
||||
}
|
||||
|
||||
// Get is concurrent-safe.
|
||||
@ -78,21 +78,30 @@ func (g *gamepads) appendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
|
||||
return ids
|
||||
}
|
||||
|
||||
func (g *gamepads) update() {
|
||||
func (g *gamepads) update() error {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if !g.inited {
|
||||
g.nativeGamepads.init()
|
||||
if err := g.nativeGamepads.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
g.inited = true
|
||||
}
|
||||
|
||||
g.nativeGamepads.update()
|
||||
if err := g.nativeGamepads.update(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, gp := range g.gamepads {
|
||||
if gp != nil {
|
||||
gp.update()
|
||||
if gp == nil {
|
||||
continue
|
||||
}
|
||||
if err := gp.update(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gamepads) get(id driver.GamepadID) *Gamepad {
|
||||
@ -156,11 +165,11 @@ type Gamepad struct {
|
||||
nativeGamepad
|
||||
}
|
||||
|
||||
func (g *Gamepad) update() {
|
||||
func (g *Gamepad) update(gamepads *gamepads) error {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
g.nativeGamepad.update()
|
||||
return g.nativeGamepad.update(gamepads)
|
||||
}
|
||||
|
||||
// Name is concurrent-safe.
|
||||
|
@ -18,6 +18,7 @@
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
@ -115,7 +116,7 @@ func (g *nativeGamepad) elementValue(e *element) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) update() {
|
||||
func (g *nativeGamepad) update(gamepads *gamepads) error {
|
||||
if cap(g.axisValues) < len(g.axes) {
|
||||
g.axisValues = make([]float64, len(g.axes))
|
||||
}
|
||||
@ -167,6 +168,8 @@ func (g *nativeGamepad) update() {
|
||||
g.hatValues[i] = hatStates[state]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) hasOwnStandardLayoutMapping() bool {
|
||||
@ -214,7 +217,7 @@ func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64,
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
||||
|
||||
func (g *nativeGamepads) init() {
|
||||
func (g *nativeGamepads) init() error {
|
||||
var dicts []C.CFDictionaryRef
|
||||
|
||||
page := C.kHIDPage_GenericDesktop
|
||||
@ -225,13 +228,13 @@ func (g *nativeGamepads) init() {
|
||||
} {
|
||||
pageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&page))
|
||||
if pageRef == 0 {
|
||||
panic("gamepad: CFNumberCreate returned nil")
|
||||
return errors.New("gamepad: CFNumberCreate returned nil")
|
||||
}
|
||||
defer C.CFRelease(C.CFTypeRef(pageRef))
|
||||
|
||||
usageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&usage))
|
||||
if usageRef == 0 {
|
||||
panic("gamepad: CFNumberCreate returned nil")
|
||||
return errors.New("gamepad: CFNumberCreate returned nil")
|
||||
}
|
||||
defer C.CFRelease(C.CFTypeRef(usageRef))
|
||||
|
||||
@ -249,7 +252,7 @@ func (g *nativeGamepads) init() {
|
||||
(*unsafe.Pointer)(unsafe.Pointer(&values[0])),
|
||||
C.CFIndex(len(keys)), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks)
|
||||
if dict == 0 {
|
||||
panic("gamepad: CFDictionaryCreate returned nil")
|
||||
return errors.New("gamepad: CFDictionaryCreate returned nil")
|
||||
}
|
||||
defer C.CFRelease(C.CFTypeRef(dict))
|
||||
|
||||
@ -260,13 +263,13 @@ func (g *nativeGamepads) init() {
|
||||
(*unsafe.Pointer)(unsafe.Pointer(&dicts[0])),
|
||||
C.CFIndex(len(dicts)), &C.kCFTypeArrayCallBacks)
|
||||
if matching == 0 {
|
||||
panic("gamepad: CFArrayCreateMutable returned nil")
|
||||
return errors.New("gamepad: CFArrayCreateMutable returned nil")
|
||||
}
|
||||
defer C.CFRelease(C.CFTypeRef(matching))
|
||||
|
||||
g.hidManager = C.IOHIDManagerCreate(C.kCFAllocatorDefault, C.kIOHIDOptionsTypeNone)
|
||||
if C.IOHIDManagerOpen(g.hidManager, C.kIOHIDOptionsTypeNone) != C.kIOReturnSuccess {
|
||||
panic("gamepad: IOHIDManagerOpen failed")
|
||||
return errors.New("gamepad: IOHIDManagerOpen failed")
|
||||
}
|
||||
|
||||
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.
|
||||
C.CFRunLoopRunInMode(C.kCFRunLoopDefaultMode, 0, 0 /* false */)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//export ebitenGamepadMatchingCallback
|
||||
@ -293,7 +298,7 @@ func ebitenGamepadRemovalCallback(ctx unsafe.Pointer, res C.IOReturn, sender uns
|
||||
theGamepads.devicesToRemove = append(theGamepads.devicesToRemove, device)
|
||||
}
|
||||
|
||||
func (g *nativeGamepads) update() {
|
||||
func (g *nativeGamepads) update() error {
|
||||
theGamepads.devicesM.Lock()
|
||||
defer theGamepads.devicesM.Unlock()
|
||||
|
||||
@ -307,6 +312,7 @@ func (g *nativeGamepads) update() {
|
||||
}
|
||||
g.devicesToAdd = g.devicesToAdd[:0]
|
||||
g.devicesToRemove = g.devicesToRemove[:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepads) addDevice(device C.IOHIDDeviceRef) {
|
||||
|
@ -31,10 +31,11 @@ type nativeGamepads 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.
|
||||
|
||||
defer func() {
|
||||
@ -45,12 +46,12 @@ func (g *nativeGamepads) update() {
|
||||
|
||||
nav := js.Global().Get("navigator")
|
||||
if !nav.Truthy() {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
gps := nav.Call("getGamepads")
|
||||
if !gps.Truthy() {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
l := gps.Length()
|
||||
@ -90,6 +91,8 @@ func (g *nativeGamepads) update() {
|
||||
_, ok := g.indices[gamepad.index]
|
||||
return !ok
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepad struct {
|
||||
@ -106,7 +109,8 @@ func (g *nativeGamepad) hasOwnStandardLayoutMapping() bool {
|
||||
return g.mapping == "standard"
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) update() {
|
||||
func (g *nativeGamepad) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) axisNum() int {
|
||||
|
760
internal/gamepad/gamepad_windows.go
Normal file
760
internal/gamepad/gamepad_windows.go
Normal 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)
|
||||
}
|
@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !android && !js && !darwin
|
||||
// +build !android,!js,!darwin
|
||||
//go:build !android && !js && !darwin && !windows
|
||||
// +build !android,!js,!darwin,!windows
|
||||
|
||||
package glfw
|
||||
|
||||
@ -28,7 +28,7 @@ import (
|
||||
type nativeGamepads struct {
|
||||
}
|
||||
|
||||
func (i *Input) updateGamepads() {
|
||||
func (i *Input) updateGamepads() error {
|
||||
for id := glfw.Joystick(0); id < glfw.Joystick(len(i.gamepads)); id++ {
|
||||
i.gamepads[id].valid = false
|
||||
if !id.Present() {
|
||||
@ -79,6 +79,8 @@ func (i *Input) updateGamepads() {
|
||||
i.gamepads[id].guid = id.GetGUID()
|
||||
i.gamepads[id].name = id.GetName()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
|
||||
|
@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build darwin && !ios
|
||||
// +build darwin,!ios
|
||||
//go:build (darwin && !ios) || windows
|
||||
// +build darwin,!ios windows
|
||||
|
||||
package glfw
|
||||
|
||||
@ -27,8 +27,8 @@ import (
|
||||
type nativeGamepads struct{}
|
||||
|
||||
// updateGamepads must be called on the main thread.
|
||||
func (i *Input) updateGamepads() {
|
||||
gamepadpkg.Update()
|
||||
func (i *Input) updateGamepads() error {
|
||||
return gamepadpkg.Update()
|
||||
}
|
||||
|
||||
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
|
||||
|
@ -170,7 +170,7 @@ var glfwMouseButtonToMouseButton = map[glfw.MouseButton]driver.MouseButton{
|
||||
}
|
||||
|
||||
// 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()
|
||||
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.updateGamepads()
|
||||
return i.updateGamepads()
|
||||
}
|
||||
|
@ -1014,7 +1014,9 @@ func (u *UserInterface) update() (float64, float64, error) {
|
||||
} else {
|
||||
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() {
|
||||
if err := hooks.SuspendAudio(); err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user