// 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 !ebitencbackend // +build !ebitencbackend package gamepad import ( "fmt" "syscall" "unsafe" "golang.org/x/sys/windows" ) const ( _DI_OK = 0 _DI_NOEFFECT = _SI_FALSE _DI_PROPNOEFFECT = _SI_FALSE _DI_DEGREES = 100 _DI8DEVCLASS_GAMECTRL = 4 _DIDFT_ABSAXIS = 0x00000002 _DIDFT_AXIS = 0x00000003 _DIDFT_BUTTON = 0x0000000C _DIDFT_POV = 0x00000010 _DIDFT_OPTIONAL = 0x80000000 _DIDFT_ANYINSTANCE = 0x00FFFF00 _DIDOI_ASPECTPOSITION = 0x00000100 _DIEDFL_ALLDEVICES = 0x00000000 _DIENUM_STOP = 0 _DIENUM_CONTINUE = 1 _DIERR_INPUTLOST = windows.SEVERITY_ERROR<<31 | windows.FACILITY_WIN32<<16 | windows.ERROR_READ_FAULT _DIERR_NOTACQUIRED = windows.SEVERITY_ERROR<<31 | windows.FACILITY_WIN32<<16 | windows.ERROR_INVALID_ACCESS _DIJOFS_X = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lX)) _DIJOFS_Y = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lY)) _DIJOFS_Z = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lZ)) _DIJOFS_RX = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lRx)) _DIJOFS_RY = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lRy)) _DIJOFS_RZ = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lRz)) _DIPH_DEVICE = 0 _DIPH_BYID = 2 _DIPROP_AXISMODE = 2 _DIPROP_RANGE = 4 _DIPROPAXISMODE_ABS = 0 _DIRECTINPUT_VERSION = 0x0800 _GWL_WNDPROC = -4 _MAX_PATH = 260 _RIDI_DEVICEINFO = 0x2000000b _RIDI_DEVICENAME = 0x20000007 _RIM_TYPEHID = 2 _SI_FALSE = 1 _WM_DEVICECHANGE = 0x0219 _XINPUT_CAPS_WIRELESS = 0x0002 _XINPUT_DEVSUBTYPE_GAMEPAD = 0x01 _XINPUT_DEVSUBTYPE_WHEEL = 0x02 _XINPUT_DEVSUBTYPE_ARCADE_STICK = 0x03 _XINPUT_DEVSUBTYPE_FLIGHT_STICK = 0x04 _XINPUT_DEVSUBTYPE_DANCE_PAD = 0x05 _XINPUT_DEVSUBTYPE_GUITAR = 0x06 _XINPUT_DEVSUBTYPE_DRUM_KIT = 0x08 _XINPUT_GAMEPAD_DPAD_UP = 0x0001 _XINPUT_GAMEPAD_DPAD_DOWN = 0x0002 _XINPUT_GAMEPAD_DPAD_LEFT = 0x0004 _XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008 _XINPUT_GAMEPAD_START = 0x0010 _XINPUT_GAMEPAD_BACK = 0x0020 _XINPUT_GAMEPAD_LEFT_THUMB = 0x0040 _XINPUT_GAMEPAD_RIGHT_THUMB = 0x0080 _XINPUT_GAMEPAD_LEFT_SHOULDER = 0x0100 _XINPUT_GAMEPAD_RIGHT_SHOULDER = 0x0200 _XINPUT_GAMEPAD_A = 0x1000 _XINPUT_GAMEPAD_B = 0x2000 _XINPUT_GAMEPAD_X = 0x4000 _XINPUT_GAMEPAD_Y = 0x8000 ) func _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") procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW") procCallWindowProcW = user32.NewProc("CallWindowProcW") procGetActiveWindow = user32.NewProc("GetActiveWindow") 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 getCurrentThreadId() uint32 { t, _, _ := procGetCurrentThreadId.Call() return uint32(t) } func getModuleHandleW() (uintptr, error) { m, _, e := procGetModuleHandleW.Call(0) if m == 0 { if e != nil && e != windows.ERROR_SUCCESS { return 0, fmt.Errorf("gamepad: GetModuleHandleW failed: %w", e) } return 0, fmt.Errorf("gamepad: GetModuleHandleW returned 0") } return m, nil } func callWindowProcW(lpPrevWndFunc uintptr, hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr { r, _, _ := procCallWindowProcW.Call(lpPrevWndFunc, hWnd, uintptr(msg), wParam, lParam) return r } func getActiveWindow() uintptr { h, _, _ := procGetActiveWindow.Call() return h } func getRawInputDeviceInfoW(hDevice windows.Handle, uiCommand uint32, pData unsafe.Pointer, pcb *uint32) (uint32, error) { r, _, e := procGetRawInputDeviceInfoW.Call(uintptr(hDevice), uintptr(uiCommand), uintptr(pData), uintptr(unsafe.Pointer(pcb))) if uint32(r) == ^uint32(0) { if e != nil && e != windows.ERROR_SUCCESS { return 0, fmt.Errorf("gamepad: GetRawInputDeviceInfoW failed: %w", e) } return 0, fmt.Errorf("gamepad: GetRawInputDeviceInfoW returned -1") } return uint32(r), nil } func getRawInputDeviceList(pRawInputDeviceList *_RAWINPUTDEVICELIST, puiNumDevices *uint32) (uint32, error) { r, _, e := procGetRawInputDeviceList.Call(uintptr(unsafe.Pointer(pRawInputDeviceList)), uintptr(unsafe.Pointer(puiNumDevices)), unsafe.Sizeof(_RAWINPUTDEVICELIST{})) if uint32(r) == ^uint32(0) { if e != nil && e != windows.ERROR_SUCCESS { return 0, fmt.Errorf("gamepad: GetRawInputDeviceList failed: %w", e) } return 0, fmt.Errorf("gamepad: GetRawInputDeviceList returned -1") } return uint32(r), nil } func setWindowLongPtrW(hWnd uintptr, nIndex int32, dwNewLong uintptr) (uintptr, error) { var p *windows.LazyProc if procSetWindowLongPtrW.Find() == nil { // 64-Bit Windows. p = procSetWindowLongPtrW } else { // 32-Bit Windows. p = procSetWindowLongW } h, _, e := p.Call(hWnd, uintptr(nIndex), dwNewLong) if h == 0 { if e != nil && e != windows.ERROR_SUCCESS { return 0, fmt.Errorf("gamepad: SetWindowLongPtrW failed: %w", e) } return 0, fmt.Errorf("gamepad: SetWindowLongPtrW returned 0") } return h, nil } type directInputError uint32 func (d directInputError) Error() string { return fmt.Sprintf("DirectInput error: %d", d) } type _DIDATAFORMAT struct { dwSize uint32 dwObjSize uint32 dwFlags uint32 dwDataSize uint32 dwNumObjs uint32 rgodf *_DIOBJECTDATAFORMAT } type _DIDEVCAPS struct { dwSize uint32 dwFlags uint32 dwDevType uint32 dwAxes uint32 dwButtons uint32 dwPOVs uint32 dwFFSamplePeriod uint32 dwFFMinTimeResolution uint32 dwFirmwareRevision uint32 dwHardwareRevision uint32 dwFFDriverVersion uint32 } type _DIDEVICEINSTANCEW struct { dwSize uint32 guidInstance windows.GUID guidProduct windows.GUID dwDevType uint32 tszInstanceName [_MAX_PATH]uint16 tszProductName [_MAX_PATH]uint16 guidFFDriver windows.GUID wUsagePage uint16 wUsage uint16 } type _DIDEVICEOBJECTINSTANCEW struct { dwSize uint32 guidType windows.GUID dwOfs uint32 dwType uint32 dwFlags uint32 tszName [_MAX_PATH]uint16 dwFFMaxForce uint32 dwFFForceResolution uint32 wCollectionNumber uint16 wDesignatorIndex uint16 wUsagePage uint16 wUsage uint16 dwDimension uint32 wExponent uint16 wReserved uint16 } type _DIJOYSTATE struct { lX int32 lY int32 lZ int32 lRx int32 lRy int32 lRz int32 rglSlider [2]int32 rgdwPOV [4]uint32 rgbButtons [32]byte } type _DIOBJECTDATAFORMAT struct { pguid *windows.GUID dwOfs uint32 dwType uint32 dwFlags uint32 } type _DIPROPDWORD struct { diph _DIPROPHEADER dwData uint32 } type _DIPROPHEADER struct { dwSize uint32 dwHeaderSize uint32 dwObj uint32 dwHow uint32 } type _DIPROPRANGE struct { diph _DIPROPHEADER lMin int32 lMax int32 } type iDirectInput8W struct { vtbl *iDirectInput8W_Vtbl } type iDirectInput8W_Vtbl struct { QueryInterface uintptr AddRef uintptr Release uintptr CreateDevice uintptr EnumDevices uintptr GetDeviceStatus uintptr RunControlPanel uintptr Initialize uintptr FindDevice uintptr EnumDevicesBySemantics uintptr ConfigureDevices uintptr } func (d *iDirectInput8W) CreateDevice(rguid *windows.GUID, lplpDirectInputDevice **iDirectInputDevice8W, pUnkOuter unsafe.Pointer) error { r, _, _ := syscall.Syscall6(d.vtbl.CreateDevice, 4, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(rguid)), uintptr(unsafe.Pointer(lplpDirectInputDevice)), uintptr(pUnkOuter), 0, 0) if windows.Handle(r) != _DI_OK { return fmt.Errorf("gamepad: IDirectInput8::CreateDevice failed: %w", directInputError(syscall.Errno(r))) } return nil } func (d *iDirectInput8W) EnumDevices(dwDevType uint32, lpCallback uintptr, pvRef unsafe.Pointer, dwFlags uint32) error { r, _, _ := syscall.Syscall6(d.vtbl.EnumDevices, 5, uintptr(unsafe.Pointer(d)), uintptr(dwDevType), lpCallback, uintptr(pvRef), uintptr(dwFlags), 0) if windows.Handle(r) != _DI_OK { return fmt.Errorf("gamepad: IDirectInput8::EnumDevices failed: %w", directInputError(syscall.Errno(r))) } return nil } type iDirectInputDevice8W struct { vtbl *iDirectInputDevice8W_Vtbl } type iDirectInputDevice8W_Vtbl struct { QueryInterface uintptr AddRef uintptr Release uintptr GetCapabilities uintptr EnumObjects uintptr GetProperty uintptr SetProperty uintptr Acquire uintptr Unacquire uintptr GetDeviceState uintptr GetDeviceData uintptr SetDataFormat uintptr SetEventNotification uintptr SetCooperativeLevel uintptr GetObjectInfo uintptr GetDeviceInfo uintptr RunControlPanel uintptr Initialize uintptr CreateEffect uintptr EnumEffects uintptr GetEffectInfo uintptr GetForceFeedbackState uintptr SendForceFeedbackCommand uintptr EnumCreatedEffectObjects uintptr Escape uintptr Poll uintptr SendDeviceData uintptr EnumEffectsInFile uintptr WriteEffectToFile uintptr BuildActionMap uintptr SetActionMap uintptr GetImageInfo uintptr } func (d *iDirectInputDevice8W) Acquire() error { r, _, _ := syscall.Syscall(d.vtbl.Acquire, 1, uintptr(unsafe.Pointer(d)), 0, 0) if windows.Handle(r) != _DI_OK && windows.Handle(r) != _SI_FALSE { return fmt.Errorf("gamepad: IDirectInputDevice8::Acquire failed: %w", directInputError(syscall.Errno(r))) } return nil } func (d *iDirectInputDevice8W) EnumObjects(lpCallback uintptr, pvRef unsafe.Pointer, dwFlags uint32) error { r, _, _ := syscall.Syscall6(d.vtbl.EnumObjects, 4, uintptr(unsafe.Pointer(d)), lpCallback, uintptr(pvRef), uintptr(dwFlags), 0, 0) if windows.Handle(r) != _DI_OK { return fmt.Errorf("gamepad: IDirectInputDevice8::EnumObjects failed: %w", directInputError(syscall.Errno(r))) } return nil } func (d *iDirectInputDevice8W) GetCapabilities(lpDIDevCaps *_DIDEVCAPS) error { r, _, _ := syscall.Syscall(d.vtbl.GetCapabilities, 2, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(lpDIDevCaps)), 0) if windows.Handle(r) != _DI_OK { return fmt.Errorf("gamepad: IDirectInputDevice8::GetCapabilities failed: %w", directInputError(syscall.Errno(r))) } return nil } func (d *iDirectInputDevice8W) GetDeviceState(cbData uint32, lpvData unsafe.Pointer) error { r, _, _ := syscall.Syscall(d.vtbl.GetDeviceState, 3, uintptr(unsafe.Pointer(d)), uintptr(cbData), uintptr(lpvData)) if windows.Handle(r) != _DI_OK { return fmt.Errorf("gamepad: IDirectInputDevice8::GetDeviceState failed: %w", directInputError(syscall.Errno(r))) } return nil } func (d *iDirectInputDevice8W) Poll() error { r, _, _ := syscall.Syscall(d.vtbl.Poll, 1, uintptr(unsafe.Pointer(d)), 0, 0) if windows.Handle(r) != _DI_OK && windows.Handle(r) != _DI_NOEFFECT { return fmt.Errorf("gamepad: IDirectInputDevice8::Poll failed: %w", directInputError(syscall.Errno(r))) } return nil } func (d *iDirectInputDevice8W) Release() uint32 { r, _, _ := syscall.Syscall(d.vtbl.Release, 1, uintptr(unsafe.Pointer(d)), 0, 0) return uint32(r) } func (d *iDirectInputDevice8W) SetDataFormat(lpdf *_DIDATAFORMAT) error { r, _, _ := syscall.Syscall(d.vtbl.SetDataFormat, 2, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(lpdf)), 0) if windows.Handle(r) != _DI_OK { return fmt.Errorf("gamepad: IDirectInputDevice8::SetDataFormat failed: %w", directInputError(syscall.Errno(r))) } return nil } func (d *iDirectInputDevice8W) SetProperty(rguidProp uintptr, pdiph *_DIPROPHEADER) error { r, _, _ := syscall.Syscall(d.vtbl.SetProperty, 3, uintptr(unsafe.Pointer(d)), rguidProp, uintptr(unsafe.Pointer(pdiph))) if windows.Handle(r) != _DI_OK && windows.Handle(r) != _DI_PROPNOEFFECT { return fmt.Errorf("gamepad: IDirectInputDevice8::SetProperty failed: %w", directInputError(syscall.Errno(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 xinputCapabilities struct { typ byte subType byte flags uint16 gamepad xinputGamepad vibration xinputVibration } type xinputGamepad struct { wButtons uint16 bLeftTrigger byte bRightTrigger byte sThumbLX int16 sThumbLY int16 sThumbRX int16 sThumbRY int16 } type xinputState struct { dwPacketNumber uint32 Gamepad xinputGamepad } type xinputVibration struct { wLeftMotorSpeed uint16 wRightMotorSpeed uint16 }