vendor dependencies, make some changes to how input is done
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
// Copyright 2022 The Ebitengine 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 (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
type (
|
||||
_CFIndex int64
|
||||
_CFAllocatorRef uintptr
|
||||
_CFArrayRef uintptr
|
||||
_CFDictionaryRef uintptr
|
||||
_CFNumberRef uintptr
|
||||
_CFTypeRef uintptr
|
||||
_CFRunLoopRef uintptr
|
||||
_CFNumberType uintptr
|
||||
_CFStringRef uintptr
|
||||
_CFArrayCallBacks struct{}
|
||||
_CFDictionaryKeyCallBacks struct{}
|
||||
_CFDictionaryValueCallBacks struct{}
|
||||
_CFRunLoopRunResult int32
|
||||
_CFRunLoopMode = _CFStringRef
|
||||
_CFTimeInterval float64
|
||||
_CFTypeID uint64
|
||||
_CFStringEncoding uint32
|
||||
)
|
||||
|
||||
var kCFAllocatorDefault _CFAllocatorRef = 0
|
||||
|
||||
const (
|
||||
kCFStringEncodingUTF8 _CFStringEncoding = 0x08000100
|
||||
)
|
||||
|
||||
const (
|
||||
kCFNumberSInt32Type _CFNumberType = 3
|
||||
kCFNumberIntType _CFNumberType = 9
|
||||
)
|
||||
|
||||
var (
|
||||
kCFTypeDictionaryKeyCallBacks uintptr
|
||||
kCFTypeDictionaryValueCallBacks uintptr
|
||||
kCFTypeArrayCallBacks uintptr
|
||||
kCFRunLoopDefaultMode uintptr
|
||||
)
|
||||
|
||||
func initializeCF() error {
|
||||
corefoundation, err := purego.Dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kCFTypeDictionaryKeyCallBacks, err = purego.Dlsym(corefoundation, "kCFTypeDictionaryKeyCallBacks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kCFTypeDictionaryValueCallBacks, err = purego.Dlsym(corefoundation, "kCFTypeDictionaryValueCallBacks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kCFTypeArrayCallBacks, err = purego.Dlsym(corefoundation, "kCFTypeArrayCallBacks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kCFRunLoopDefaultMode, err = purego.Dlsym(corefoundation, "kCFRunLoopDefaultMode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
purego.RegisterLibFunc(&_CFNumberCreate, corefoundation, "CFNumberCreate")
|
||||
purego.RegisterLibFunc(&_CFNumberGetValue, corefoundation, "CFNumberGetValue")
|
||||
purego.RegisterLibFunc(&_CFArrayCreate, corefoundation, "CFArrayCreate")
|
||||
purego.RegisterLibFunc(&_CFArrayGetValueAtIndex, corefoundation, "CFArrayGetValueAtIndex")
|
||||
purego.RegisterLibFunc(&_CFArrayGetCount, corefoundation, "CFArrayGetCount")
|
||||
purego.RegisterLibFunc(&_CFDictionaryCreate, corefoundation, "CFDictionaryCreate")
|
||||
purego.RegisterLibFunc(&_CFRelease, corefoundation, "CFRelease")
|
||||
purego.RegisterLibFunc(&_CFRunLoopGetMain, corefoundation, "CFRunLoopGetMain")
|
||||
purego.RegisterLibFunc(&_CFRunLoopRunInMode, corefoundation, "CFRunLoopRunInMode")
|
||||
purego.RegisterLibFunc(&_CFGetTypeID, corefoundation, "CFGetTypeID")
|
||||
purego.RegisterLibFunc(&_CFStringGetCString, corefoundation, "CFStringGetCString")
|
||||
purego.RegisterLibFunc(&_CFStringCreateWithCString, corefoundation, "CFStringCreateWithCString")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_CFNumberCreate func(allocator _CFAllocatorRef, theType _CFNumberType, valuePtr unsafe.Pointer) _CFNumberRef
|
||||
_CFNumberGetValue func(number _CFNumberRef, theType _CFNumberType, valuePtr unsafe.Pointer) bool
|
||||
_CFArrayCreate func(allocator _CFAllocatorRef, values *unsafe.Pointer, numValues _CFIndex, callbacks *_CFArrayCallBacks) _CFArrayRef
|
||||
_CFArrayGetValueAtIndex func(array _CFArrayRef, index _CFIndex) uintptr
|
||||
_CFArrayGetCount func(array _CFArrayRef) _CFIndex
|
||||
_CFDictionaryCreate func(allocator _CFAllocatorRef, keys *unsafe.Pointer, values *unsafe.Pointer, numValues _CFIndex, keyCallBacks *_CFDictionaryKeyCallBacks, valueCallBacks *_CFDictionaryValueCallBacks) _CFDictionaryRef
|
||||
_CFRelease func(cf _CFTypeRef)
|
||||
_CFRunLoopGetMain func() _CFRunLoopRef
|
||||
_CFRunLoopRunInMode func(mode _CFRunLoopMode, seconds _CFTimeInterval, returnAfterSourceHandled bool) _CFRunLoopRunResult
|
||||
_CFGetTypeID func(cf _CFTypeRef) _CFTypeID
|
||||
_CFStringGetCString func(theString _CFStringRef, buffer []byte, encoding _CFStringEncoding) bool
|
||||
_CFStringCreateWithCString func(alloc _CFAllocatorRef, cstr []byte, encoding _CFStringEncoding) _CFStringRef
|
||||
)
|
||||
+503
@@ -0,0 +1,503 @@
|
||||
// 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_GUIDANDPATH = 12
|
||||
_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 _DIDFT_GETTYPE(n uint32) byte {
|
||||
return byte(n)
|
||||
}
|
||||
|
||||
func _DIJOFS_SLIDER(n int) uint32 {
|
||||
return uint32(unsafe.Offsetof(_DIJOYSTATE{}.rglSlider) + uintptr(n)*unsafe.Sizeof(int32(0)))
|
||||
}
|
||||
|
||||
func _DIJOFS_POV(n int) uint32 {
|
||||
return uint32(unsafe.Offsetof(_DIJOYSTATE{}.rgdwPOV) + uintptr(n)*unsafe.Sizeof(uint32(0)))
|
||||
}
|
||||
|
||||
func _DIJOFS_BUTTON(n int) uint32 {
|
||||
return uint32(unsafe.Offsetof(_DIJOYSTATE{}.rgbButtons) + uintptr(n))
|
||||
}
|
||||
|
||||
var (
|
||||
_IID_IDirectInput8W = windows.GUID{Data1: 0xbf798031, Data2: 0x483a, Data3: 0x4da2, Data4: [...]byte{0xaa, 0x99, 0x5d, 0x64, 0xed, 0x36, 0x97, 0x00}}
|
||||
_GUID_XAxis = windows.GUID{Data1: 0xa36d02e0, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
_GUID_YAxis = windows.GUID{Data1: 0xa36d02e1, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
_GUID_ZAxis = windows.GUID{Data1: 0xa36d02e2, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
_GUID_RxAxis = windows.GUID{Data1: 0xa36d02f4, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
_GUID_RyAxis = windows.GUID{Data1: 0xa36d02f5, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
_GUID_RzAxis = windows.GUID{Data1: 0xa36d02e3, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
_GUID_Slider = windows.GUID{Data1: 0xa36d02e4, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
_GUID_POV = windows.GUID{Data1: 0xa36d02f2, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||
|
||||
procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
||||
|
||||
procCallWindowProcW = user32.NewProc("CallWindowProcW")
|
||||
procGetRawInputDeviceInfoW = user32.NewProc("GetRawInputDeviceInfoW")
|
||||
procGetRawInputDeviceList = user32.NewProc("GetRawInputDeviceList")
|
||||
|
||||
procSetWindowLongW = user32.NewProc("SetWindowLongW") // 32-Bit Windows version.
|
||||
procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW") // 64-Bit Windows version.
|
||||
)
|
||||
|
||||
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 _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 windows.HWND, nIndex int32, dwNewLong uintptr) (uintptr, error) {
|
||||
var p *windows.LazyProc
|
||||
if procSetWindowLongPtrW.Find() == nil {
|
||||
// 64-Bit Windows.
|
||||
p = procSetWindowLongPtrW
|
||||
} else {
|
||||
// 32-Bit Windows.
|
||||
p = procSetWindowLongW
|
||||
}
|
||||
h, _, e := p.Call(uintptr(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 _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 _DIPROPGUIDANDPATH struct {
|
||||
diph _DIPROPHEADER
|
||||
guidClass windows.GUID
|
||||
wszPath [_MAX_PATH]uint16
|
||||
}
|
||||
|
||||
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 uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: IDirectInput8::CreateDevice failed: %w", handleError(windows.Handle(uint32(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 uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: IDirectInput8::EnumDevices failed: %w", handleError(windows.Handle(uint32(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 uint32(r) != _DI_OK && uint32(r) != _SI_FALSE {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::Acquire failed: %w", handleError(windows.Handle(uint32(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 uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::EnumObjects failed: %w", handleError(windows.Handle(uint32(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 uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::GetCapabilities failed: %w", handleError(windows.Handle(uint32(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 uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::GetDeviceState failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *_IDirectInputDevice8W) GetProperty(rguidProp uintptr, pdiph *_DIPROPHEADER) error {
|
||||
r, _, _ := syscall.Syscall(d.vtbl.GetProperty, 3, uintptr(unsafe.Pointer(d)), rguidProp, uintptr(unsafe.Pointer(pdiph)))
|
||||
if uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::GetProperty failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *_IDirectInputDevice8W) Poll() error {
|
||||
r, _, _ := syscall.Syscall(d.vtbl.Poll, 1, uintptr(unsafe.Pointer(d)), 0, 0)
|
||||
if uint32(r) != _DI_OK && uint32(r) != _DI_NOEFFECT {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::Poll failed: %w", handleError(windows.Handle(uint32(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 uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::SetDataFormat failed: %w", handleError(windows.Handle(uint32(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 uint32(r) != _DI_OK && uint32(r) != _DI_PROPNOEFFECT {
|
||||
return fmt.Errorf("gamepad: IDirectInputDevice8::SetProperty failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type _RID_DEVICE_INFO struct {
|
||||
cbSize uint32
|
||||
dwType uint32
|
||||
hid _RID_DEVICE_INFO_HID // Originally, this member is a union.
|
||||
}
|
||||
|
||||
type _RID_DEVICE_INFO_HID 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 _XINPUT_CAPABILITIES struct {
|
||||
typ byte
|
||||
subType byte
|
||||
flags uint16
|
||||
gamepad _XINPUT_GAMEPAD
|
||||
vibration _XINPUT_VIBRATION
|
||||
}
|
||||
|
||||
type _XINPUT_GAMEPAD struct {
|
||||
wButtons uint16
|
||||
bLeftTrigger byte
|
||||
bRightTrigger byte
|
||||
sThumbLX int16
|
||||
sThumbLY int16
|
||||
sThumbRX int16
|
||||
sThumbRY int16
|
||||
}
|
||||
|
||||
type _XINPUT_STATE struct {
|
||||
dwPacketNumber uint32
|
||||
Gamepad _XINPUT_GAMEPAD
|
||||
}
|
||||
|
||||
type _XINPUT_VIBRATION struct {
|
||||
wLeftMotorSpeed uint16
|
||||
wRightMotorSpeed uint16
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// Copyright 2022 The Ebitengine 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 (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
const kIOReturnSuccess = 0
|
||||
|
||||
const kIOHIDOptionsTypeNone _IOOptionBits = 0
|
||||
|
||||
const (
|
||||
kIOHIDElementTypeInput_Misc = 1
|
||||
kIOHIDElementTypeInput_Button = 2
|
||||
kIOHIDElementTypeInput_Axis = 3
|
||||
)
|
||||
|
||||
const (
|
||||
kHIDPage_GenericDesktop = 0x1
|
||||
kHIDPage_Simulation = 0x2
|
||||
kHIDPage_Button = 0x9
|
||||
kHIDPage_Consumer = 0x0C
|
||||
)
|
||||
|
||||
const (
|
||||
kHIDUsage_GD_Joystick = 0x4
|
||||
kHIDUsage_GD_GamePad = 0x5
|
||||
kHIDUsage_GD_MultiAxisController = 0x8
|
||||
kHIDUsage_GD_X = 0x30
|
||||
kHIDUsage_GD_Y = 0x31
|
||||
kHIDUsage_GD_Z = 0x32
|
||||
kHIDUsage_GD_Rx = 0x33
|
||||
kHIDUsage_GD_Ry = 0x34
|
||||
kHIDUsage_GD_Rz = 0x35
|
||||
kHIDUsage_GD_Slider = 0x36
|
||||
kHIDUsage_GD_Dial = 0x37
|
||||
kHIDUsage_GD_Wheel = 0x38
|
||||
kHIDUsage_GD_Hatswitch = 0x39
|
||||
kHIDUsage_GD_Start = 0x3D
|
||||
kHIDUsage_GD_Select = 0x3E
|
||||
kHIDUsage_GD_SystemMainMenu = 0x85
|
||||
kHIDUsage_GD_DPadUp = 0x90
|
||||
kHIDUsage_GD_DPadDown = 0x91
|
||||
kHIDUsage_GD_DPadRight = 0x92
|
||||
kHIDUsage_GD_DPadLeft = 0x93
|
||||
kHIDUsage_Sim_Rudder = 0xBA
|
||||
kHIDUsage_Sim_Throttle = 0xBB
|
||||
kHIDUsage_Sim_Accelerator = 0xC4
|
||||
kHIDUsage_Sim_Brake = 0xC5
|
||||
kHIDUsage_Sim_Steering = 0xC8
|
||||
)
|
||||
|
||||
var (
|
||||
kIOHIDVendorIDKey = []byte("VendorID\x00")
|
||||
kIOHIDProductIDKey = []byte("ProductID\x00")
|
||||
kIOHIDVersionNumberKey = []byte("VersionNumber\x00")
|
||||
kIOHIDProductKey = []byte("Product\x00")
|
||||
kIOHIDDeviceUsagePageKey = []byte("DeviceUsagePage\x00")
|
||||
kIOHIDDeviceUsageKey = []byte("DeviceUsage\x00")
|
||||
)
|
||||
|
||||
type (
|
||||
_IOOptionBits uint32
|
||||
_IOHIDManagerRef uintptr
|
||||
_IOHIDDeviceRef uintptr
|
||||
_IOHIDElementRef uintptr
|
||||
_IOHIDValueRef uintptr
|
||||
_IOReturn int32
|
||||
_IOHIDElementType uint32
|
||||
)
|
||||
|
||||
type _IOHIDDeviceCallback func(context unsafe.Pointer, result _IOReturn, sender unsafe.Pointer, device _IOHIDDeviceRef)
|
||||
|
||||
func initializeIOKit() error {
|
||||
iokit, err := purego.Dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
purego.RegisterLibFunc(&_IOHIDElementGetTypeID, iokit, "IOHIDElementGetTypeID")
|
||||
purego.RegisterLibFunc(&_IOHIDManagerCreate, iokit, "IOHIDManagerCreate")
|
||||
purego.RegisterLibFunc(&_IOHIDDeviceGetProperty, iokit, "IOHIDDeviceGetProperty")
|
||||
purego.RegisterLibFunc(&_IOHIDManagerOpen, iokit, "IOHIDManagerOpen")
|
||||
purego.RegisterLibFunc(&_IOHIDManagerSetDeviceMatchingMultiple, iokit, "IOHIDManagerSetDeviceMatchingMultiple")
|
||||
purego.RegisterLibFunc(&_IOHIDManagerRegisterDeviceMatchingCallback, iokit, "IOHIDManagerRegisterDeviceMatchingCallback")
|
||||
purego.RegisterLibFunc(&_IOHIDManagerRegisterDeviceRemovalCallback, iokit, "IOHIDManagerRegisterDeviceRemovalCallback")
|
||||
purego.RegisterLibFunc(&_IOHIDManagerScheduleWithRunLoop, iokit, "IOHIDManagerScheduleWithRunLoop")
|
||||
purego.RegisterLibFunc(&_IOHIDElementGetType, iokit, "IOHIDElementGetType")
|
||||
purego.RegisterLibFunc(&_IOHIDElementGetUsage, iokit, "IOHIDElementGetUsage")
|
||||
purego.RegisterLibFunc(&_IOHIDElementGetUsagePage, iokit, "IOHIDElementGetUsagePage")
|
||||
purego.RegisterLibFunc(&_IOHIDElementGetLogicalMin, iokit, "IOHIDElementGetLogicalMin")
|
||||
purego.RegisterLibFunc(&_IOHIDElementGetLogicalMax, iokit, "IOHIDElementGetLogicalMax")
|
||||
purego.RegisterLibFunc(&_IOHIDDeviceGetValue, iokit, "IOHIDDeviceGetValue")
|
||||
purego.RegisterLibFunc(&_IOHIDValueGetIntegerValue, iokit, "IOHIDValueGetIntegerValue")
|
||||
purego.RegisterLibFunc(&_IOHIDDeviceCopyMatchingElements, iokit, "IOHIDDeviceCopyMatchingElements")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_IOHIDElementGetTypeID func() _CFTypeID
|
||||
_IOHIDManagerCreate func(allocator _CFAllocatorRef, options _IOOptionBits) _IOHIDManagerRef
|
||||
_IOHIDDeviceGetProperty func(device _IOHIDDeviceRef, key _CFStringRef) _CFTypeRef
|
||||
_IOHIDManagerOpen func(manager _IOHIDManagerRef, options _IOOptionBits) _IOReturn
|
||||
_IOHIDManagerSetDeviceMatchingMultiple func(manager _IOHIDManagerRef, multiple _CFArrayRef)
|
||||
_IOHIDManagerRegisterDeviceMatchingCallback func(manager _IOHIDManagerRef, callback _IOHIDDeviceCallback, context unsafe.Pointer)
|
||||
_IOHIDManagerRegisterDeviceRemovalCallback func(manager _IOHIDManagerRef, callback _IOHIDDeviceCallback, context unsafe.Pointer)
|
||||
_IOHIDManagerScheduleWithRunLoop func(manager _IOHIDManagerRef, runLoop _CFRunLoopRef, runLoopMode _CFStringRef)
|
||||
_IOHIDElementGetType func(element _IOHIDElementRef) _IOHIDElementType
|
||||
_IOHIDElementGetUsage func(element _IOHIDElementRef) uint32
|
||||
_IOHIDElementGetUsagePage func(element _IOHIDElementRef) uint32
|
||||
_IOHIDElementGetLogicalMin func(element _IOHIDElementRef) _CFIndex
|
||||
_IOHIDElementGetLogicalMax func(element _IOHIDElementRef) _CFIndex
|
||||
_IOHIDDeviceGetValue func(device _IOHIDDeviceRef, element _IOHIDElementRef, pValue *_IOHIDValueRef) _IOReturn
|
||||
_IOHIDValueGetIntegerValue func(value _IOHIDValueRef) _CFIndex
|
||||
_IOHIDDeviceCopyMatchingElements func(device _IOHIDDeviceRef, matching _CFDictionaryRef, options _IOOptionBits) _CFArrayRef
|
||||
)
|
||||
+464
@@ -0,0 +1,464 @@
|
||||
// Copyright 2021 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
|
||||
|
||||
// #cgo CFLAGS: -x objective-c
|
||||
// #cgo LDFLAGS: -framework Foundation -framework GameController
|
||||
//
|
||||
// #import <GameController/GameController.h>
|
||||
//
|
||||
// static NSString* GCInputXboxShareButton = @"Button Share";
|
||||
//
|
||||
// enum ControllerButton {
|
||||
// kControllerButtonInvalid = -1,
|
||||
// kControllerButtonA,
|
||||
// kControllerButtonB,
|
||||
// kControllerButtonX,
|
||||
// kControllerButtonY,
|
||||
// kControllerButtonBack,
|
||||
// kControllerButtonGuide,
|
||||
// kControllerButtonStart,
|
||||
// kControllerButtonLeftStick,
|
||||
// kControllerButtonRightStick,
|
||||
// kControllerButtonLeftShoulder,
|
||||
// kControllerButtonRightShoulder,
|
||||
// kControllerButtonDpadUp,
|
||||
// kControllerButtonDpadDown,
|
||||
// kControllerButtonDpadLeft,
|
||||
// kControllerButtonDpadRight,
|
||||
// kControllerButtonMisc1,
|
||||
// kControllerButtonPaddle1,
|
||||
// kControllerButtonPaddle2,
|
||||
// kControllerButtonPaddle3,
|
||||
// kControllerButtonPaddle4,
|
||||
// kControllerButtonTouchpad,
|
||||
// kControllerButtonMax,
|
||||
// };
|
||||
//
|
||||
// enum HatState : uint8_t {
|
||||
// kHatCentered = 0x00,
|
||||
// kHatUp = 0x01,
|
||||
// kHatRight = 0x02,
|
||||
// kHatDown = 0x04,
|
||||
// kHatLeft = 0x08,
|
||||
// kHatRightUp = kHatRight | kHatUp,
|
||||
// kHatRightDown = kHatRight | kHatDown,
|
||||
// kHatLeftUp = kHatLeft | kHatUp,
|
||||
// kHatLeftDown = kHatLeft | kHatDown,
|
||||
// };
|
||||
//
|
||||
// enum USBVendor {
|
||||
// kUSBVendorApple = 0x05ac,
|
||||
// kUSBVendorMicrosoft = 0x045e,
|
||||
// kUSBVendorSony = 0x054c,
|
||||
// };
|
||||
//
|
||||
// enum USBProduct {
|
||||
// kUSBProductSonyDS4Slim = 0x09cc,
|
||||
// kUSBProductSonyDS5 = 0x0ce6,
|
||||
// kUSBProductXboxOneEliteSeries2Bluetooth = 0x0b05,
|
||||
// kUSBProductXboxOneSRev1Bluetooth = 0x02e0,
|
||||
// kUSBProductXboxSeriesXBluetooth = 0x0b13,
|
||||
// };
|
||||
//
|
||||
// struct ControllerProperty {
|
||||
// uint8_t nAxes;
|
||||
// uint8_t nButtons;
|
||||
// uint8_t nHats;
|
||||
// uint16_t buttonMask;
|
||||
// char guid[16];
|
||||
// char name[256];
|
||||
// bool hasDualshockTouchpad;
|
||||
// bool hasXboxPaddles;
|
||||
// bool hasXboxShareButton;
|
||||
// };
|
||||
//
|
||||
// void ebitenAddGamepad(uintptr_t controller, struct ControllerProperty* prop);
|
||||
// void ebitenRemoveGamepad(uintptr_t controller);
|
||||
//
|
||||
// static size_t min(size_t a, size_t b) {
|
||||
// return a < b ? a : b;
|
||||
// }
|
||||
//
|
||||
// static void getControllerPropertyFromController(GCController* controller, struct ControllerProperty* property) {
|
||||
// @autoreleasepool {
|
||||
// uint16_t vendor = 0;
|
||||
// uint16_t product = 0;
|
||||
// uint16_t subtype = 0;
|
||||
//
|
||||
// const char* name = nil;
|
||||
// if (controller.vendorName) {
|
||||
// name = controller.vendorName.UTF8String;
|
||||
// }
|
||||
// if (!name) {
|
||||
// name = "MFi Gamepad";
|
||||
// }
|
||||
// memcpy(property->name, name, min(sizeof(property->name), strlen(name)));
|
||||
//
|
||||
// if (controller.extendedGamepad) {
|
||||
// GCExtendedGamepad* gamepad = controller.extendedGamepad;
|
||||
//
|
||||
// bool isXbox = false;
|
||||
// bool isPS4 = false;
|
||||
// bool isPS5 = false;
|
||||
// if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
|
||||
// NSString* productCategory = [controller productCategory];
|
||||
// if ([productCategory isEqualToString:@"DualShock 4"]) {
|
||||
// isPS4 = 1;
|
||||
// } else if ([productCategory isEqualToString:@"DualSense"]) {
|
||||
// isPS5 = 1;
|
||||
// } else if ([productCategory isEqualToString:@"Xbox One"]) {
|
||||
// isXbox = 1;
|
||||
// }
|
||||
// } else {
|
||||
// NSString* vendorName = [controller vendorName];
|
||||
// if ([vendorName isEqualToString:@"DUALSHOCK"]) {
|
||||
// isPS4 = 1;
|
||||
// } else if ([vendorName isEqualToString:@"DualSense"]) {
|
||||
// isPS5 = 1;
|
||||
// } else if ([vendorName isEqualToString:@"Xbox"]) {
|
||||
// isXbox = 1;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// property->buttonMask |= (1 << kControllerButtonA);
|
||||
// property->buttonMask |= (1 << kControllerButtonB);
|
||||
// property->buttonMask |= (1 << kControllerButtonX);
|
||||
// property->buttonMask |= (1 << kControllerButtonY);
|
||||
// property->buttonMask |= (1 << kControllerButtonLeftShoulder);
|
||||
// property->buttonMask |= (1 << kControllerButtonRightShoulder);
|
||||
// property->nButtons += 6;
|
||||
//
|
||||
// #pragma clang diagnostic push
|
||||
// #pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
//
|
||||
// if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) {
|
||||
// property->buttonMask |= (1 << kControllerButtonLeftStick);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) {
|
||||
// property->buttonMask |= (1 << kControllerButtonRightStick);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) {
|
||||
// property->buttonMask |= (1 << kControllerButtonBack);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if ([gamepad respondsToSelector:@selector(buttonHome)] && gamepad.buttonHome) {
|
||||
// property->buttonMask |= (1 << kControllerButtonGuide);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
//
|
||||
// property->buttonMask |= (1 << kControllerButtonStart);
|
||||
// property->nButtons++;
|
||||
//
|
||||
// if ([controller respondsToSelector:@selector(physicalInputProfile)]) {
|
||||
// if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
|
||||
// property->hasDualshockTouchpad = true;
|
||||
// property->buttonMask |= (1 << kControllerButtonMisc1);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
|
||||
// property->hasXboxPaddles = true;
|
||||
// property->buttonMask |= (1 << kControllerButtonPaddle1);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo] != nil) {
|
||||
// property->hasXboxPaddles = true;
|
||||
// property->buttonMask |= (1 << kControllerButtonPaddle2);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleThree] != nil) {
|
||||
// property->hasXboxPaddles = true;
|
||||
// property->buttonMask |= (1 << kControllerButtonPaddle3);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleFour] != nil) {
|
||||
// property->hasXboxPaddles = true;
|
||||
// property->buttonMask |= (1 << kControllerButtonPaddle4);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// if (controller.physicalInputProfile.buttons[GCInputXboxShareButton] != nil) {
|
||||
// property->hasXboxShareButton = true;
|
||||
// property->buttonMask |= (1 << kControllerButtonMisc1);
|
||||
// property->nButtons++;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #pragma clang diagnostic pop
|
||||
//
|
||||
// if (isXbox) {
|
||||
// vendor = kUSBVendorMicrosoft;
|
||||
// if (property->hasXboxPaddles) {
|
||||
// product = kUSBProductXboxOneEliteSeries2Bluetooth;
|
||||
// subtype = 1;
|
||||
// } else if (property->hasXboxShareButton) {
|
||||
// product = kUSBProductXboxSeriesXBluetooth;
|
||||
// subtype = 1;
|
||||
// } else {
|
||||
// product = kUSBProductXboxOneSRev1Bluetooth;
|
||||
// subtype = 0;
|
||||
// }
|
||||
// } else if (isPS4) {
|
||||
// vendor = kUSBVendorSony;
|
||||
// product = kUSBProductSonyDS4Slim;
|
||||
// if (property->hasDualshockTouchpad) {
|
||||
// subtype = 1;
|
||||
// } else {
|
||||
// subtype = 0;
|
||||
// }
|
||||
// } else if (isPS5) {
|
||||
// vendor = kUSBVendorSony;
|
||||
// product = kUSBProductSonyDS5;
|
||||
// subtype = 0;
|
||||
// } else {
|
||||
// vendor = kUSBVendorApple;
|
||||
// product = 1;
|
||||
// subtype = 1;
|
||||
// }
|
||||
//
|
||||
// property->nAxes = 6;
|
||||
// property->nHats = 1;
|
||||
// }
|
||||
//
|
||||
// const int kSDLHardwareBusBluetooth = 0x05;
|
||||
// property->guid[0] = (uint8_t)(kSDLHardwareBusBluetooth);
|
||||
// property->guid[1] = (uint8_t)(kSDLHardwareBusBluetooth >> 8);
|
||||
// property->guid[2] = 0;
|
||||
// property->guid[3] = 0;
|
||||
// property->guid[4] = (uint8_t)(vendor);
|
||||
// property->guid[5] = (uint8_t)(vendor >> 8);
|
||||
// property->guid[6] = 0;
|
||||
// property->guid[7] = 0;
|
||||
// property->guid[8] = (uint8_t)(product);
|
||||
// property->guid[9] = (uint8_t)(product >> 8);
|
||||
// property->guid[10] = 0;
|
||||
// property->guid[11] = 0;
|
||||
// property->guid[12] = (uint8_t)(property->buttonMask);
|
||||
// property->guid[13] = (uint8_t)(property->buttonMask >> 8);
|
||||
// if (vendor == kUSBVendorApple) {
|
||||
// property->guid[14] = 'm';
|
||||
// } else {
|
||||
// property->guid[14] = 0;
|
||||
// }
|
||||
// property->guid[15] = subtype;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static void addController(GCController* controller) {
|
||||
// // Ignore if the controller is not an actual controller.
|
||||
// if (!controller.extendedGamepad && controller.microGamepad) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// struct ControllerProperty property = {};
|
||||
// getControllerPropertyFromController(controller, &property);
|
||||
// ebitenAddGamepad((uintptr_t)(controller), &property);
|
||||
// }
|
||||
//
|
||||
// static void removeController(GCController* controller) {
|
||||
// ebitenRemoveGamepad((uintptr_t)(controller));
|
||||
// }
|
||||
//
|
||||
// struct ControllerState {
|
||||
// uint8_t buttons[32];
|
||||
// float axes[32];
|
||||
// enum HatState hat;
|
||||
// };
|
||||
//
|
||||
// static enum HatState getHatState(GCControllerDirectionPad* dpad) {
|
||||
// enum HatState hat = 0;
|
||||
// if (dpad.up.isPressed) {
|
||||
// hat |= kHatUp;
|
||||
// } else if (dpad.down.isPressed) {
|
||||
// hat |= kHatDown;
|
||||
// }
|
||||
// if (dpad.left.isPressed) {
|
||||
// hat |= kHatLeft;
|
||||
// } else if (dpad.right.isPressed) {
|
||||
// hat |= kHatRight;
|
||||
// }
|
||||
// return hat;
|
||||
// }
|
||||
//
|
||||
// static void getControllerState(uintptr_t controller_ptr, struct ControllerState* controllerState,
|
||||
// uint16_t buttonMask, uint8_t nHats,
|
||||
// bool hasDualshockTouchpad, bool hasXboxPaddles, bool hasXboxShareButton) {
|
||||
// GCController* controller = (GCController*)(controller_ptr);
|
||||
// @autoreleasepool {
|
||||
// if (controller.extendedGamepad) {
|
||||
// GCExtendedGamepad* gamepad = controller.extendedGamepad;
|
||||
//
|
||||
// controllerState->axes[0] = gamepad.leftThumbstick.xAxis.value;
|
||||
// controllerState->axes[1] = -gamepad.leftThumbstick.yAxis.value;
|
||||
// controllerState->axes[2] = gamepad.leftTrigger.value * 2 - 1;
|
||||
// controllerState->axes[3] = gamepad.rightThumbstick.xAxis.value;
|
||||
// controllerState->axes[4] = -gamepad.rightThumbstick.yAxis.value;
|
||||
// controllerState->axes[5] = gamepad.rightTrigger.value * 2 - 1;
|
||||
//
|
||||
// int buttonCount = 0;
|
||||
// controllerState->buttons[buttonCount++] = gamepad.buttonA.isPressed;
|
||||
// controllerState->buttons[buttonCount++] = gamepad.buttonB.isPressed;
|
||||
// controllerState->buttons[buttonCount++] = gamepad.buttonX.isPressed;
|
||||
// controllerState->buttons[buttonCount++] = gamepad.buttonY.isPressed;
|
||||
// controllerState->buttons[buttonCount++] = gamepad.leftShoulder.isPressed;
|
||||
// controllerState->buttons[buttonCount++] = gamepad.rightShoulder.isPressed;
|
||||
//
|
||||
// #pragma clang diagnostic push
|
||||
// #pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
//
|
||||
// if (buttonMask & (1 << kControllerButtonLeftStick)) {
|
||||
// controllerState->buttons[buttonCount++] = gamepad.leftThumbstickButton.isPressed;
|
||||
// }
|
||||
// if (buttonMask & (1 << kControllerButtonRightStick)) {
|
||||
// controllerState->buttons[buttonCount++] = gamepad.rightThumbstickButton.isPressed;
|
||||
// }
|
||||
// if (buttonMask & (1 << kControllerButtonBack)) {
|
||||
// controllerState->buttons[buttonCount++] = gamepad.buttonOptions.isPressed;
|
||||
// }
|
||||
// if (buttonMask & (1 << kControllerButtonGuide)) {
|
||||
// controllerState->buttons[buttonCount++] = gamepad.buttonHome.isPressed;
|
||||
// }
|
||||
// if (buttonMask & (1 << kControllerButtonStart)) {
|
||||
// controllerState->buttons[buttonCount++] = gamepad.buttonMenu.isPressed;
|
||||
// }
|
||||
//
|
||||
// if (hasDualshockTouchpad) {
|
||||
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed;
|
||||
// }
|
||||
// if (hasXboxPaddles) {
|
||||
// if (buttonMask & (1 << kControllerButtonPaddle1)) {
|
||||
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed;
|
||||
// }
|
||||
// if (buttonMask & (1 << kControllerButtonPaddle2)) {
|
||||
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed;
|
||||
// }
|
||||
// if (buttonMask & (1 << kControllerButtonPaddle3)) {
|
||||
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed;
|
||||
// }
|
||||
// if (buttonMask & (1 << kControllerButtonPaddle4)) {
|
||||
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed;
|
||||
// }
|
||||
// }
|
||||
// if (hasXboxShareButton) {
|
||||
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxShareButton].isPressed;
|
||||
// }
|
||||
//
|
||||
// #pragma clang diagnostic pop
|
||||
//
|
||||
// if (nHats) {
|
||||
// controllerState->hat = getHatState(gamepad.dpad);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static void initializeGamepads(void) {
|
||||
// @autoreleasepool {
|
||||
// for (GCController* controller in [GCController controllers]) {
|
||||
// addController(controller);
|
||||
// }
|
||||
// NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
||||
// [center addObserverForName:GCControllerDidConnectNotification
|
||||
// object:nil
|
||||
// queue:nil
|
||||
// usingBlock:^(NSNotification* notification) {
|
||||
// addController(notification.object);
|
||||
// }];
|
||||
// [center addObserverForName:GCControllerDidDisconnectNotification
|
||||
// object:nil
|
||||
// queue:nil
|
||||
// usingBlock:^(NSNotification* notification) {
|
||||
// removeController(notification.object);
|
||||
// }];
|
||||
// }
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//export ebitenAddGamepad
|
||||
func ebitenAddGamepad(controller C.uintptr_t, prop *C.struct_ControllerProperty) {
|
||||
theGamepads.addIOSGamepad(controller, prop)
|
||||
}
|
||||
|
||||
//export ebitenRemoveGamepad
|
||||
func ebitenRemoveGamepad(controller C.uintptr_t) {
|
||||
theGamepads.removeIOSGamepad(controller)
|
||||
}
|
||||
|
||||
func (g *gamepads) addIOSGamepad(controller C.uintptr_t, prop *C.struct_ControllerProperty) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
name := C.GoString(&prop.name[0])
|
||||
sdlID := hex.EncodeToString(C.GoBytes(unsafe.Pointer(&prop.guid[0]), 16))
|
||||
gp := g.add(name, sdlID)
|
||||
gp.native = &nativeGamepadImpl{
|
||||
controller: uintptr(controller),
|
||||
axes: make([]float64, prop.nAxes),
|
||||
buttons: make([]bool, prop.nButtons+prop.nHats*4),
|
||||
hats: make([]int, prop.nHats),
|
||||
buttonMask: uint16(prop.buttonMask),
|
||||
hasDualshockTouchpad: bool(prop.hasDualshockTouchpad),
|
||||
hasXboxPaddles: bool(prop.hasXboxPaddles),
|
||||
hasXboxShareButton: bool(prop.hasXboxShareButton),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gamepads) removeIOSGamepad(controller C.uintptr_t) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
g.remove(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).controller == uintptr(controller)
|
||||
})
|
||||
}
|
||||
|
||||
func initializeIOSGamepads() {
|
||||
C.initializeGamepads()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) updateIOSGamepad() {
|
||||
var state C.struct_ControllerState
|
||||
C.getControllerState(C.uintptr_t(g.controller), &state, C.uint16_t(g.buttonMask), C.uint8_t(len(g.hats)),
|
||||
C.bool(g.hasDualshockTouchpad), C.bool(g.hasXboxPaddles), C.bool(g.hasXboxShareButton))
|
||||
|
||||
nButtons := len(g.buttons) - len(g.hats)*4
|
||||
for i := 0; i < nButtons; i++ {
|
||||
g.buttons[i] = state.buttons[i] != 0
|
||||
}
|
||||
|
||||
// Follow the GLFW way to process hats.
|
||||
// See _glfwInputJoystickHat.
|
||||
if len(g.hats) > 0 {
|
||||
base := len(g.buttons) - len(g.hats)*4
|
||||
g.buttons[base] = state.hat&0x01 != 0
|
||||
g.buttons[base+1] = state.hat&0x02 != 0
|
||||
g.buttons[base+2] = state.hat&0x04 != 0
|
||||
g.buttons[base+3] = state.hat&0x08 != 0
|
||||
}
|
||||
|
||||
for i := range g.axes {
|
||||
g.axes[i] = float64(state.axes[i])
|
||||
}
|
||||
|
||||
if len(g.hats) > 0 {
|
||||
g.hats[0] = int(state.hat)
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
// 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.
|
||||
|
||||
//go:build !android && !nintendosdk && !playstation5
|
||||
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
_ABS_X = 0x00
|
||||
_ABS_Y = 0x01
|
||||
_ABS_Z = 0x02
|
||||
_ABS_RX = 0x03
|
||||
_ABS_RY = 0x04
|
||||
_ABS_RZ = 0x05
|
||||
_ABS_HAT0X = 0x10
|
||||
_ABS_HAT0Y = 0x11
|
||||
_ABS_HAT1X = 0x12
|
||||
_ABS_HAT1Y = 0x13
|
||||
_ABS_HAT2X = 0x14
|
||||
_ABS_HAT2Y = 0x15
|
||||
_ABS_HAT3Y = 0x17
|
||||
_ABS_MAX = 0x3f
|
||||
_ABS_CNT = _ABS_MAX + 1
|
||||
|
||||
_BTN_MISC = 0x100
|
||||
_BTN_GAMEPAD = 0x130
|
||||
_BTN_A = 0x130
|
||||
_BTN_B = 0x131
|
||||
_BTN_NORTH = 0x133
|
||||
_BTN_X = 0x133
|
||||
_BTN_WEST = 0x134
|
||||
_BTN_Y = 0x134
|
||||
_BTN_TL = 0x136
|
||||
_BTN_TR = 0x137
|
||||
_BTN_TL2 = 0x138
|
||||
_BTN_TR2 = 0x139
|
||||
_BTN_SELECT = 0x13a
|
||||
_BTN_START = 0x13b
|
||||
_BTN_MODE = 0x13c
|
||||
_BTN_THUMBL = 0x13d
|
||||
_BTN_THUMBR = 0x13e
|
||||
_BTN_DPAD_UP = 0x220
|
||||
_BTN_DPAD_DOWN = 0x221
|
||||
_BTN_DPAD_LEFT = 0x222
|
||||
_BTN_DPAD_RIGHT = 0x223
|
||||
|
||||
_IOC_NONE = 0
|
||||
_IOC_WRITE = 1
|
||||
_IOC_READ = 2
|
||||
|
||||
_IOC_NRBITS = 8
|
||||
_IOC_TYPEBITS = 8
|
||||
_IOC_SIZEBITS = 14
|
||||
_IOC_DIRBITS = 2
|
||||
|
||||
_IOC_NRSHIFT = 0
|
||||
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
|
||||
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
|
||||
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
|
||||
|
||||
_KEY_MAX = 0x2ff
|
||||
_KEY_CNT = _KEY_MAX + 1
|
||||
|
||||
_SYN_REPORT = 0
|
||||
_SYN_DROPPED = 3
|
||||
)
|
||||
|
||||
func _IOC(dir, typ, nr, size uint) uint {
|
||||
return dir<<_IOC_DIRSHIFT | typ<<_IOC_TYPESHIFT | nr<<_IOC_NRSHIFT | size<<_IOC_SIZESHIFT
|
||||
}
|
||||
|
||||
func _IOR(typ, nr, size uint) uint {
|
||||
return _IOC(_IOC_READ, typ, nr, size)
|
||||
}
|
||||
|
||||
func _EVIOCGABS(abs uint) uint {
|
||||
return _IOR('E', 0x40+abs, uint(unsafe.Sizeof(input_absinfo{})))
|
||||
}
|
||||
|
||||
func _EVIOCGBIT(ev, len uint) uint {
|
||||
return _IOC(_IOC_READ, 'E', 0x20+ev, len)
|
||||
}
|
||||
|
||||
func _EVIOCGID() uint {
|
||||
return _IOR('E', 0x02, uint(unsafe.Sizeof(input_id{})))
|
||||
}
|
||||
|
||||
func _EVIOCGNAME(len uint) uint {
|
||||
return _IOC(_IOC_READ, 'E', 0x06, len)
|
||||
}
|
||||
|
||||
type input_absinfo struct {
|
||||
value int32
|
||||
minimum int32
|
||||
maximum int32
|
||||
fuzz int32
|
||||
flat int32
|
||||
resolution int32
|
||||
}
|
||||
|
||||
type input_event struct {
|
||||
time unix.Timeval
|
||||
typ uint16
|
||||
code uint16
|
||||
value int32
|
||||
}
|
||||
|
||||
type input_id struct {
|
||||
bustype uint16
|
||||
vendor uint16
|
||||
product uint16
|
||||
version uint16
|
||||
}
|
||||
|
||||
func ioctl(fd int, request uint, ptr unsafe.Pointer) error {
|
||||
r, _, e := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(request), uintptr(ptr))
|
||||
if r < 0 {
|
||||
return unix.Errno(e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
// Copyright 2022 The Ebitengine 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"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type handleError windows.Handle
|
||||
|
||||
func (h handleError) Error() string {
|
||||
return fmt.Sprintf("HANDLE(%d)", h)
|
||||
}
|
||||
|
||||
var (
|
||||
gameInput = windows.NewLazySystemDLL("GameInput.dll")
|
||||
|
||||
procGameInputCreate = gameInput.NewProc("GameInputCreate")
|
||||
)
|
||||
|
||||
type _GameInputCallbackToken uint64
|
||||
|
||||
type _GameInputDeviceStatus int32
|
||||
|
||||
const (
|
||||
_GameInputDeviceNoStatus _GameInputDeviceStatus = 0x00000000
|
||||
_GameInputDeviceConnected _GameInputDeviceStatus = 0x00000001
|
||||
_GameInputDeviceInputEnabled _GameInputDeviceStatus = 0x00000002
|
||||
_GameInputDeviceOutputEnabled _GameInputDeviceStatus = 0x00000004
|
||||
_GameInputDeviceRawIoEnabled _GameInputDeviceStatus = 0x00000008
|
||||
_GameInputDeviceAudioCapture _GameInputDeviceStatus = 0x00000010
|
||||
_GameInputDeviceAudioRender _GameInputDeviceStatus = 0x00000020
|
||||
_GameInputDeviceSynchronized _GameInputDeviceStatus = 0x00000040
|
||||
_GameInputDeviceWireless _GameInputDeviceStatus = 0x00000080
|
||||
_GameInputDeviceUserIdle _GameInputDeviceStatus = 0x00100000
|
||||
_GameInputDeviceAnyStatus _GameInputDeviceStatus = 0x00FFFFFF
|
||||
)
|
||||
|
||||
type _GameInputEnumerationKind int32
|
||||
|
||||
const (
|
||||
_GameInputNoEnumeration _GameInputEnumerationKind = 0
|
||||
_GameInputAsyncEnumeration _GameInputEnumerationKind = 1
|
||||
_GameInputBlockingEnumeration _GameInputEnumerationKind = 2
|
||||
)
|
||||
|
||||
type _GameInputGamepadButtons int32
|
||||
|
||||
const (
|
||||
_GameInputGamepadNone _GameInputGamepadButtons = 0x00000000
|
||||
_GameInputGamepadMenu _GameInputGamepadButtons = 0x00000001
|
||||
_GameInputGamepadView _GameInputGamepadButtons = 0x00000002
|
||||
_GameInputGamepadA _GameInputGamepadButtons = 0x00000004
|
||||
_GameInputGamepadB _GameInputGamepadButtons = 0x00000008
|
||||
_GameInputGamepadX _GameInputGamepadButtons = 0x00000010
|
||||
_GameInputGamepadY _GameInputGamepadButtons = 0x00000020
|
||||
_GameInputGamepadDPadUp _GameInputGamepadButtons = 0x00000040
|
||||
_GameInputGamepadDPadDown _GameInputGamepadButtons = 0x00000080
|
||||
_GameInputGamepadDPadLeft _GameInputGamepadButtons = 0x00000100
|
||||
_GameInputGamepadDPadRight _GameInputGamepadButtons = 0x00000200
|
||||
_GameInputGamepadLeftShoulder _GameInputGamepadButtons = 0x00000400
|
||||
_GameInputGamepadRightShoulder _GameInputGamepadButtons = 0x00000800
|
||||
_GameInputGamepadLeftThumbstick _GameInputGamepadButtons = 0x00001000
|
||||
_GameInputGamepadRightThumbstick _GameInputGamepadButtons = 0x00002000
|
||||
)
|
||||
|
||||
type _GameInputKind int32
|
||||
|
||||
const (
|
||||
_GameInputKindUnknown _GameInputKind = 0x00000000
|
||||
_GameInputKindRawDeviceReport _GameInputKind = 0x00000001
|
||||
_GameInputKindControllerAxis _GameInputKind = 0x00000002
|
||||
_GameInputKindControllerButton _GameInputKind = 0x00000004
|
||||
_GameInputKindControllerSwitch _GameInputKind = 0x00000008
|
||||
_GameInputKindController _GameInputKind = 0x0000000E
|
||||
_GameInputKindKeyboard _GameInputKind = 0x00000010
|
||||
_GameInputKindMouse _GameInputKind = 0x00000020
|
||||
_GameInputKindTouch _GameInputKind = 0x00000100
|
||||
_GameInputKindMotion _GameInputKind = 0x00001000
|
||||
_GameInputKindArcadeStick _GameInputKind = 0x00010000
|
||||
_GameInputKindFlightStick _GameInputKind = 0x00020000
|
||||
_GameInputKindGamepad _GameInputKind = 0x00040000
|
||||
_GameInputKindRacingWheel _GameInputKind = 0x00080000
|
||||
_GameInputKindUiNavigation _GameInputKind = 0x01000000
|
||||
_GameInputKindAny _GameInputKind = 0x0FFFFFFF
|
||||
)
|
||||
|
||||
type _GameInputGamepadState struct {
|
||||
buttons _GameInputGamepadButtons
|
||||
leftTrigger float32
|
||||
rightTrigger float32
|
||||
leftThumbstickX float32
|
||||
leftThumbstickY float32
|
||||
rightThumbstickX float32
|
||||
rightThumbstickY float32
|
||||
}
|
||||
|
||||
type _GameInputRumbleParams struct {
|
||||
lowFrequency float32
|
||||
highFrequency float32
|
||||
leftTrigger float32
|
||||
rightTrigger float32
|
||||
}
|
||||
|
||||
func _GameInputCreate() (*_IGameInput, error) {
|
||||
var gameInput *_IGameInput
|
||||
r, _, _ := procGameInputCreate.Call(uintptr(unsafe.Pointer(&gameInput)))
|
||||
if uint32(r) != uint32(windows.S_OK) {
|
||||
return nil, fmt.Errorf("gamepad: GameInputCreate failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return gameInput, nil
|
||||
}
|
||||
|
||||
type _IGameInput struct {
|
||||
vtbl *_IGameInput_Vtbl
|
||||
}
|
||||
|
||||
type _IGameInput_Vtbl struct {
|
||||
QueryInterface uintptr
|
||||
AddRef uintptr
|
||||
Release uintptr
|
||||
|
||||
GetCurrentTimestamp uintptr
|
||||
GetCurrentReading uintptr
|
||||
GetNextReading uintptr
|
||||
GetPreviousReading uintptr
|
||||
GetTemporalReading uintptr
|
||||
RegisterReadingCallback uintptr
|
||||
RegisterDeviceCallback uintptr
|
||||
RegisterGuideButtonCallback uintptr
|
||||
RegisterKeyboardLayoutCallback uintptr
|
||||
StopCallback uintptr
|
||||
UnregisterCallback uintptr
|
||||
CreateDispatcher uintptr
|
||||
CreateAggregateDevice uintptr
|
||||
FindDeviceFromId uintptr
|
||||
FindDeviceFromObject uintptr
|
||||
FindDeviceFromPlatformHandle uintptr
|
||||
FindDeviceFromPlatformString uintptr
|
||||
EnableOemDeviceSupport uintptr
|
||||
SetFocusPolicy uintptr
|
||||
}
|
||||
|
||||
func (i *_IGameInput) GetCurrentReading(inputKind _GameInputKind, device *_IGameInputDevice) (*_IGameInputReading, error) {
|
||||
var reading *_IGameInputReading
|
||||
r, _, _ := syscall.Syscall6(i.vtbl.GetCurrentReading, 4, uintptr(unsafe.Pointer(i)),
|
||||
uintptr(inputKind), uintptr(unsafe.Pointer(device)), uintptr(unsafe.Pointer(&reading)),
|
||||
0, 0)
|
||||
runtime.KeepAlive(device)
|
||||
if uint32(r) != uint32(windows.S_OK) {
|
||||
return nil, fmt.Errorf("gamepad: IGameInput::GetCurrentReading failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return reading, nil
|
||||
}
|
||||
|
||||
func (i *_IGameInput) RegisterDeviceCallback(device *_IGameInputDevice,
|
||||
inputKind _GameInputKind,
|
||||
statusFilter _GameInputDeviceStatus,
|
||||
enumerationKind _GameInputEnumerationKind,
|
||||
context unsafe.Pointer,
|
||||
callbackFunc uintptr,
|
||||
callbackToken *_GameInputCallbackToken) error {
|
||||
r, _, _ := syscall.Syscall9(i.vtbl.RegisterDeviceCallback, 8, uintptr(unsafe.Pointer(i)),
|
||||
uintptr(unsafe.Pointer(device)), uintptr(inputKind), uintptr(statusFilter),
|
||||
uintptr(enumerationKind), uintptr(context), callbackFunc,
|
||||
uintptr(unsafe.Pointer(callbackToken)), 0)
|
||||
runtime.KeepAlive(device)
|
||||
runtime.KeepAlive(callbackToken)
|
||||
if uint32(r) != uint32(windows.S_OK) {
|
||||
return fmt.Errorf("gamepad: IGameInput::RegisterDeviceCallback failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type _IGameInputDevice struct {
|
||||
vtbl *_IGameInputDevice_Vtbl
|
||||
}
|
||||
|
||||
type _IGameInputDevice_Vtbl struct {
|
||||
QueryInterface uintptr
|
||||
AddRef uintptr
|
||||
Release uintptr
|
||||
|
||||
GetDeviceInfo uintptr
|
||||
GetDeviceStatus uintptr
|
||||
GetBatteryState uintptr
|
||||
CreateForceFeedbackEffect uintptr
|
||||
IsForceFeedbackMotorPoweredOn uintptr
|
||||
SetForceFeedbackMotorGain uintptr
|
||||
SetHapticMotorState uintptr
|
||||
SetRumbleState uintptr
|
||||
SetInputSynchronizationState uintptr
|
||||
SendInputSynchronizationHint uintptr
|
||||
PowerOff uintptr
|
||||
CreateRawDeviceReport uintptr
|
||||
GetRawDeviceFeature uintptr
|
||||
SetRawDeviceFeature uintptr
|
||||
SendRawDeviceOutput uintptr
|
||||
ExecuteRawDeviceIoControl uintptr
|
||||
AcquireExclusiveRawDeviceAccess uintptr
|
||||
ReleaseExclusiveRawDeviceAccess uintptr
|
||||
}
|
||||
|
||||
func (i *_IGameInputDevice) SetRumbleState(params *_GameInputRumbleParams, timestamp uint64) {
|
||||
_, _, _ = syscall.Syscall(i.vtbl.SetRumbleState, 3, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(params)), uintptr(timestamp))
|
||||
runtime.KeepAlive(params)
|
||||
}
|
||||
|
||||
type _IGameInputReading struct {
|
||||
vtbl *_IGameInputReading_Vtbl
|
||||
}
|
||||
|
||||
type _IGameInputReading_Vtbl struct {
|
||||
QueryInterface uintptr
|
||||
AddRef uintptr
|
||||
Release uintptr
|
||||
|
||||
GetInputKind uintptr
|
||||
GetSequenceNumber uintptr
|
||||
GetTimestamp uintptr
|
||||
GetDevice uintptr
|
||||
GetRawReport uintptr
|
||||
GetControllerAxisCount uintptr
|
||||
GetControllerAxisState uintptr
|
||||
GetControllerButtonCount uintptr
|
||||
GetControllerButtonState uintptr
|
||||
GetControllerSwitchCount uintptr
|
||||
GetControllerSwitchState uintptr
|
||||
GetKeyCount uintptr
|
||||
GetKeyState uintptr
|
||||
GetMouseState uintptr
|
||||
GetTouchCount uintptr
|
||||
GetTouchState uintptr
|
||||
GetMotionState uintptr
|
||||
GetArcadeStickState uintptr
|
||||
GetFlightStickState uintptr
|
||||
GetGamepadState uintptr
|
||||
GetRacingWheelState uintptr
|
||||
GetUiNavigationState uintptr
|
||||
}
|
||||
|
||||
func (i *_IGameInputReading) GetGamepadState() (_GameInputGamepadState, bool) {
|
||||
var state _GameInputGamepadState
|
||||
r, _, _ := syscall.Syscall(i.vtbl.GetGamepadState, 2, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(&state)), 0)
|
||||
return state, int32(r) != 0
|
||||
}
|
||||
|
||||
func (i *_IGameInputReading) Release() uint32 {
|
||||
r, _, _ := syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0)
|
||||
return uint32(r)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// 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
|
||||
|
||||
type Button int
|
||||
|
||||
const (
|
||||
Button0 Button = iota
|
||||
Button1
|
||||
Button2
|
||||
Button3
|
||||
Button4
|
||||
Button5
|
||||
Button6
|
||||
Button7
|
||||
Button8
|
||||
Button9
|
||||
Button10
|
||||
Button11
|
||||
Button12
|
||||
Button13
|
||||
Button14
|
||||
Button15
|
||||
Button16
|
||||
Button17
|
||||
Button18
|
||||
Button19
|
||||
Button20
|
||||
Button21
|
||||
Button22
|
||||
Button23
|
||||
Button24
|
||||
Button25
|
||||
Button26
|
||||
Button27
|
||||
Button28
|
||||
Button29
|
||||
Button30
|
||||
Button31
|
||||
)
|
||||
|
||||
const ButtonCount = 32
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// 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 (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
func AddAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) {
|
||||
theGamepads.addAndroidGamepad(androidDeviceID, name, sdlID, axisCount, hatCount)
|
||||
}
|
||||
|
||||
func RemoveAndroidGamepad(androidDeviceID int) {
|
||||
theGamepads.removeAndroidGamepad(androidDeviceID)
|
||||
}
|
||||
|
||||
func UpdateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
|
||||
theGamepads.updateAndroidGamepadAxis(androidDeviceID, axis, value)
|
||||
}
|
||||
|
||||
func UpdateAndroidGamepadButton(androidDeviceID int, button Button, pressed bool) {
|
||||
theGamepads.updateAndroidGamepadButton(androidDeviceID, button, pressed)
|
||||
}
|
||||
|
||||
func UpdateAndroidGamepadHat(androidDeviceID int, hat int, xValue, yValue int) {
|
||||
theGamepads.updateAndroidGamepadHat(androidDeviceID, hat, xValue, yValue)
|
||||
}
|
||||
|
||||
func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
gp := g.add(name, sdlID)
|
||||
gp.native = &nativeGamepadImpl{
|
||||
androidDeviceID: androidDeviceID,
|
||||
axesReady: make([]bool, axisCount),
|
||||
axes: make([]float64, axisCount),
|
||||
buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1),
|
||||
hats: make([]int, hatCount),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gamepads) removeAndroidGamepad(androidDeviceID int) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
g.remove(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
|
||||
})
|
||||
}
|
||||
|
||||
func (g *gamepads) updateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
gp := g.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
|
||||
})
|
||||
if gp == nil {
|
||||
return
|
||||
}
|
||||
gp.updateAndroidGamepadAxis(axis, value)
|
||||
}
|
||||
|
||||
func (g *gamepads) updateAndroidGamepadButton(androidDeviceID int, button Button, pressed bool) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
gp := g.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
|
||||
})
|
||||
if gp == nil {
|
||||
return
|
||||
}
|
||||
gp.updateAndroidGamepadButton(button, pressed)
|
||||
}
|
||||
|
||||
func (g *gamepads) updateAndroidGamepadHat(androidDeviceID int, hat int, xValue, yValue int) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
gp := g.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
|
||||
})
|
||||
if gp == nil {
|
||||
return
|
||||
}
|
||||
gp.updateAndroidGamepadHat(hat, xValue, yValue)
|
||||
}
|
||||
|
||||
func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
n := g.native.(*nativeGamepadImpl)
|
||||
if axis < 0 || axis >= len(n.axes) {
|
||||
return
|
||||
}
|
||||
n.axes[axis] = value
|
||||
|
||||
// MotionEvent with 0 value can be sent when a gamepad is connected even though an axis is not touched (#2598).
|
||||
// This is problematic when an axis is a trigger button where -1 should be the default value.
|
||||
// When MotionEvent with non-0 value is sent, it seems fine to assume that the axis is actually touched and ready.
|
||||
if value != 0 {
|
||||
n.axesReady[axis] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gamepad) updateAndroidGamepadButton(button Button, pressed bool) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
n := g.native.(*nativeGamepadImpl)
|
||||
if button < 0 || int(button) >= len(n.buttons) {
|
||||
return
|
||||
}
|
||||
n.buttons[button] = pressed
|
||||
}
|
||||
|
||||
func (g *Gamepad) updateAndroidGamepadHat(hat int, xValue, yValue int) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
n := g.native.(*nativeGamepadImpl)
|
||||
if hat < 0 || hat >= len(n.hats) {
|
||||
return
|
||||
}
|
||||
var v int
|
||||
switch {
|
||||
case xValue < 0:
|
||||
v |= hatLeft
|
||||
case xValue > 0:
|
||||
v |= hatRight
|
||||
}
|
||||
switch {
|
||||
case yValue < 0:
|
||||
v |= hatUp
|
||||
case yValue > 0:
|
||||
v |= hatDown
|
||||
}
|
||||
n.hats[hat] = v
|
||||
|
||||
// Update the gamepad buttons in addition to hats.
|
||||
// See https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L290-L301
|
||||
n.buttons[gamepaddb.SDLControllerButtonDpadLeft] = v&hatLeft != 0
|
||||
n.buttons[gamepaddb.SDLControllerButtonDpadRight] = v&hatRight != 0
|
||||
n.buttons[gamepaddb.SDLControllerButtonDpadUp] = v&hatUp != 0
|
||||
n.buttons[gamepaddb.SDLControllerButtonDpadDown] = v&hatDown != 0
|
||||
}
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
// 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 (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
type ID int
|
||||
|
||||
const (
|
||||
hatCentered = 0
|
||||
hatUp = 1
|
||||
hatRight = 2
|
||||
hatDown = 4
|
||||
hatLeft = 8
|
||||
hatRightUp = hatRight | hatUp
|
||||
hatRightDown = hatRight | hatDown
|
||||
hatLeftUp = hatLeft | hatUp
|
||||
hatLeftDown = hatLeft | hatDown
|
||||
)
|
||||
|
||||
type gamepads struct {
|
||||
inited bool
|
||||
gamepads []*Gamepad
|
||||
m sync.Mutex
|
||||
|
||||
native nativeGamepads
|
||||
}
|
||||
|
||||
type nativeGamepads interface {
|
||||
init(gamepads *gamepads) error
|
||||
update(gamepads *gamepads) error
|
||||
}
|
||||
|
||||
var theGamepads = gamepads{
|
||||
native: newNativeGamepadsImpl(),
|
||||
}
|
||||
|
||||
// AppendGamepadIDs is concurrent-safe.
|
||||
func AppendGamepadIDs(ids []ID) []ID {
|
||||
return theGamepads.appendGamepadIDs(ids)
|
||||
}
|
||||
|
||||
// Update is concurrent-safe.
|
||||
func Update() error {
|
||||
return theGamepads.update()
|
||||
}
|
||||
|
||||
// Get is concurrent-safe.
|
||||
func Get(id ID) *Gamepad {
|
||||
return theGamepads.get(id)
|
||||
}
|
||||
|
||||
func SetNativeWindow(nativeWindow uintptr) {
|
||||
theGamepads.setNativeWindow(nativeWindow)
|
||||
}
|
||||
|
||||
func (g *gamepads) appendGamepadIDs(ids []ID) []ID {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
for i, gp := range g.gamepads {
|
||||
if gp != nil {
|
||||
ids = append(ids, ID(i))
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (g *gamepads) update() error {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if !g.inited {
|
||||
if err := g.native.init(g); err != nil {
|
||||
return err
|
||||
}
|
||||
g.inited = true
|
||||
}
|
||||
|
||||
if err := g.native.update(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// A gamepad can be detected even though there are not. Apparently, some special devices are
|
||||
// recognized as gamepads by OSes. In this case, the number of the 'buttons' can exceed the
|
||||
// maximum. Skip such devices as a tentative solution (#1173, #2039).
|
||||
g.remove(func(gamepad *Gamepad) bool {
|
||||
return gamepad.ButtonCount() > ButtonCount
|
||||
})
|
||||
|
||||
for _, gp := range g.gamepads {
|
||||
if gp == nil {
|
||||
continue
|
||||
}
|
||||
if err := gp.update(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gamepads) get(id ID) *Gamepad {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if id < 0 || int(id) >= len(g.gamepads) {
|
||||
return nil
|
||||
}
|
||||
return g.gamepads[id]
|
||||
}
|
||||
|
||||
func (g *gamepads) find(cond func(*Gamepad) bool) *Gamepad {
|
||||
for _, gp := range g.gamepads {
|
||||
if gp == nil {
|
||||
continue
|
||||
}
|
||||
if cond(gp) {
|
||||
return gp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gamepads) add(name, sdlID string) *Gamepad {
|
||||
for i, gp := range g.gamepads {
|
||||
if gp == nil {
|
||||
gp := &Gamepad{
|
||||
name: name,
|
||||
sdlID: sdlID,
|
||||
}
|
||||
g.gamepads[i] = gp
|
||||
return gp
|
||||
}
|
||||
}
|
||||
|
||||
gp := &Gamepad{
|
||||
name: name,
|
||||
sdlID: sdlID,
|
||||
}
|
||||
g.gamepads = append(g.gamepads, gp)
|
||||
return gp
|
||||
}
|
||||
|
||||
func (g *gamepads) remove(cond func(*Gamepad) bool) {
|
||||
for i, gp := range g.gamepads {
|
||||
if gp == nil {
|
||||
continue
|
||||
}
|
||||
if cond(gp) {
|
||||
g.gamepads[i] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gamepads) setNativeWindow(nativeWindow uintptr) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
var n any = g.native
|
||||
if n, ok := n.(interface{ setNativeWindow(uintptr) }); ok {
|
||||
n.setNativeWindow(nativeWindow)
|
||||
}
|
||||
}
|
||||
|
||||
type Gamepad struct {
|
||||
name string
|
||||
sdlID string
|
||||
m sync.Mutex
|
||||
|
||||
native nativeGamepad
|
||||
}
|
||||
|
||||
type mappingInput interface {
|
||||
Pressed() bool
|
||||
Value() float64 // Normalized to range: 0..1.
|
||||
}
|
||||
|
||||
type axisMappingInput struct {
|
||||
g nativeGamepad
|
||||
axis int
|
||||
}
|
||||
|
||||
func (a axisMappingInput) Pressed() bool {
|
||||
return a.g.axisValue(a.axis) > gamepaddb.ButtonPressedThreshold
|
||||
}
|
||||
|
||||
func (a axisMappingInput) Value() float64 {
|
||||
return a.g.axisValue(a.axis)*0.5 + 0.5
|
||||
}
|
||||
|
||||
type buttonMappingInput struct {
|
||||
g nativeGamepad
|
||||
button int
|
||||
}
|
||||
|
||||
func (b buttonMappingInput) Pressed() bool {
|
||||
return b.g.isButtonPressed(b.button)
|
||||
}
|
||||
|
||||
func (b buttonMappingInput) Value() float64 {
|
||||
return b.g.buttonValue(b.button)
|
||||
}
|
||||
|
||||
type hatMappingInput struct {
|
||||
g nativeGamepad
|
||||
hat int
|
||||
direction int
|
||||
}
|
||||
|
||||
func (h hatMappingInput) Pressed() bool {
|
||||
return h.g.hatState(h.hat)&h.direction != 0
|
||||
}
|
||||
|
||||
func (h hatMappingInput) Value() float64 {
|
||||
if h.Pressed() {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type nativeGamepad interface {
|
||||
update(gamepads *gamepads) error
|
||||
hasOwnStandardLayoutMapping() bool
|
||||
standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput
|
||||
standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput
|
||||
axisCount() int
|
||||
buttonCount() int
|
||||
hatCount() int
|
||||
isAxisReady(axis int) bool
|
||||
axisValue(axis int) float64
|
||||
buttonValue(button int) float64
|
||||
isButtonPressed(button int) bool
|
||||
hatState(hat int) int
|
||||
vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64)
|
||||
}
|
||||
|
||||
func (g *Gamepad) update(gamepads *gamepads) error {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.update(gamepads)
|
||||
}
|
||||
|
||||
// Name is concurrent-safe.
|
||||
func (g *Gamepad) Name() string {
|
||||
// This is immutable and doesn't have to be protected by a mutex.
|
||||
if name := gamepaddb.Name(g.sdlID); name != "" {
|
||||
return name
|
||||
}
|
||||
return g.name
|
||||
}
|
||||
|
||||
// SDLID is concurrent-safe.
|
||||
func (g *Gamepad) SDLID() string {
|
||||
// This is immutable and doesn't have to be protected by a mutex.
|
||||
return g.sdlID
|
||||
}
|
||||
|
||||
// AxisCount is concurrent-safe.
|
||||
func (g *Gamepad) AxisCount() int {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.axisCount()
|
||||
}
|
||||
|
||||
// ButtonCount is concurrent-safe.
|
||||
func (g *Gamepad) ButtonCount() int {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.buttonCount()
|
||||
}
|
||||
|
||||
// HatCount is concurrent-safe.
|
||||
func (g *Gamepad) HatCount() int {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.hatCount()
|
||||
}
|
||||
|
||||
// IsAxisReady is concurrent-safe.
|
||||
func (g *Gamepad) IsAxisReady(axis int) bool {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.isAxisReady(axis)
|
||||
}
|
||||
|
||||
// Axis is concurrent-safe.
|
||||
func (g *Gamepad) Axis(axis int) float64 {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.axisValue(axis)
|
||||
}
|
||||
|
||||
// Button is concurrent-safe.
|
||||
func (g *Gamepad) Button(button int) bool {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.isButtonPressed(button)
|
||||
}
|
||||
|
||||
// Hat is concurrent-safe.
|
||||
func (g *Gamepad) Hat(hat int) int {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
return g.native.hatState(hat)
|
||||
}
|
||||
|
||||
// IsStandardLayoutAvailable is concurrent-safe.
|
||||
func (g *Gamepad) IsStandardLayoutAvailable() bool {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||
return true
|
||||
}
|
||||
return g.native.hasOwnStandardLayoutMapping()
|
||||
}
|
||||
|
||||
// IsStandardAxisAvailable is concurrent safe.
|
||||
func (g *Gamepad) IsStandardAxisAvailable(axis gamepaddb.StandardAxis) bool {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||
return gamepaddb.HasStandardAxis(g.sdlID, axis)
|
||||
}
|
||||
return g.native.standardAxisInOwnMapping(axis) != nil
|
||||
}
|
||||
|
||||
// IsStandardButtonAvailable is concurrent safe.
|
||||
func (g *Gamepad) IsStandardButtonAvailable(button gamepaddb.StandardButton) bool {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||
return gamepaddb.HasStandardButton(g.sdlID, button)
|
||||
}
|
||||
return g.native.standardButtonInOwnMapping(button) != nil
|
||||
}
|
||||
|
||||
// StandardAxisValue is concurrent-safe.
|
||||
func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 {
|
||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||
// StandardAxisValue invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
|
||||
return gamepaddb.StandardAxisValue(g.sdlID, axis, g)
|
||||
}
|
||||
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if m := g.native.standardAxisInOwnMapping(axis); m != nil {
|
||||
return m.Value()*2 - 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// StandardButtonValue is concurrent-safe.
|
||||
func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 {
|
||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||
// StandardButtonValue invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
|
||||
return gamepaddb.StandardButtonValue(g.sdlID, button, g)
|
||||
}
|
||||
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if m := g.native.standardButtonInOwnMapping(button); m != nil {
|
||||
return m.Value()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// IsStandardButtonPressed is concurrent-safe.
|
||||
func (g *Gamepad) IsStandardButtonPressed(button gamepaddb.StandardButton) bool {
|
||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||
// IsStandardButtonPressed invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
|
||||
return gamepaddb.IsStandardButtonPressed(g.sdlID, button, g)
|
||||
}
|
||||
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
if m := g.native.standardButtonInOwnMapping(button); m != nil {
|
||||
return m.Pressed()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Vibrate is concurrent-safe.
|
||||
func (g *Gamepad) Vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
g.native.vibrate(duration, strongMagnitude, weakMagnitude)
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
// 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 (
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
type nativeGamepadsImpl struct{}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct {
|
||||
androidDeviceID int
|
||||
|
||||
axesReady []bool
|
||||
axes []float64
|
||||
buttons []bool
|
||||
hats []int
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
|
||||
// Do nothing. The state of gamepads are given via APIs in extern_android.go.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisCount() int {
|
||||
return len(g.axes)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonCount() int {
|
||||
return len(g.buttons)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatCount() int {
|
||||
return len(g.hats)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||
if axis < 0 || axis >= len(g.axesReady) {
|
||||
return false
|
||||
}
|
||||
return g.axesReady[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||
if axis < 0 || axis >= len(g.axes) {
|
||||
return 0
|
||||
}
|
||||
return g.axes[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
|
||||
if button < 0 || button >= len(g.buttons) {
|
||||
return false
|
||||
}
|
||||
return g.buttons[button]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
if g.isButtonPressed(button) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatState(hat int) int {
|
||||
if hat < 0 || hat >= len(g.hats) {
|
||||
return 0
|
||||
}
|
||||
return g.hats[hat]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
||||
+436
@@ -0,0 +1,436 @@
|
||||
// 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.
|
||||
|
||||
//go:build !ios
|
||||
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
type nativeGamepadsImpl struct {
|
||||
hidManager _IOHIDManagerRef
|
||||
devicesToAdd []_IOHIDDeviceRef
|
||||
devicesToRemove []_IOHIDDeviceRef
|
||||
devicesM sync.Mutex
|
||||
}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
if err := initializeCF(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := initializeIOKit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dicts []_CFDictionaryRef
|
||||
|
||||
page := kHIDPage_GenericDesktop
|
||||
for _, usage := range []uint{
|
||||
kHIDUsage_GD_Joystick,
|
||||
kHIDUsage_GD_GamePad,
|
||||
kHIDUsage_GD_MultiAxisController,
|
||||
} {
|
||||
pageRef := _CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, unsafe.Pointer(&page))
|
||||
if pageRef == 0 {
|
||||
return errors.New("gamepad: CFNumberCreate returned nil")
|
||||
}
|
||||
defer _CFRelease(_CFTypeRef(pageRef))
|
||||
|
||||
usageRef := _CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, unsafe.Pointer(&usage))
|
||||
if usageRef == 0 {
|
||||
return errors.New("gamepad: CFNumberCreate returned nil")
|
||||
}
|
||||
defer _CFRelease(_CFTypeRef(usageRef))
|
||||
|
||||
keys := []_CFStringRef{
|
||||
_CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDDeviceUsagePageKey, kCFStringEncodingUTF8),
|
||||
_CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDDeviceUsageKey, kCFStringEncodingUTF8),
|
||||
}
|
||||
values := []_CFNumberRef{
|
||||
pageRef,
|
||||
usageRef,
|
||||
}
|
||||
|
||||
dict := _CFDictionaryCreate(kCFAllocatorDefault,
|
||||
(*unsafe.Pointer)(unsafe.Pointer(&keys[0])),
|
||||
(*unsafe.Pointer)(unsafe.Pointer(&values[0])),
|
||||
_CFIndex(len(keys)), *(**_CFDictionaryKeyCallBacks)(unsafe.Pointer(&kCFTypeDictionaryKeyCallBacks)), *(**_CFDictionaryValueCallBacks)(unsafe.Pointer(&kCFTypeDictionaryValueCallBacks)))
|
||||
if dict == 0 {
|
||||
return errors.New("gamepad: CFDictionaryCreate returned nil")
|
||||
}
|
||||
defer _CFRelease(_CFTypeRef(dict))
|
||||
|
||||
dicts = append(dicts, dict)
|
||||
}
|
||||
|
||||
matching := _CFArrayCreate(kCFAllocatorDefault,
|
||||
(*unsafe.Pointer)(unsafe.Pointer(&dicts[0])),
|
||||
_CFIndex(len(dicts)), *(**_CFArrayCallBacks)(unsafe.Pointer(&kCFTypeArrayCallBacks)))
|
||||
if matching == 0 {
|
||||
return errors.New("gamepad: CFArrayCreateMutable returned nil")
|
||||
}
|
||||
defer _CFRelease(_CFTypeRef(matching))
|
||||
|
||||
g.hidManager = _IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone)
|
||||
if _IOHIDManagerOpen(g.hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess {
|
||||
return errors.New("gamepad: IOHIDManagerOpen failed")
|
||||
}
|
||||
|
||||
_IOHIDManagerSetDeviceMatchingMultiple(g.hidManager, matching)
|
||||
_IOHIDManagerRegisterDeviceMatchingCallback(g.hidManager, ebitenGamepadMatchingCallback, nil)
|
||||
_IOHIDManagerRegisterDeviceRemovalCallback(g.hidManager, ebitenGamepadRemovalCallback, nil)
|
||||
|
||||
_IOHIDManagerScheduleWithRunLoop(g.hidManager, _CFRunLoopGetMain(), **(**_CFStringRef)(unsafe.Pointer(&kCFRunLoopDefaultMode)))
|
||||
|
||||
// Execute the run loop once in order to register any initially-attached gamepads.
|
||||
_CFRunLoopRunInMode(**(**_CFStringRef)(unsafe.Pointer(&kCFRunLoopDefaultMode)), 0, false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ebitenGamepadMatchingCallback(ctx unsafe.Pointer, res _IOReturn, sender unsafe.Pointer, device _IOHIDDeviceRef) {
|
||||
n := theGamepads.native.(*nativeGamepadsImpl)
|
||||
n.devicesM.Lock()
|
||||
defer n.devicesM.Unlock()
|
||||
n.devicesToAdd = append(n.devicesToAdd, device)
|
||||
}
|
||||
|
||||
func ebitenGamepadRemovalCallback(ctx unsafe.Pointer, res _IOReturn, sender unsafe.Pointer, device _IOHIDDeviceRef) {
|
||||
n := theGamepads.native.(*nativeGamepadsImpl)
|
||||
n.devicesM.Lock()
|
||||
defer n.devicesM.Unlock()
|
||||
n.devicesToRemove = append(n.devicesToRemove, device)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
n := theGamepads.native.(*nativeGamepadsImpl)
|
||||
n.devicesM.Lock()
|
||||
defer n.devicesM.Unlock()
|
||||
|
||||
for _, device := range g.devicesToAdd {
|
||||
g.addDevice(device, gamepads)
|
||||
}
|
||||
for _, device := range g.devicesToRemove {
|
||||
gamepads.remove(func(g *Gamepad) bool {
|
||||
return g.native.(*nativeGamepadImpl).device == device
|
||||
})
|
||||
}
|
||||
g.devicesToAdd = g.devicesToAdd[:0]
|
||||
g.devicesToRemove = g.devicesToRemove[:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) addDevice(device _IOHIDDeviceRef, gamepads *gamepads) {
|
||||
if gamepads.find(func(g *Gamepad) bool {
|
||||
return g.native.(*nativeGamepadImpl).device == device
|
||||
}) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := "Unknown"
|
||||
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductKey, kCFStringEncodingUTF8)); prop != 0 {
|
||||
var cstr [256]byte
|
||||
_CFStringGetCString(_CFStringRef(prop), cstr[:], kCFStringEncodingUTF8)
|
||||
name = strings.TrimRight(string(cstr[:]), "\x00")
|
||||
}
|
||||
|
||||
var vendor uint32
|
||||
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDVendorIDKey, kCFStringEncodingUTF8)); prop != 0 {
|
||||
_CFNumberGetValue(_CFNumberRef(prop), kCFNumberSInt32Type, unsafe.Pointer(&vendor))
|
||||
}
|
||||
|
||||
var product uint32
|
||||
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductIDKey, kCFStringEncodingUTF8)); prop != 0 {
|
||||
_CFNumberGetValue(_CFNumberRef(prop), kCFNumberSInt32Type, unsafe.Pointer(&product))
|
||||
}
|
||||
|
||||
var version uint32
|
||||
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDVersionNumberKey, kCFStringEncodingUTF8)); prop != 0 {
|
||||
_CFNumberGetValue(_CFNumberRef(prop), kCFNumberSInt32Type, unsafe.Pointer(&version))
|
||||
}
|
||||
|
||||
var sdlID string
|
||||
if vendor != 0 && product != 0 {
|
||||
sdlID = fmt.Sprintf("03000000%02x%02x0000%02x%02x0000%02x%02x0000",
|
||||
byte(vendor), byte(vendor>>8),
|
||||
byte(product), byte(product>>8),
|
||||
byte(version), byte(version>>8))
|
||||
} 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])
|
||||
}
|
||||
|
||||
elements := _IOHIDDeviceCopyMatchingElements(device, 0, kIOHIDOptionsTypeNone)
|
||||
defer _CFRelease(_CFTypeRef(elements))
|
||||
|
||||
n := &nativeGamepadImpl{
|
||||
device: device,
|
||||
}
|
||||
gp := gamepads.add(name, sdlID)
|
||||
gp.native = n
|
||||
|
||||
for i := _CFIndex(0); i < _CFArrayGetCount(elements); i++ {
|
||||
native := (_IOHIDElementRef)(_CFArrayGetValueAtIndex(elements, i))
|
||||
if _CFGetTypeID(_CFTypeRef(native)) != _IOHIDElementGetTypeID() {
|
||||
continue
|
||||
}
|
||||
|
||||
typ := _IOHIDElementGetType(native)
|
||||
if typ != kIOHIDElementTypeInput_Axis &&
|
||||
typ != kIOHIDElementTypeInput_Button &&
|
||||
typ != kIOHIDElementTypeInput_Misc {
|
||||
continue
|
||||
}
|
||||
|
||||
usage := _IOHIDElementGetUsage(native)
|
||||
page := _IOHIDElementGetUsagePage(native)
|
||||
|
||||
switch page {
|
||||
case kHIDPage_GenericDesktop:
|
||||
switch usage {
|
||||
case kHIDUsage_GD_X, kHIDUsage_GD_Y, kHIDUsage_GD_Z,
|
||||
kHIDUsage_GD_Rx, kHIDUsage_GD_Ry, kHIDUsage_GD_Rz,
|
||||
kHIDUsage_GD_Slider, kHIDUsage_GD_Dial, kHIDUsage_GD_Wheel:
|
||||
n.axes = append(n.axes, element{
|
||||
native: native,
|
||||
usage: int(usage),
|
||||
index: len(n.axes),
|
||||
minimum: int(_IOHIDElementGetLogicalMin(native)),
|
||||
maximum: int(_IOHIDElementGetLogicalMax(native)),
|
||||
})
|
||||
case kHIDUsage_GD_Hatswitch:
|
||||
n.hats = append(n.hats, element{
|
||||
native: native,
|
||||
usage: int(usage),
|
||||
index: len(n.hats),
|
||||
minimum: int(_IOHIDElementGetLogicalMin(native)),
|
||||
maximum: int(_IOHIDElementGetLogicalMax(native)),
|
||||
})
|
||||
case kHIDUsage_GD_DPadUp, kHIDUsage_GD_DPadRight, kHIDUsage_GD_DPadDown, kHIDUsage_GD_DPadLeft,
|
||||
kHIDUsage_GD_SystemMainMenu, kHIDUsage_GD_Select, kHIDUsage_GD_Start:
|
||||
n.buttons = append(n.buttons, element{
|
||||
native: native,
|
||||
usage: int(usage),
|
||||
index: len(n.buttons),
|
||||
minimum: int(_IOHIDElementGetLogicalMin(native)),
|
||||
maximum: int(_IOHIDElementGetLogicalMax(native)),
|
||||
})
|
||||
}
|
||||
case kHIDPage_Simulation:
|
||||
switch usage {
|
||||
case kHIDUsage_Sim_Accelerator, kHIDUsage_Sim_Brake, kHIDUsage_Sim_Throttle, kHIDUsage_Sim_Rudder, kHIDUsage_Sim_Steering:
|
||||
n.axes = append(n.axes, element{
|
||||
native: native,
|
||||
usage: int(usage),
|
||||
index: len(n.axes),
|
||||
minimum: int(_IOHIDElementGetLogicalMin(native)),
|
||||
maximum: int(_IOHIDElementGetLogicalMax(native)),
|
||||
})
|
||||
}
|
||||
case kHIDPage_Button, kHIDPage_Consumer:
|
||||
n.buttons = append(n.buttons, element{
|
||||
native: native,
|
||||
usage: int(usage),
|
||||
index: len(n.buttons),
|
||||
minimum: int(_IOHIDElementGetLogicalMin(native)),
|
||||
maximum: int(_IOHIDElementGetLogicalMax(native)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Stable(n.axes)
|
||||
sort.Stable(n.buttons)
|
||||
sort.Stable(n.hats)
|
||||
}
|
||||
|
||||
type element struct {
|
||||
native _IOHIDElementRef
|
||||
usage int
|
||||
index int
|
||||
minimum int
|
||||
maximum int
|
||||
}
|
||||
|
||||
type elements []element
|
||||
|
||||
func (e elements) Len() int {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
func (e elements) Less(i, j int) bool {
|
||||
if e[i].usage != e[j].usage {
|
||||
return e[i].usage < e[j].usage
|
||||
}
|
||||
if e[i].index != e[j].index {
|
||||
return e[i].index < e[j].index
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e elements) Swap(i, j int) {
|
||||
e[i], e[j] = e[j], e[i]
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct {
|
||||
device _IOHIDDeviceRef
|
||||
axes elements
|
||||
buttons elements
|
||||
hats elements
|
||||
|
||||
axisValues []float64
|
||||
buttonValues []bool
|
||||
hatValues []int
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) elementValue(e *element) int {
|
||||
var valueRef _IOHIDValueRef
|
||||
if _IOHIDDeviceGetValue(g.device, e.native, &valueRef) == kIOReturnSuccess {
|
||||
return int(_IOHIDValueGetIntegerValue(valueRef))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) update(gamepads *gamepads) error {
|
||||
if cap(g.axisValues) < len(g.axes) {
|
||||
g.axisValues = make([]float64, len(g.axes))
|
||||
}
|
||||
g.axisValues = g.axisValues[:len(g.axes)]
|
||||
|
||||
if cap(g.buttonValues) < len(g.buttons) {
|
||||
g.buttonValues = make([]bool, len(g.buttons))
|
||||
}
|
||||
g.buttonValues = g.buttonValues[:len(g.buttons)]
|
||||
|
||||
if cap(g.hatValues) < len(g.hats) {
|
||||
g.hatValues = make([]int, len(g.hats))
|
||||
}
|
||||
g.hatValues = g.hatValues[:len(g.hats)]
|
||||
|
||||
for i, a := range g.axes {
|
||||
raw := g.elementValue(&a)
|
||||
if raw < a.minimum {
|
||||
a.minimum = raw
|
||||
}
|
||||
if raw > a.maximum {
|
||||
a.maximum = raw
|
||||
}
|
||||
var value float64
|
||||
if size := a.maximum - a.minimum; size != 0 {
|
||||
value = 2*float64(raw-a.minimum)/float64(size) - 1
|
||||
}
|
||||
g.axisValues[i] = value
|
||||
}
|
||||
|
||||
for i, b := range g.buttons {
|
||||
g.buttonValues[i] = (g.elementValue(&b) - b.minimum) > 0
|
||||
}
|
||||
|
||||
hatStates := []int{
|
||||
hatUp,
|
||||
hatRightUp,
|
||||
hatRight,
|
||||
hatRightDown,
|
||||
hatDown,
|
||||
hatLeftDown,
|
||||
hatLeft,
|
||||
hatLeftUp,
|
||||
}
|
||||
for i, h := range g.hats {
|
||||
if state := g.elementValue(&h) - h.minimum; state < 0 || state >= len(hatStates) {
|
||||
g.hatValues[i] = hatCentered
|
||||
} else {
|
||||
g.hatValues[i] = hatStates[state]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisCount() int {
|
||||
return len(g.axisValues)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonCount() int {
|
||||
return len(g.buttonValues)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatCount() int {
|
||||
return len(g.hatValues)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||
return axis >= 0 && axis < g.axisCount()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||
if axis < 0 || axis >= len(g.axisValues) {
|
||||
return 0
|
||||
}
|
||||
return g.axisValues[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
if g.isButtonPressed(button) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
|
||||
if button < 0 || button >= len(g.buttonValues) {
|
||||
return false
|
||||
}
|
||||
return g.buttonValues[button]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatState(hat int) int {
|
||||
if hat < 0 || hat >= len(g.hatValues) {
|
||||
return hatCentered
|
||||
}
|
||||
return g.hatValues[hat]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
||||
Generated
Vendored
+837
@@ -0,0 +1,837 @@
|
||||
// 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"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
type dinputObjectType int
|
||||
|
||||
const (
|
||||
dinputObjectTypeAxis dinputObjectType = iota
|
||||
dinputObjectTypeSlider
|
||||
dinputObjectTypeButton
|
||||
dinputObjectTypePOV
|
||||
)
|
||||
|
||||
var dinputObjectDataFormats = []_DIOBJECTDATAFORMAT{
|
||||
{&_GUID_XAxis, _DIJOFS_X, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_YAxis, _DIJOFS_Y, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_ZAxis, _DIJOFS_Z, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_RxAxis, _DIJOFS_RX, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_RyAxis, _DIJOFS_RY, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_RzAxis, _DIJOFS_RZ, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_Slider, _DIJOFS_SLIDER(0), _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_Slider, _DIJOFS_SLIDER(1), _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
|
||||
{&_GUID_POV, _DIJOFS_POV(0), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{&_GUID_POV, _DIJOFS_POV(1), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{&_GUID_POV, _DIJOFS_POV(2), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{&_GUID_POV, _DIJOFS_POV(3), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(0), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(1), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(2), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(3), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(4), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(5), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(6), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(7), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(8), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(9), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(10), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(11), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(12), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(13), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(14), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(15), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(16), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(17), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(18), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(19), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(20), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(21), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(22), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(23), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(24), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(25), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(26), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(27), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(28), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(29), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(30), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
|
||||
{nil, _DIJOFS_BUTTON(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 nativeGamepadsDesktop struct {
|
||||
dinput8 windows.Handle
|
||||
dinput8API *_IDirectInput8W
|
||||
xinput windows.Handle
|
||||
|
||||
procDirectInput8Create uintptr
|
||||
procXInputGetCapabilities uintptr
|
||||
procXInputGetState uintptr
|
||||
|
||||
origWndProc uintptr
|
||||
wndProcCallback uintptr
|
||||
enumDevicesCallback uintptr
|
||||
enumObjectsCallback uintptr
|
||||
|
||||
nativeWindow windows.HWND
|
||||
deviceChanged int32
|
||||
err error
|
||||
}
|
||||
|
||||
type dinputObject struct {
|
||||
objectType dinputObjectType
|
||||
index int
|
||||
}
|
||||
|
||||
type enumObjectsContext struct {
|
||||
device *_IDirectInputDevice8W
|
||||
objects []dinputObject
|
||||
axisCount int
|
||||
sliderCount int
|
||||
buttonCount int
|
||||
povCount int
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) init(gamepads *gamepads) 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 {
|
||||
// TODO: Use _GetModuleHandleExW to align with GLFW v3.3.8.
|
||||
m, err := _GetModuleHandleW()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var api *_IDirectInput8W
|
||||
if err := g.directInput8Create(m, _DIRECTINPUT_VERSION, &_IID_IDirectInput8W, &api, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
g.dinput8API = api
|
||||
|
||||
if err := g.detectConnection(gamepads); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) directInput8Create(hinst uintptr, dwVersion uint32, riidltf *windows.GUID, ppvOut **_IDirectInput8W, punkOuter unsafe.Pointer) error {
|
||||
r, _, _ := syscall.Syscall6(g.procDirectInput8Create, 5,
|
||||
hinst, uintptr(dwVersion), uintptr(unsafe.Pointer(riidltf)), uintptr(unsafe.Pointer(ppvOut)), uintptr(punkOuter),
|
||||
0)
|
||||
if uint32(r) != _DI_OK {
|
||||
return fmt.Errorf("gamepad: DirectInput8Create failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) xinputGetCapabilities(dwUserIndex uint32, dwFlags uint32, pCapabilities *_XINPUT_CAPABILITIES) error {
|
||||
// XInputGetCapabilities doesn't call SetLastError and returns an error code directly.
|
||||
r, _, _ := syscall.Syscall(g.procXInputGetCapabilities, 3,
|
||||
uintptr(dwUserIndex), uintptr(dwFlags), uintptr(unsafe.Pointer(pCapabilities)))
|
||||
if e := syscall.Errno(uint32(r)); e != windows.ERROR_SUCCESS {
|
||||
return fmt.Errorf("gamepad: XInputGetCapabilities failed: %w", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) xinputGetState(dwUserIndex uint32, pState *_XINPUT_STATE) error {
|
||||
// XInputGetState doesn't call SetLastError and returns an error code directly.
|
||||
r, _, _ := syscall.Syscall(g.procXInputGetState, 2,
|
||||
uintptr(dwUserIndex), uintptr(unsafe.Pointer(pState)), 0)
|
||||
if e := syscall.Errno(uint32(r)); e != windows.ERROR_SUCCESS {
|
||||
return fmt.Errorf("gamepad: XInputGetCapabilities failed: %w", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) detectConnection(gamepads *gamepads) error {
|
||||
if g.dinput8 != 0 {
|
||||
if g.enumDevicesCallback == 0 {
|
||||
g.enumDevicesCallback = windows.NewCallback(g.dinput8EnumDevicesCallback)
|
||||
}
|
||||
if err := g.dinput8API.EnumDevices(_DI8DEVCLASS_GAMECTRL, g.enumDevicesCallback, unsafe.Pointer(gamepads), _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 gamepads.find(func(g *Gamepad) bool {
|
||||
n := g.native.(*nativeGamepadDesktop)
|
||||
return n.dinputDevice == nil && n.xinputIndex == i
|
||||
}) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var xic _XINPUT_CAPABILITIES
|
||||
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 := gamepads.add(name, sdlID)
|
||||
gp.native = &nativeGamepadDesktop{
|
||||
xinputIndex: i,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) dinput8EnumDevicesCallback(lpddi *_DIDEVICEINSTANCEW, pvRef unsafe.Pointer) uintptr {
|
||||
gamepads := (*gamepads)(pvRef)
|
||||
|
||||
if g.err != nil {
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// lpddi.guidInstance is not relialable as a unique identity when the same multiple devices are connected (#3046).
|
||||
// Use HID Path instead.
|
||||
getDInputPath := func(device *_IDirectInputDevice8W) (string, error) {
|
||||
var prop _DIPROPGUIDANDPATH
|
||||
prop.diph.dwHeaderSize = uint32(unsafe.Sizeof(_DIPROPHEADER{}))
|
||||
prop.diph.dwSize = uint32(unsafe.Sizeof(_DIPROPGUIDANDPATH{}))
|
||||
if err := device.GetProperty(_DIPROP_GUIDANDPATH, &prop.diph); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return windows.UTF16ToString(prop.wszPath[:]), nil
|
||||
}
|
||||
dinputPath, err := getDInputPath(device)
|
||||
if err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
var findErr error
|
||||
if gamepads.find(func(g *Gamepad) bool {
|
||||
// A DInput device can be nil when the device is an XInput device (#3047).
|
||||
d := g.native.(*nativeGamepadDesktop).dinputDevice
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
path, err := getDInputPath(d)
|
||||
if err != nil {
|
||||
findErr = err
|
||||
return true
|
||||
}
|
||||
return path == dinputPath
|
||||
}) != nil {
|
||||
if findErr != nil {
|
||||
g.err = findErr
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
device.Release()
|
||||
return _DIENUM_CONTINUE
|
||||
}
|
||||
|
||||
dataFormat := _DIDATAFORMAT{
|
||||
dwSize: uint32(unsafe.Sizeof(_DIDATAFORMAT{})),
|
||||
dwObjSize: uint32(unsafe.Sizeof(_DIOBJECTDATAFORMAT{})),
|
||||
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 := gamepads.add(name, sdlID)
|
||||
gp.native = &nativeGamepadDesktop{
|
||||
dinputDevice: device,
|
||||
dinputObjects: ctx.objects,
|
||||
dinputPath: dinputPath,
|
||||
dinputAxes: make([]float64, ctx.axisCount+ctx.sliderCount),
|
||||
dinputButtons: make([]bool, ctx.buttonCount),
|
||||
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
|
||||
}
|
||||
|
||||
if count == 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 := _RID_DEVICE_INFO{
|
||||
cbSize: uint32(unsafe.Sizeof(_RID_DEVICE_INFO{})),
|
||||
}
|
||||
size := uint32(unsafe.Sizeof(rdi))
|
||||
if _, err := _GetRawInputDeviceInfoW(ridl[i].hDevice, _RIDI_DEVICEINFO, unsafe.Pointer(&rdi), &size); err != nil {
|
||||
// GetRawInputDeviceInfoW can return an error (#2603).
|
||||
continue
|
||||
}
|
||||
|
||||
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 *nativeGamepadsDesktop) dinputDevice8EnumObjectsCallback(lpddoi *_DIDEVICEOBJECTINSTANCEW, pvRef unsafe.Pointer) uintptr {
|
||||
ctx := (*enumObjectsContext)(pvRef)
|
||||
|
||||
switch {
|
||||
case _DIDFT_GETTYPE(lpddoi.dwType)&_DIDFT_AXIS != 0:
|
||||
var index int
|
||||
switch lpddoi.guidType {
|
||||
case _GUID_Slider:
|
||||
index = ctx.sliderCount
|
||||
case _GUID_XAxis:
|
||||
index = 0
|
||||
case _GUID_YAxis:
|
||||
index = 1
|
||||
case _GUID_ZAxis:
|
||||
index = 2
|
||||
case _GUID_RxAxis:
|
||||
index = 3
|
||||
case _GUID_RyAxis:
|
||||
index = 4
|
||||
case _GUID_RzAxis:
|
||||
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 == _GUID_Slider {
|
||||
objectType = dinputObjectTypeSlider
|
||||
ctx.sliderCount++
|
||||
} else {
|
||||
objectType = dinputObjectTypeAxis
|
||||
ctx.axisCount++
|
||||
}
|
||||
ctx.objects = append(ctx.objects, dinputObject{
|
||||
objectType: objectType,
|
||||
index: index,
|
||||
})
|
||||
case _DIDFT_GETTYPE(lpddoi.dwType)&_DIDFT_BUTTON != 0:
|
||||
ctx.objects = append(ctx.objects, dinputObject{
|
||||
objectType: dinputObjectTypeButton,
|
||||
index: ctx.buttonCount,
|
||||
})
|
||||
ctx.buttonCount++
|
||||
case _DIDFT_GETTYPE(lpddoi.dwType)&_DIDFT_POV != 0:
|
||||
ctx.objects = append(ctx.objects, dinputObject{
|
||||
objectType: dinputObjectTypePOV,
|
||||
index: ctx.povCount,
|
||||
})
|
||||
ctx.povCount++
|
||||
}
|
||||
|
||||
return _DIENUM_CONTINUE
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) update(gamepads *gamepads) error {
|
||||
if g.err != nil {
|
||||
return g.err
|
||||
}
|
||||
if g.origWndProc == 0 {
|
||||
if g.wndProcCallback == 0 {
|
||||
g.wndProcCallback = windows.NewCallback(g.wndProc)
|
||||
}
|
||||
// Note that a Win32API GetActiveWindow doesn't work on Xbox.
|
||||
h, err := _SetWindowLongPtrW(g.nativeWindow, _GWL_WNDPROC, g.wndProcCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.origWndProc = h
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&g.deviceChanged) != 0 {
|
||||
if err := g.detectConnection(gamepads); err != nil {
|
||||
g.err = err
|
||||
}
|
||||
atomic.StoreInt32(&g.deviceChanged, 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsDesktop) 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 (g *nativeGamepadsDesktop) setNativeWindow(nativeWindow uintptr) {
|
||||
g.nativeWindow = windows.HWND(nativeWindow)
|
||||
}
|
||||
|
||||
type nativeGamepadDesktop struct {
|
||||
dinputDevice *_IDirectInputDevice8W
|
||||
dinputObjects []dinputObject
|
||||
dinputPath string
|
||||
dinputAxes []float64
|
||||
dinputButtons []bool
|
||||
dinputHats []int
|
||||
|
||||
xinputIndex int
|
||||
xinputState _XINPUT_STATE
|
||||
}
|
||||
|
||||
func (*nativeGamepadDesktop) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nativeGamepadDesktop) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadDesktop) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadDesktop) usesDInput() bool {
|
||||
return g.dinputDevice != nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadDesktop) update(gamepads *gamepads) (err error) {
|
||||
var disconnected bool
|
||||
defer func() {
|
||||
if !disconnected && err == nil {
|
||||
return
|
||||
}
|
||||
gamepads.remove(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native == g
|
||||
})
|
||||
if g.dinputDevice != nil {
|
||||
g.dinputDevice.Release()
|
||||
}
|
||||
}()
|
||||
|
||||
if g.usesDInput() {
|
||||
if err := g.dinputDevice.Poll(); err != nil {
|
||||
if !errors.Is(err, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_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, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_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, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
|
||||
if !errors.Is(err, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_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 _XINPUT_STATE
|
||||
if err := gamepads.native.(*nativeGamepadsDesktop).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 *nativeGamepadDesktop) axisCount() int {
|
||||
if g.usesDInput() {
|
||||
return len(g.dinputAxes)
|
||||
}
|
||||
return 6
|
||||
}
|
||||
|
||||
func (g *nativeGamepadDesktop) buttonCount() int {
|
||||
if g.usesDInput() {
|
||||
return len(g.dinputButtons)
|
||||
}
|
||||
return len(xinputButtons)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadDesktop) hatCount() int {
|
||||
if g.usesDInput() {
|
||||
return len(g.dinputHats)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (g *nativeGamepadDesktop) isAxisReady(axis int) bool {
|
||||
return axis >= 0 && axis < g.axisCount()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadDesktop) 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 *nativeGamepadDesktop) 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 *nativeGamepadDesktop) buttonValue(button int) float64 {
|
||||
if g.isButtonPressed(button) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadDesktop) hatState(hat int) int {
|
||||
if g.usesDInput() {
|
||||
if hat < 0 || hat >= len(g.dinputHats) {
|
||||
return 0
|
||||
}
|
||||
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 *nativeGamepadDesktop) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// 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 (
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
type nativeGamepadsImpl struct{}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
initializeIOSGamepads()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct {
|
||||
controller uintptr
|
||||
buttonMask uint16
|
||||
hasDualshockTouchpad bool
|
||||
hasXboxPaddles bool
|
||||
hasXboxShareButton bool
|
||||
|
||||
axes []float64
|
||||
buttons []bool
|
||||
hats []int
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) update(gamepad *gamepads) error {
|
||||
g.updateIOSGamepad()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisCount() int {
|
||||
return len(g.axes)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonCount() int {
|
||||
return len(g.buttons)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatCount() int {
|
||||
return len(g.hats)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||
return axis >= 0 && axis < g.axisCount()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||
if axis < 0 || axis >= len(g.axes) {
|
||||
return 0
|
||||
}
|
||||
return g.axes[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
|
||||
if button < 0 || button >= len(g.buttons) {
|
||||
return false
|
||||
}
|
||||
return g.buttons[button]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
if g.isButtonPressed(button) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatState(hat int) int {
|
||||
if hat < 0 || hat >= len(g.hats) {
|
||||
return 0
|
||||
}
|
||||
return g.hats[hat]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
// 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 (
|
||||
"encoding/hex"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
var (
|
||||
object = js.Global().Get("Object")
|
||||
)
|
||||
|
||||
type nativeGamepadsImpl struct {
|
||||
indices map[int]struct{}
|
||||
}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
// TODO: Use the gamepad events instead of navigator.getGamepads.
|
||||
|
||||
defer func() {
|
||||
for k := range g.indices {
|
||||
delete(g.indices, k)
|
||||
}
|
||||
}()
|
||||
|
||||
nav := js.Global().Get("navigator")
|
||||
if !nav.Truthy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getGamepads might not exist under a non-secure context (#2100).
|
||||
if !nav.Get("getGamepads").Truthy() {
|
||||
js.Global().Get("console").Call("warn", "navigator.getGamepads is not available. This might require a secure (HTTPS) context.")
|
||||
return nil
|
||||
}
|
||||
|
||||
gps := nav.Call("getGamepads")
|
||||
if !gps.Truthy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l := gps.Length()
|
||||
for idx := 0; idx < l; idx++ {
|
||||
gp := gps.Index(idx)
|
||||
if !gp.Truthy() {
|
||||
continue
|
||||
}
|
||||
index := gp.Get("index").Int()
|
||||
|
||||
if g.indices == nil {
|
||||
g.indices = map[int]struct{}{}
|
||||
}
|
||||
g.indices[index] = struct{}{}
|
||||
|
||||
// The gamepad is not registered yet, register this.
|
||||
gamepad := gamepads.find(func(gamepad *Gamepad) bool {
|
||||
return index == gamepad.native.(*nativeGamepadImpl).index
|
||||
})
|
||||
if gamepad == nil {
|
||||
name := gp.Get("id").String()
|
||||
|
||||
// This emulates the implementation of EMSCRIPTEN_JoystickGetDeviceGUID.
|
||||
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/src/joystick/emscripten/SDL_sysjoystick.c#L385
|
||||
var sdlID [16]byte
|
||||
copy(sdlID[:], []byte(name))
|
||||
|
||||
gamepad = gamepads.add(name, hex.EncodeToString(sdlID[:]))
|
||||
gamepad.native = &nativeGamepadImpl{
|
||||
index: index,
|
||||
mapping: gp.Get("mapping").String(),
|
||||
}
|
||||
}
|
||||
gamepad.native.(*nativeGamepadImpl).value = gp
|
||||
}
|
||||
|
||||
// Remove an unused gamepads.
|
||||
gamepads.remove(func(gamepad *Gamepad) bool {
|
||||
_, ok := g.indices[gamepad.native.(*nativeGamepadImpl).index]
|
||||
return !ok
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct {
|
||||
value js.Value
|
||||
index int
|
||||
mapping string
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return g.mapping == "standard"
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
if !g.hasOwnStandardLayoutMapping() {
|
||||
return nil
|
||||
}
|
||||
if axis < 0 || int(axis) >= g.axisCount() {
|
||||
return nil
|
||||
}
|
||||
return axisMappingInput{g: g, axis: int(axis)}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
if !g.hasOwnStandardLayoutMapping() {
|
||||
return nil
|
||||
}
|
||||
if button < 0 || int(button) >= g.buttonCount() {
|
||||
return nil
|
||||
}
|
||||
return buttonMappingInput{g: g, button: int(button)}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisCount() int {
|
||||
return g.value.Get("axes").Length()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonCount() int {
|
||||
return g.value.Get("buttons").Length()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||
return axis >= 0 && axis < g.axisCount()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||
axes := g.value.Get("axes")
|
||||
if axis < 0 || axis >= axes.Length() {
|
||||
return 0
|
||||
}
|
||||
return axes.Index(axis).Float()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
buttons := g.value.Get("buttons")
|
||||
if button < 0 || button >= buttons.Length() {
|
||||
return 0
|
||||
}
|
||||
return buttons.Index(button).Get("value").Float()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
|
||||
buttons := g.value.Get("buttons")
|
||||
if button < 0 || button >= buttons.Length() {
|
||||
return false
|
||||
}
|
||||
return buttons.Index(button).Get("pressed").Bool()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatState(hat int) int {
|
||||
return hatCentered
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// vibrationActuator is available on Chrome.
|
||||
if va := g.value.Get("vibrationActuator"); va.Truthy() {
|
||||
if !va.Get("playEffect").Truthy() {
|
||||
return
|
||||
}
|
||||
|
||||
prop := object.New()
|
||||
prop.Set("startDelay", 0)
|
||||
prop.Set("duration", float64(duration/time.Millisecond))
|
||||
prop.Set("strongMagnitude", strongMagnitude)
|
||||
prop.Set("weakMagnitude", weakMagnitude)
|
||||
va.Call("playEffect", "dual-rumble", prop)
|
||||
return
|
||||
}
|
||||
|
||||
// hapticActuators is available on Firefox.
|
||||
if ha := g.value.Get("hapticActuators"); ha.Truthy() {
|
||||
// TODO: Is this order correct?
|
||||
if ha.Length() > 0 {
|
||||
ha.Index(0).Call("pulse", strongMagnitude, float64(duration/time.Millisecond))
|
||||
}
|
||||
if ha.Length() > 1 {
|
||||
ha.Index(1).Call("pulse", weakMagnitude, float64(duration/time.Millisecond))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
+629
@@ -0,0 +1,629 @@
|
||||
// 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.
|
||||
|
||||
//go:build !android && !nintendosdk && !playstation5
|
||||
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
const dirName = "/dev/input"
|
||||
|
||||
var reEvent = regexp.MustCompile(`^event[0-9]+$`)
|
||||
|
||||
func isBitSet(s []byte, bit int) bool {
|
||||
return s[bit/8]&(1<<(bit%8)) != 0
|
||||
}
|
||||
|
||||
type nativeGamepadsImpl struct {
|
||||
inotify int
|
||||
watch int
|
||||
}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
// Check the existence of the directory `dirName`.
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Stat(dirName, &stat); err != nil {
|
||||
if err == unix.ENOENT {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Stat failed: %w", err)
|
||||
}
|
||||
if stat.Mode&unix.S_IFDIR == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inotify, err := unix.InotifyInit1(unix.IN_NONBLOCK | unix.IN_CLOEXEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamepad: InotifyInit1 failed: %w", err)
|
||||
}
|
||||
g.inotify = inotify
|
||||
|
||||
if g.inotify > 0 {
|
||||
// Register for IN_ATTRIB to get notified when udev is done.
|
||||
// This works well in practice but the true way is libudev.
|
||||
watch, err := unix.InotifyAddWatch(g.inotify, dirName, unix.IN_CREATE|unix.IN_ATTRIB|unix.IN_DELETE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamepad: InotifyAddWatch failed: %w", err)
|
||||
}
|
||||
g.watch = watch
|
||||
}
|
||||
|
||||
ents, err := os.ReadDir(dirName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamepad: ReadDir(%s) failed: %w", dirName, err)
|
||||
}
|
||||
for _, ent := range ents {
|
||||
if ent.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !reEvent.MatchString(ent.Name()) {
|
||||
continue
|
||||
}
|
||||
if err := g.openGamepad(gamepads, filepath.Join(dirName, ent.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err error) {
|
||||
if gamepads.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).path == path
|
||||
}) != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
if err == unix.EACCES {
|
||||
return nil
|
||||
}
|
||||
// This happens with the Snap sandbox.
|
||||
if err == unix.EPERM {
|
||||
return nil
|
||||
}
|
||||
// This happens just after a disconnection.
|
||||
if err == unix.ENOENT {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Open failed: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = unix.Close(fd)
|
||||
}
|
||||
}()
|
||||
|
||||
evBits := make([]byte, (unix.EV_CNT+7)/8)
|
||||
keyBits := make([]byte, (_KEY_CNT+7)/8)
|
||||
absBits := make([]byte, (_ABS_CNT+7)/8)
|
||||
var id input_id
|
||||
if err := ioctl(fd, _EVIOCGBIT(0, uint(len(evBits))), unsafe.Pointer(&evBits[0])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for evBits failed: %w", err)
|
||||
}
|
||||
if err := ioctl(fd, _EVIOCGBIT(unix.EV_KEY, uint(len(keyBits))), unsafe.Pointer(&keyBits[0])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for keyBits failed: %w", err)
|
||||
}
|
||||
if err := ioctl(fd, _EVIOCGBIT(unix.EV_ABS, uint(len(absBits))), unsafe.Pointer(&absBits[0])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for absBits failed: %w", err)
|
||||
}
|
||||
if err := ioctl(fd, _EVIOCGID(), unsafe.Pointer(&id)); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for an ID failed: %w", err)
|
||||
}
|
||||
|
||||
if !isBitSet(evBits, unix.EV_KEY) {
|
||||
if err := unix.Close(fd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
if !isBitSet(evBits, unix.EV_ABS) {
|
||||
if err := unix.Close(fd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
cname := make([]byte, 256)
|
||||
name := "Unknown"
|
||||
// TODO: Is it OK to ignore the error here?
|
||||
if err := ioctl(fd, uint(_EVIOCGNAME(uint(len(cname)))), unsafe.Pointer(&cname[0])); err == nil {
|
||||
name = unix.ByteSliceToString(cname)
|
||||
}
|
||||
|
||||
var sdlID string
|
||||
if id.vendor != 0 && id.product != 0 && id.version != 0 {
|
||||
sdlID = fmt.Sprintf("%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000",
|
||||
byte(id.bustype), byte(id.bustype>>8),
|
||||
byte(id.vendor), byte(id.vendor>>8),
|
||||
byte(id.product), byte(id.product>>8),
|
||||
byte(id.version), byte(id.version>>8))
|
||||
} else {
|
||||
bs := []byte(name)
|
||||
if len(bs) < 12 {
|
||||
bs = append(bs, make([]byte, 12-len(bs))...)
|
||||
}
|
||||
sdlID = fmt.Sprintf("%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
byte(id.bustype), byte(id.bustype>>8),
|
||||
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
|
||||
}
|
||||
|
||||
n := &nativeGamepadImpl{
|
||||
path: path,
|
||||
fd: fd,
|
||||
}
|
||||
gp := gamepads.add(name, sdlID)
|
||||
gp.native = n
|
||||
runtime.SetFinalizer(gp, func(gp *Gamepad) {
|
||||
n.close()
|
||||
})
|
||||
|
||||
var axisCount int
|
||||
var buttonCount int
|
||||
var hatCount int
|
||||
for i := range n.keyMap {
|
||||
n.keyMap[i] = -1
|
||||
}
|
||||
for i := range n.absMap {
|
||||
n.absMap[i] = -1
|
||||
}
|
||||
for code := _BTN_MISC; code < _KEY_CNT; code++ {
|
||||
if !isBitSet(keyBits, code) {
|
||||
continue
|
||||
}
|
||||
n.keyMap[code-_BTN_MISC] = buttonCount
|
||||
buttonCount++
|
||||
}
|
||||
for code := 0; code < _ABS_CNT; code++ {
|
||||
if !isBitSet(absBits, code) {
|
||||
continue
|
||||
}
|
||||
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
||||
// Write the hat index both for the X and the Y hat axis.
|
||||
// That way, the hat can be referenced using either axis, which is used by the code building hatMappingInput.
|
||||
n.absMap[code] = hatCount
|
||||
code++
|
||||
n.absMap[code] = hatCount
|
||||
hatCount++
|
||||
continue
|
||||
}
|
||||
if err := ioctl(n.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&n.absInfo[code])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for an abs at openGamepad failed: %w", err)
|
||||
}
|
||||
n.absMap[code] = axisCount
|
||||
axisCount++
|
||||
}
|
||||
|
||||
n.axisCount_ = axisCount
|
||||
n.buttonCount_ = buttonCount
|
||||
n.hatCount_ = hatCount
|
||||
|
||||
n.computeStandardLayout(id.vendor)
|
||||
|
||||
if err := n.pollAbsState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
if g.inotify <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, 16384)
|
||||
n, err := unix.Read(g.inotify, buf[:])
|
||||
if err != nil {
|
||||
if err == unix.EAGAIN {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Read failed: %w", err)
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
for len(buf) > 0 {
|
||||
e := unix.InotifyEvent{
|
||||
Wd: int32(buf[0]) | int32(buf[1])<<8 | int32(buf[2])<<16 | int32(buf[3])<<24,
|
||||
Mask: uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16 | uint32(buf[7])<<24,
|
||||
Cookie: uint32(buf[8]) | uint32(buf[9])<<8 | uint32(buf[10])<<16 | uint32(buf[11])<<24,
|
||||
Len: uint32(buf[12]) | uint32(buf[13])<<8 | uint32(buf[14])<<16 | uint32(buf[15])<<24,
|
||||
}
|
||||
name := unix.ByteSliceToString(buf[16 : 16+e.Len-1]) // len includes the null terminate.
|
||||
buf = buf[16+e.Len:]
|
||||
if !reEvent.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(dirName, name)
|
||||
if e.Mask&(unix.IN_CREATE|unix.IN_ATTRIB) != 0 {
|
||||
if err := g.openGamepad(gamepads, path); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if e.Mask&unix.IN_DELETE != 0 {
|
||||
if gp := gamepads.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).path == path
|
||||
}); gp != nil {
|
||||
gp.native.(*nativeGamepadImpl).close()
|
||||
gamepads.remove(func(gamepad *Gamepad) bool {
|
||||
return gamepad == gp
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct {
|
||||
fd int
|
||||
path string
|
||||
keyMap [_KEY_CNT - _BTN_MISC]int
|
||||
absMap [_ABS_CNT]int
|
||||
absInfo [_ABS_CNT]input_absinfo
|
||||
dropped bool
|
||||
|
||||
axes [_ABS_CNT]float64
|
||||
buttons [_KEY_CNT - _BTN_MISC]bool
|
||||
hats [4]int
|
||||
|
||||
axisCount_ int
|
||||
buttonCount_ int
|
||||
hatCount_ int
|
||||
|
||||
stdAxisMap map[gamepaddb.StandardAxis]mappingInput
|
||||
stdButtonMap map[gamepaddb.StandardButton]mappingInput
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) close() {
|
||||
if g.fd != 0 {
|
||||
_ = unix.Close(g.fd)
|
||||
}
|
||||
g.fd = 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) update(gamepad *gamepads) error {
|
||||
if g.fd == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
buf := make([]byte, unsafe.Sizeof(input_event{}))
|
||||
// TODO: Should the returned byte count be cared?
|
||||
if _, err := unix.Read(g.fd, buf); err != nil {
|
||||
if err == unix.EAGAIN {
|
||||
break
|
||||
}
|
||||
// Disconnected
|
||||
if err == unix.ENODEV {
|
||||
g.close()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Read failed: %w", err)
|
||||
}
|
||||
|
||||
const (
|
||||
offsetTyp = unsafe.Offsetof(input_event{}.typ)
|
||||
offsetCode = unsafe.Offsetof(input_event{}.code)
|
||||
offsetValue = unsafe.Offsetof(input_event{}.value)
|
||||
)
|
||||
// time is not used.
|
||||
e := input_event{
|
||||
typ: uint16(buf[offsetTyp]) | uint16(buf[offsetTyp+1])<<8,
|
||||
code: uint16(buf[offsetCode]) | uint16(buf[offsetCode+1])<<8,
|
||||
value: int32(buf[offsetValue]) | int32(buf[offsetValue+1])<<8 | int32(buf[offsetValue+2])<<16 | int32(buf[offsetValue+3])<<24,
|
||||
}
|
||||
|
||||
if e.typ == unix.EV_SYN {
|
||||
switch e.code {
|
||||
case _SYN_DROPPED:
|
||||
g.dropped = true
|
||||
case _SYN_REPORT:
|
||||
g.dropped = false
|
||||
if err := g.pollAbsState(); err != nil {
|
||||
return fmt.Errorf("gamepad: poll absolute state: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if g.dropped {
|
||||
continue
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
case unix.EV_KEY:
|
||||
if int(e.code-_BTN_MISC) < len(g.keyMap) {
|
||||
idx := g.keyMap[e.code-_BTN_MISC]
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
g.buttons[idx] = e.value != 0
|
||||
}
|
||||
case unix.EV_ABS:
|
||||
g.handleAbsEvent(int(e.code), e.value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) pollAbsState() error {
|
||||
for code := 0; code < _ABS_CNT; code++ {
|
||||
if g.absMap[code] < 0 {
|
||||
continue
|
||||
}
|
||||
if err := ioctl(g.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&g.absInfo[code])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for an abs at pollAbsState failed: %w", err)
|
||||
}
|
||||
g.handleAbsEvent(code, g.absInfo[code].value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) handleAbsEvent(code int, value int32) {
|
||||
index := g.absMap[code]
|
||||
if index < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
||||
axis := (code - _ABS_HAT0X) % 2
|
||||
|
||||
switch axis {
|
||||
case 0:
|
||||
switch {
|
||||
case value < 0:
|
||||
g.hats[index] |= hatLeft
|
||||
g.hats[index] &^= hatRight
|
||||
case value > 0:
|
||||
g.hats[index] &^= hatLeft
|
||||
g.hats[index] |= hatRight
|
||||
default:
|
||||
g.hats[index] &^= hatLeft | hatRight
|
||||
}
|
||||
case 1:
|
||||
switch {
|
||||
case value < 0:
|
||||
g.hats[index] |= hatUp
|
||||
g.hats[index] &^= hatDown
|
||||
case value > 0:
|
||||
g.hats[index] &^= hatUp
|
||||
g.hats[index] |= hatDown
|
||||
default:
|
||||
g.hats[index] &^= hatUp | hatDown
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
info := g.absInfo[code]
|
||||
v := float64(value)
|
||||
if r := float64(info.maximum) - float64(info.minimum); r != 0 {
|
||||
v = (v - float64(info.minimum)) / r
|
||||
v = v*2 - 1
|
||||
}
|
||||
g.axes[index] = v
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) computeStandardLayout(vendor uint16) {
|
||||
g.stdAxisMap = map[gamepaddb.StandardAxis]mappingInput{}
|
||||
g.stdButtonMap = map[gamepaddb.StandardButton]mappingInput{}
|
||||
|
||||
// NOTE: assignments to the same value are in exact reverse order as SDL2,
|
||||
// so we can just overwrite rather than checking.
|
||||
|
||||
// BTN_GAMEPAD implies that the kernel module implements standard mapping.
|
||||
if b := g.keyMap[_BTN_GAMEPAD-_BTN_MISC]; b < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// A and B buttons go by name.
|
||||
if b := g.keyMap[_BTN_A-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonRightBottom] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_B-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonRightRight] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if vendor == 0x054c /* USB_VENDOR_SONY */ {
|
||||
// Sony uses WEST/NORTH buttons.
|
||||
if b := g.keyMap[_BTN_WEST-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonRightLeft] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_NORTH-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonRightTop] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
} else {
|
||||
// Xbox uses X/Y buttons.
|
||||
// Note that this is the opposite assignment following the WEST/NORTH mappings,
|
||||
// and contradicts Linux kernel documentation which states
|
||||
// that buttons are always assigned by physical location.
|
||||
// However, it matches actual Xbox gamepads, and SDL2 has the same logic.
|
||||
if b := g.keyMap[_BTN_X-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonRightLeft] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_Y-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonRightTop] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
}
|
||||
|
||||
// Center and thumb buttons.
|
||||
if b := g.keyMap[_BTN_SELECT-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonCenterLeft] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_START-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonCenterRight] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_THUMBL-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftStick] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_THUMBR-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonRightStick] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_MODE-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonCenterCenter] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
|
||||
// Shoulder buttons can be analog or digital. Prefer digital ones.
|
||||
if h := g.absMap[_ABS_HAT1Y]; h >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontTopLeft] = hatMappingInput{g: g, hat: h, direction: hatDown}
|
||||
}
|
||||
if h := g.absMap[_ABS_HAT1X]; h >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontTopRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
|
||||
}
|
||||
if b := g.keyMap[_BTN_TL-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontTopLeft] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_TR-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontTopRight] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
|
||||
// Triggers can be analog or digital. Prefer analog ones.
|
||||
if b := g.keyMap[_BTN_TL2-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_TR2-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if a := g.absMap[_ABS_Z]; a >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = axisMappingInput{g: g, axis: a}
|
||||
}
|
||||
if a := g.absMap[_ABS_RZ]; a >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = axisMappingInput{g: g, axis: a}
|
||||
}
|
||||
if h := g.absMap[_ABS_HAT2Y]; h >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = hatMappingInput{g: g, hat: h, direction: hatDown}
|
||||
}
|
||||
if h := g.absMap[_ABS_HAT2X]; h >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
|
||||
}
|
||||
|
||||
// D-pad can be analog or digital. Prefer digital one.
|
||||
if h := g.absMap[_ABS_HAT0X]; h >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftLeft] = hatMappingInput{g: g, hat: h, direction: hatLeft}
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
|
||||
}
|
||||
if h := g.absMap[_ABS_HAT0Y]; h >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftTop] = hatMappingInput{g: g, hat: h, direction: hatUp}
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftBottom] = hatMappingInput{g: g, hat: h, direction: hatDown}
|
||||
}
|
||||
if b := g.keyMap[_BTN_DPAD_UP-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftTop] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_DPAD_DOWN-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftBottom] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_DPAD_LEFT-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftLeft] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
if b := g.keyMap[_BTN_DPAD_RIGHT-_BTN_MISC]; b >= 0 {
|
||||
g.stdButtonMap[gamepaddb.StandardButtonLeftRight] = buttonMappingInput{g: g, button: b}
|
||||
}
|
||||
|
||||
// Left stick.
|
||||
if a := g.absMap[_ABS_X]; a >= 0 {
|
||||
g.stdAxisMap[gamepaddb.StandardAxisLeftStickHorizontal] = axisMappingInput{g: g, axis: a}
|
||||
}
|
||||
if a := g.absMap[_ABS_Y]; a >= 0 {
|
||||
g.stdAxisMap[gamepaddb.StandardAxisLeftStickVertical] = axisMappingInput{g: g, axis: a}
|
||||
}
|
||||
|
||||
// Right stick.
|
||||
if a := g.absMap[_ABS_RX]; a >= 0 {
|
||||
g.stdAxisMap[gamepaddb.StandardAxisRightStickHorizontal] = axisMappingInput{g: g, axis: a}
|
||||
}
|
||||
if a := g.absMap[_ABS_RY]; a >= 0 {
|
||||
g.stdAxisMap[gamepaddb.StandardAxisRightStickVertical] = axisMappingInput{g: g, axis: a}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return len(g.stdAxisMap) != 0 || len(g.stdButtonMap) != 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
return g.stdAxisMap[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
return g.stdButtonMap[button]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisCount() int {
|
||||
return g.axisCount_
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonCount() int {
|
||||
return g.buttonCount_
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatCount() int {
|
||||
return g.hatCount_
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||
return axis >= 0 && axis < g.axisCount()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||
if axis < 0 || axis >= g.axisCount_ {
|
||||
return 0
|
||||
}
|
||||
return g.axes[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
|
||||
if button < 0 || button >= g.buttonCount_ {
|
||||
return false
|
||||
}
|
||||
return g.buttons[button]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
if g.isButtonPressed(button) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatState(hat int) int {
|
||||
if hat < 0 || hat >= g.hatCount_ {
|
||||
return hatCentered
|
||||
}
|
||||
return g.hats[hat]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 The Ebitengine 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.
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
// The actual implementation will be provided by -overlay.
|
||||
|
||||
#include "gamepad_nintendosdk.h"
|
||||
|
||||
extern "C" void ebitengine_UpdateGamepads() {}
|
||||
|
||||
extern "C" int ebitengine_GetGamepadCount() { return 0; }
|
||||
|
||||
extern "C" void ebitengine_GetGamepads(struct Gamepad *gamepads) {}
|
||||
|
||||
extern "C" void ebitengine_VibrateGamepad(int id, double durationInSeconds,
|
||||
double strongMagnitude,
|
||||
double weakMagnitude) {}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
// 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.
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
package gamepad
|
||||
|
||||
// #cgo !darwin LDFLAGS: -Wl,-unresolved-symbols=ignore-all
|
||||
// #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup
|
||||
//
|
||||
// #include "gamepad_nintendosdk.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
type nativeGamepadsImpl struct {
|
||||
gamepads []C.struct_Gamepad
|
||||
ids map[int]struct{}
|
||||
}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
C.ebitengine_UpdateGamepads()
|
||||
|
||||
g.gamepads = g.gamepads[:0]
|
||||
if n := int(C.ebitengine_GetGamepadCount()); n > 0 {
|
||||
if cap(g.gamepads) < n {
|
||||
g.gamepads = make([]C.struct_Gamepad, n)
|
||||
} else {
|
||||
g.gamepads = g.gamepads[:n]
|
||||
}
|
||||
C.ebitengine_GetGamepads(&g.gamepads[0])
|
||||
}
|
||||
|
||||
for id := range g.ids {
|
||||
delete(g.ids, id)
|
||||
}
|
||||
|
||||
for _, gp := range g.gamepads {
|
||||
if g.ids == nil {
|
||||
g.ids = map[int]struct{}{}
|
||||
}
|
||||
g.ids[int(gp.id)] = struct{}{}
|
||||
|
||||
gamepad := gamepads.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadImpl).id == int(gp.id)
|
||||
})
|
||||
if gamepad == nil {
|
||||
gamepad = gamepads.add("", "")
|
||||
gamepad.native = &nativeGamepadImpl{
|
||||
id: int(gp.id),
|
||||
standard: bool(gp.standard != 0),
|
||||
axisValues: make([]float64, gp.axis_count),
|
||||
buttonPressed: make([]bool, gp.button_count),
|
||||
buttonValues: make([]float64, gp.button_count),
|
||||
}
|
||||
}
|
||||
|
||||
gamepad.m.Lock()
|
||||
n := gamepad.native.(*nativeGamepadImpl)
|
||||
for i := range n.axisValues {
|
||||
n.axisValues[i] = float64(gp.axis_values[i])
|
||||
}
|
||||
for i := range n.buttonValues {
|
||||
n.buttonValues[i] = float64(gp.button_values[i])
|
||||
}
|
||||
for i := range n.buttonPressed {
|
||||
n.buttonPressed[i] = gp.button_pressed[i] != 0
|
||||
}
|
||||
gamepad.m.Unlock()
|
||||
}
|
||||
|
||||
// Remove an unused gamepads.
|
||||
gamepads.remove(func(gamepad *Gamepad) bool {
|
||||
_, ok := g.ids[gamepad.native.(*nativeGamepadImpl).id]
|
||||
return !ok
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct {
|
||||
id int
|
||||
standard bool
|
||||
|
||||
axisValues []float64
|
||||
buttonPressed []bool
|
||||
buttonValues []float64
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return g.standard
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
// TODO: Implement this on the C side.
|
||||
if axis < 0 || int(axis) >= len(g.axisValues) {
|
||||
return nil
|
||||
}
|
||||
return axisMappingInput{g: g, axis: int(axis)}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
// TODO: Implement this on the C side.
|
||||
if button < 0 || int(button) >= len(g.buttonValues) {
|
||||
return nil
|
||||
}
|
||||
return buttonMappingInput{g: g, button: int(button)}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisCount() int {
|
||||
return len(g.axisValues)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonCount() int {
|
||||
return len(g.buttonValues)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||
return axis >= 0 && axis < g.axisCount()
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||
if axis < 0 || axis >= len(g.axisValues) {
|
||||
return 0
|
||||
}
|
||||
return g.axisValues[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
|
||||
if button < 0 || button >= len(g.buttonPressed) {
|
||||
return false
|
||||
}
|
||||
return g.buttonPressed[button]
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
if button < 0 || button >= len(g.buttonValues) {
|
||||
return 0
|
||||
}
|
||||
return g.buttonValues[button]
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hatState(hat int) int {
|
||||
return hatCentered
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
C.ebitengine_VibrateGamepad(C.int(g.id), C.double(float64(duration)/float64(time.Second)), C.double(strongMagnitude), C.double(weakMagnitude))
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright 2022 The Ebitengine 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.
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
struct Gamepad {
|
||||
int id;
|
||||
char standard;
|
||||
int button_count;
|
||||
int axis_count;
|
||||
char button_pressed[32];
|
||||
float button_values[32];
|
||||
float axis_values[16];
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void ebitengine_UpdateGamepads();
|
||||
int ebitengine_GetGamepadCount();
|
||||
void ebitengine_GetGamepads(struct Gamepad *gamepads);
|
||||
void ebitengine_VibrateGamepad(int id, double durationInSeconds,
|
||||
double strongMagnitude, double weakMagnitude);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// 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.
|
||||
|
||||
//go:build !darwin && !js && !linux && !windows
|
||||
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
type nativeGamepadsImpl struct{}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct{}
|
||||
|
||||
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) axisCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) buttonCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hatCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) axisValue(axis int) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) isButtonPressed(button int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hatState(hat int) int {
|
||||
return hatCentered
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2023 The Ebitengine 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.
|
||||
|
||||
//go:build playstation5
|
||||
|
||||
package gamepad
|
||||
|
||||
type nativeGamepadsImpl struct {
|
||||
}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2022 The Ebitengine 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 (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
||||
)
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return &nativeGamepadsXbox{}
|
||||
}
|
||||
return &nativeGamepadsDesktop{}
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
// Copyright 2022 The Ebitengine 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 (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
)
|
||||
|
||||
func standardButtonToGamepadInputGamepadButton(b gamepaddb.StandardButton) (_GameInputGamepadButtons, bool) {
|
||||
switch b {
|
||||
case gamepaddb.StandardButtonRightBottom:
|
||||
return _GameInputGamepadA, true
|
||||
case gamepaddb.StandardButtonRightRight:
|
||||
return _GameInputGamepadB, true
|
||||
case gamepaddb.StandardButtonRightLeft:
|
||||
return _GameInputGamepadX, true
|
||||
case gamepaddb.StandardButtonRightTop:
|
||||
return _GameInputGamepadY, true
|
||||
case gamepaddb.StandardButtonFrontTopLeft:
|
||||
return _GameInputGamepadLeftShoulder, true
|
||||
case gamepaddb.StandardButtonFrontTopRight:
|
||||
return _GameInputGamepadRightShoulder, true
|
||||
case gamepaddb.StandardButtonFrontBottomLeft:
|
||||
return 0, false // Use leftTrigger instead.
|
||||
case gamepaddb.StandardButtonFrontBottomRight:
|
||||
return 0, false // Use rightTrigger instead.
|
||||
case gamepaddb.StandardButtonCenterLeft:
|
||||
return _GameInputGamepadView, true
|
||||
case gamepaddb.StandardButtonCenterRight:
|
||||
return _GameInputGamepadMenu, true
|
||||
case gamepaddb.StandardButtonLeftStick:
|
||||
return _GameInputGamepadLeftThumbstick, true
|
||||
case gamepaddb.StandardButtonRightStick:
|
||||
return _GameInputGamepadRightThumbstick, true
|
||||
case gamepaddb.StandardButtonLeftTop:
|
||||
return _GameInputGamepadDPadUp, true
|
||||
case gamepaddb.StandardButtonLeftBottom:
|
||||
return _GameInputGamepadDPadDown, true
|
||||
case gamepaddb.StandardButtonLeftLeft:
|
||||
return _GameInputGamepadDPadLeft, true
|
||||
case gamepaddb.StandardButtonLeftRight:
|
||||
return _GameInputGamepadDPadRight, true
|
||||
case gamepaddb.StandardButtonCenterCenter:
|
||||
return 0, false
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type nativeGamepadsXbox struct {
|
||||
gameInput *_IGameInput
|
||||
deviceCallbackPtr uintptr
|
||||
token _GameInputCallbackToken
|
||||
}
|
||||
|
||||
func (n *nativeGamepadsXbox) init(gamepads *gamepads) error {
|
||||
g, err := _GameInputCreate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.gameInput = g
|
||||
n.deviceCallbackPtr = windows.NewCallbackCDecl(n.deviceCallback)
|
||||
|
||||
if err := n.gameInput.RegisterDeviceCallback(
|
||||
nil,
|
||||
_GameInputKindGamepad,
|
||||
_GameInputDeviceConnected,
|
||||
_GameInputBlockingEnumeration,
|
||||
unsafe.Pointer(gamepads),
|
||||
n.deviceCallbackPtr,
|
||||
&n.token,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nativeGamepadsXbox) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nativeGamepadsXbox) deviceCallback(callbackToken _GameInputCallbackToken, context unsafe.Pointer, device *_IGameInputDevice, timestamp uint64, currentStatus _GameInputDeviceStatus, previousStatus _GameInputDeviceStatus) uintptr {
|
||||
gps := (*gamepads)(context)
|
||||
|
||||
// Connected.
|
||||
if currentStatus&_GameInputDeviceConnected != 0 {
|
||||
// TODO: Give a good name and a SDL ID.
|
||||
gp := gps.add("", "00000000000000000000000000000000")
|
||||
gp.native = &nativeGamepadXbox{
|
||||
gameInputDevice: device,
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Disconnected.
|
||||
gps.remove(func(gamepad *Gamepad) bool {
|
||||
return gamepad.native.(*nativeGamepadXbox).gameInputDevice == device
|
||||
})
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
type nativeGamepadXbox struct {
|
||||
gameInputDevice *_IGameInputDevice
|
||||
state _GameInputGamepadState
|
||||
|
||||
vib bool
|
||||
vibEnd time.Time
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) update(gamepads *gamepads) error {
|
||||
gameInput := gamepads.native.(*nativeGamepadsXbox).gameInput
|
||||
r, err := gameInput.GetCurrentReading(_GameInputKindGamepad, n.gameInputDevice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Release()
|
||||
|
||||
state, ok := r.GetGamepadState()
|
||||
if !ok {
|
||||
n.state = _GameInputGamepadState{}
|
||||
return nil
|
||||
}
|
||||
n.state = state
|
||||
|
||||
if n.vib && time.Now().Sub(n.vibEnd) >= 0 {
|
||||
n.gameInputDevice.SetRumbleState(&_GameInputRumbleParams{
|
||||
lowFrequency: 0,
|
||||
highFrequency: 0,
|
||||
}, 0)
|
||||
n.vib = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) hasOwnStandardLayoutMapping() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
|
||||
switch axis {
|
||||
case gamepaddb.StandardAxisLeftStickHorizontal,
|
||||
gamepaddb.StandardAxisLeftStickVertical,
|
||||
gamepaddb.StandardAxisRightStickHorizontal,
|
||||
gamepaddb.StandardAxisRightStickVertical:
|
||||
return axisMappingInput{g: n, axis: int(axis)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
|
||||
switch button {
|
||||
case gamepaddb.StandardButtonFrontBottomLeft,
|
||||
gamepaddb.StandardButtonFrontBottomRight:
|
||||
return buttonMappingInput{g: n, button: int(button)}
|
||||
}
|
||||
if _, ok := standardButtonToGamepadInputGamepadButton(button); !ok {
|
||||
return nil
|
||||
}
|
||||
return buttonMappingInput{g: n, button: int(button)}
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) axisCount() int {
|
||||
return int(gamepaddb.StandardAxisMax) + 1
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) buttonCount() int {
|
||||
return int(gamepaddb.StandardButtonMax) + 1
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) hatCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepadXbox) isAxisReady(axis int) bool {
|
||||
return axis >= 0 && axis < g.axisCount()
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) axisValue(axis int) float64 {
|
||||
switch gamepaddb.StandardAxis(axis) {
|
||||
case gamepaddb.StandardAxisLeftStickHorizontal:
|
||||
return float64(n.state.leftThumbstickX)
|
||||
case gamepaddb.StandardAxisLeftStickVertical:
|
||||
return -float64(n.state.leftThumbstickY)
|
||||
case gamepaddb.StandardAxisRightStickHorizontal:
|
||||
return float64(n.state.rightThumbstickX)
|
||||
case gamepaddb.StandardAxisRightStickVertical:
|
||||
return -float64(n.state.rightThumbstickY)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) buttonValue(button int) float64 {
|
||||
switch gamepaddb.StandardButton(button) {
|
||||
case gamepaddb.StandardButtonFrontBottomLeft:
|
||||
return float64(n.state.leftTrigger)
|
||||
case gamepaddb.StandardButtonFrontBottomRight:
|
||||
return float64(n.state.rightTrigger)
|
||||
}
|
||||
b, ok := standardButtonToGamepadInputGamepadButton(gamepaddb.StandardButton(button))
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
if n.state.buttons&b != 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) isButtonPressed(button int) bool {
|
||||
switch gamepaddb.StandardButton(button) {
|
||||
case gamepaddb.StandardButtonFrontBottomLeft:
|
||||
return n.state.leftTrigger > gamepaddb.ButtonPressedThreshold
|
||||
case gamepaddb.StandardButtonFrontBottomRight:
|
||||
return n.state.rightTrigger > gamepaddb.ButtonPressedThreshold
|
||||
}
|
||||
|
||||
b, ok := standardButtonToGamepadInputGamepadButton(gamepaddb.StandardButton(button))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if n.state.buttons&b != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) hatState(hat int) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (n *nativeGamepadXbox) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
if strongMagnitude <= 0 && weakMagnitude <= 0 {
|
||||
n.vib = false
|
||||
n.gameInputDevice.SetRumbleState(&_GameInputRumbleParams{
|
||||
lowFrequency: 0,
|
||||
highFrequency: 0,
|
||||
}, 0)
|
||||
return
|
||||
}
|
||||
n.vib = true
|
||||
n.vibEnd = time.Now().Add(duration)
|
||||
n.gameInputDevice.SetRumbleState(&_GameInputRumbleParams{
|
||||
lowFrequency: float32(strongMagnitude),
|
||||
highFrequency: float32(weakMagnitude),
|
||||
}, 0)
|
||||
}
|
||||
Reference in New Issue
Block a user