mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +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
|
// 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.
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
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
|
// 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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user