vendor dependencies, make some changes to how input is done

This commit is contained in:
2026-06-15 18:54:00 +02:00
parent 535130933c
commit 4800eb28d9
759 changed files with 360941 additions and 30 deletions
@@ -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
)
@@ -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
}
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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
View File
@@ -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
@@ -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
View File
@@ -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)
}
@@ -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)
}
@@ -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)
}
@@ -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
View File
@@ -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
View File
@@ -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
}
}
@@ -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)
}
@@ -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) {}
@@ -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))
}
@@ -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
@@ -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) {
}
@@ -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
}
@@ -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{}
}
@@ -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)
}