mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
parent
847c4f067a
commit
3fb3997160
779
internal/gamepad/gamepad_desktop_windows.go
Normal file
779
internal/gamepad/gamepad_desktop_windows.go
Normal file
@ -0,0 +1,779 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type dinputObjectType int
|
||||
|
||||
const (
|
||||
dinputObjectTypeAxis dinputObjectType = iota
|
||||
dinputObjectTypeSlider
|
||||
dinputObjectTypeButton
|
||||
dinputObjectTypePOV
|
||||
)
|
||||
|
||||
var dinputObjectDataFormats = []_DIOBJECTDATAFORMAT{
|
||||
{&_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 {
|
||||
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", directInputError(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
|
||||
}
|
||||
|
||||
if gamepads.find(func(g *Gamepad) bool {
|
||||
return g.native.(*nativeGamepadDesktop).dinputGUID == lpddi.guidInstance
|
||||
}) != nil {
|
||||
return _DIENUM_CONTINUE
|
||||
}
|
||||
|
||||
s, err := supportsXInput(lpddi.guidProduct)
|
||||
if err != nil {
|
||||
g.err = err
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
if s {
|
||||
return _DIENUM_CONTINUE
|
||||
}
|
||||
|
||||
var device *_IDirectInputDevice8W
|
||||
if err := g.dinput8API.CreateDevice(&lpddi.guidInstance, &device, nil); err != nil {
|
||||
g.err = err
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
dataFormat := _DIDATAFORMAT{
|
||||
dwSize: uint32(unsafe.Sizeof(_DIDATAFORMAT{})),
|
||||
dwObjSize: uint32(unsafe.Sizeof(_DIOBJECTDATAFORMAT{})),
|
||||
dwFlags: _DIDFT_ABSAXIS,
|
||||
dwDataSize: uint32(unsafe.Sizeof(_DIJOYSTATE{})),
|
||||
dwNumObjs: uint32(len(dinputObjectDataFormats)),
|
||||
rgodf: &dinputObjectDataFormats[0],
|
||||
}
|
||||
if err := device.SetDataFormat(&dataFormat); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
dc := _DIDEVCAPS{
|
||||
dwSize: uint32(unsafe.Sizeof(_DIDEVCAPS{})),
|
||||
}
|
||||
if err := device.GetCapabilities(&dc); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
dipd := _DIPROPDWORD{
|
||||
diph: _DIPROPHEADER{
|
||||
dwSize: uint32(unsafe.Sizeof(_DIPROPDWORD{})),
|
||||
dwHeaderSize: uint32(unsafe.Sizeof(_DIPROPHEADER{})),
|
||||
dwHow: _DIPH_DEVICE,
|
||||
},
|
||||
dwData: _DIPROPAXISMODE_ABS,
|
||||
}
|
||||
if err := device.SetProperty(_DIPROP_AXISMODE, &dipd.diph); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
ctx := enumObjectsContext{
|
||||
device: device,
|
||||
}
|
||||
if g.enumObjectsCallback == 0 {
|
||||
g.enumObjectsCallback = windows.NewCallback(g.dinputDevice8EnumObjectsCallback)
|
||||
}
|
||||
if err := device.EnumObjects(g.enumObjectsCallback, unsafe.Pointer(&ctx), _DIDFT_AXIS|_DIDFT_BUTTON|_DIDFT_POV); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
sort.Slice(ctx.objects, func(i, j int) bool {
|
||||
if ctx.objects[i].objectType != ctx.objects[j].objectType {
|
||||
return ctx.objects[i].objectType < ctx.objects[j].objectType
|
||||
}
|
||||
return ctx.objects[i].index < ctx.objects[j].index
|
||||
})
|
||||
|
||||
name := windows.UTF16ToString(lpddi.tszInstanceName[:])
|
||||
var sdlID string
|
||||
if string(lpddi.guidProduct.Data4[2:8]) == "PIDVID" {
|
||||
// This seems different from the current SDL implementation.
|
||||
// Probably guidProduct includes the vendor and the product information, but this works.
|
||||
// From the game controller database, the 'version' part seems always 0.
|
||||
sdlID = fmt.Sprintf("03000000%02x%02x0000%02x%02x000000000000",
|
||||
byte(lpddi.guidProduct.Data1),
|
||||
byte(lpddi.guidProduct.Data1>>8),
|
||||
byte(lpddi.guidProduct.Data1>>16),
|
||||
byte(lpddi.guidProduct.Data1>>24))
|
||||
} else {
|
||||
bs := []byte(name)
|
||||
if len(bs) < 12 {
|
||||
bs = append(bs, make([]byte, 12-len(bs))...)
|
||||
}
|
||||
sdlID = fmt.Sprintf("05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
|
||||
}
|
||||
|
||||
gp := gamepads.add(name, sdlID)
|
||||
gp.native = &nativeGamepadDesktop{
|
||||
dinputDevice: device,
|
||||
dinputObjects: ctx.objects,
|
||||
dinputGUID: lpddi.guidInstance,
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if uint32(rdi.hid.dwVendorId)|(uint32(rdi.hid.dwProductId)<<16) != guid.Data1 {
|
||||
continue
|
||||
}
|
||||
|
||||
var name [256]uint16
|
||||
size = uint32(unsafe.Sizeof(name))
|
||||
if _, err := _GetRawInputDeviceInfoW(ridl[i].hDevice, _RIDI_DEVICENAME, unsafe.Pointer(&name[0]), &size); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if strings.Contains(windows.UTF16ToString(name[:]), "IG_") {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *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
|
||||
dinputGUID windows.GUID
|
||||
dinputAxes []float64
|
||||
dinputButtons []bool
|
||||
dinputHats []int
|
||||
|
||||
xinputIndex int
|
||||
xinputState _XINPUT_STATE
|
||||
}
|
||||
|
||||
func (*nativeGamepadDesktop) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
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.usesDInput() {
|
||||
if err := g.dinputDevice.Poll(); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var state _DIJOYSTATE
|
||||
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
// Acquire can return an error just after a gamepad is disconnected. Ignore the error.
|
||||
g.dinputDevice.Acquire()
|
||||
if err := g.dinputDevice.Poll(); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
disconnected = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var ai, bi, hi int
|
||||
for _, obj := range g.dinputObjects {
|
||||
switch obj.objectType {
|
||||
case dinputObjectTypeAxis:
|
||||
var v int32
|
||||
switch obj.index {
|
||||
case 0:
|
||||
v = state.lX
|
||||
case 1:
|
||||
v = state.lY
|
||||
case 2:
|
||||
v = state.lZ
|
||||
case 3:
|
||||
v = state.lRx
|
||||
case 4:
|
||||
v = state.lRy
|
||||
case 5:
|
||||
v = state.lRz
|
||||
}
|
||||
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
|
||||
ai++
|
||||
case dinputObjectTypeSlider:
|
||||
v := state.rglSlider[obj.index]
|
||||
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
|
||||
ai++
|
||||
case dinputObjectTypeButton:
|
||||
v := (state.rgbButtons[obj.index] & 0x80) != 0
|
||||
g.dinputButtons[bi] = v
|
||||
bi++
|
||||
case dinputObjectTypePOV:
|
||||
stateIndex := state.rgdwPOV[obj.index] / (45 * _DI_DEGREES)
|
||||
v := hatCentered
|
||||
switch stateIndex {
|
||||
case 0:
|
||||
v = hatUp
|
||||
case 1:
|
||||
v = hatRightUp
|
||||
case 2:
|
||||
v = hatRight
|
||||
case 3:
|
||||
v = hatRightDown
|
||||
case 4:
|
||||
v = hatDown
|
||||
case 5:
|
||||
v = hatLeftDown
|
||||
case 6:
|
||||
v = hatLeft
|
||||
case 7:
|
||||
v = hatLeftUp
|
||||
}
|
||||
g.dinputHats[hi] = v
|
||||
hi++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var state _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) 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 {
|
||||
panic("gamepad: buttonValue is not implemented")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The Ebiten Authors
|
||||
// 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.
|
||||
@ -18,766 +18,12 @@
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
||||
)
|
||||
|
||||
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 nativeGamepadsImpl 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
|
||||
}
|
||||
|
||||
func newNativeGamepadsImpl() nativeGamepads {
|
||||
return &nativeGamepadsImpl{}
|
||||
}
|
||||
|
||||
type dinputObject struct {
|
||||
objectType dinputObjectType
|
||||
index int
|
||||
}
|
||||
|
||||
type enumObjectsContext struct {
|
||||
device *_IDirectInputDevice8W
|
||||
objects []dinputObject
|
||||
axisCount int
|
||||
sliderCount int
|
||||
buttonCount int
|
||||
povCount int
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) 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 {
|
||||
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 *nativeGamepadsImpl) 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", directInputError(r))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) 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 *nativeGamepadsImpl) 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 *nativeGamepadsImpl) 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.(*nativeGamepadImpl)
|
||||
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 = &nativeGamepadImpl{
|
||||
xinputIndex: i,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) dinput8EnumDevicesCallback(lpddi *_DIDEVICEINSTANCEW, pvRef unsafe.Pointer) uintptr {
|
||||
gamepads := (*gamepads)(pvRef)
|
||||
|
||||
if g.err != nil {
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
if gamepads.find(func(g *Gamepad) bool {
|
||||
return g.native.(*nativeGamepadImpl).dinputGUID == lpddi.guidInstance
|
||||
}) != nil {
|
||||
return _DIENUM_CONTINUE
|
||||
}
|
||||
|
||||
s, err := supportsXInput(lpddi.guidProduct)
|
||||
if err != nil {
|
||||
g.err = err
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
if s {
|
||||
return _DIENUM_CONTINUE
|
||||
}
|
||||
|
||||
var device *_IDirectInputDevice8W
|
||||
if err := g.dinput8API.CreateDevice(&lpddi.guidInstance, &device, nil); err != nil {
|
||||
g.err = err
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
dataFormat := _DIDATAFORMAT{
|
||||
dwSize: uint32(unsafe.Sizeof(_DIDATAFORMAT{})),
|
||||
dwObjSize: uint32(unsafe.Sizeof(_DIOBJECTDATAFORMAT{})),
|
||||
dwFlags: _DIDFT_ABSAXIS,
|
||||
dwDataSize: uint32(unsafe.Sizeof(_DIJOYSTATE{})),
|
||||
dwNumObjs: uint32(len(dinputObjectDataFormats)),
|
||||
rgodf: &dinputObjectDataFormats[0],
|
||||
}
|
||||
if err := device.SetDataFormat(&dataFormat); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
dc := _DIDEVCAPS{
|
||||
dwSize: uint32(unsafe.Sizeof(_DIDEVCAPS{})),
|
||||
}
|
||||
if err := device.GetCapabilities(&dc); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
dipd := _DIPROPDWORD{
|
||||
diph: _DIPROPHEADER{
|
||||
dwSize: uint32(unsafe.Sizeof(_DIPROPDWORD{})),
|
||||
dwHeaderSize: uint32(unsafe.Sizeof(_DIPROPHEADER{})),
|
||||
dwHow: _DIPH_DEVICE,
|
||||
},
|
||||
dwData: _DIPROPAXISMODE_ABS,
|
||||
}
|
||||
if err := device.SetProperty(_DIPROP_AXISMODE, &dipd.diph); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
ctx := enumObjectsContext{
|
||||
device: device,
|
||||
}
|
||||
if g.enumObjectsCallback == 0 {
|
||||
g.enumObjectsCallback = windows.NewCallback(g.dinputDevice8EnumObjectsCallback)
|
||||
}
|
||||
if err := device.EnumObjects(g.enumObjectsCallback, unsafe.Pointer(&ctx), _DIDFT_AXIS|_DIDFT_BUTTON|_DIDFT_POV); err != nil {
|
||||
g.err = err
|
||||
device.Release()
|
||||
return _DIENUM_STOP
|
||||
}
|
||||
|
||||
sort.Slice(ctx.objects, func(i, j int) bool {
|
||||
if ctx.objects[i].objectType != ctx.objects[j].objectType {
|
||||
return ctx.objects[i].objectType < ctx.objects[j].objectType
|
||||
}
|
||||
return ctx.objects[i].index < ctx.objects[j].index
|
||||
})
|
||||
|
||||
name := windows.UTF16ToString(lpddi.tszInstanceName[:])
|
||||
var sdlID string
|
||||
if string(lpddi.guidProduct.Data4[2:8]) == "PIDVID" {
|
||||
// This seems different from the current SDL implementation.
|
||||
// Probably guidProduct includes the vendor and the product information, but this works.
|
||||
// From the game controller database, the 'version' part seems always 0.
|
||||
sdlID = fmt.Sprintf("03000000%02x%02x0000%02x%02x000000000000",
|
||||
byte(lpddi.guidProduct.Data1),
|
||||
byte(lpddi.guidProduct.Data1>>8),
|
||||
byte(lpddi.guidProduct.Data1>>16),
|
||||
byte(lpddi.guidProduct.Data1>>24))
|
||||
} else {
|
||||
bs := []byte(name)
|
||||
if len(bs) < 12 {
|
||||
bs = append(bs, make([]byte, 12-len(bs))...)
|
||||
}
|
||||
sdlID = fmt.Sprintf("05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
|
||||
}
|
||||
|
||||
gp := gamepads.add(name, sdlID)
|
||||
gp.native = &nativeGamepadImpl{
|
||||
dinputDevice: device,
|
||||
dinputObjects: ctx.objects,
|
||||
dinputGUID: lpddi.guidInstance,
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if uint32(rdi.hid.dwVendorId)|(uint32(rdi.hid.dwProductId)<<16) != guid.Data1 {
|
||||
continue
|
||||
}
|
||||
|
||||
var name [256]uint16
|
||||
size = uint32(unsafe.Sizeof(name))
|
||||
if _, err := _GetRawInputDeviceInfoW(ridl[i].hDevice, _RIDI_DEVICENAME, unsafe.Pointer(&name[0]), &size); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if strings.Contains(windows.UTF16ToString(name[:]), "IG_") {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadsImpl) 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 *nativeGamepadsImpl) 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 *nativeGamepadsImpl) 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 *nativeGamepadsImpl) setNativeWindow(nativeWindow uintptr) {
|
||||
g.nativeWindow = windows.HWND(nativeWindow)
|
||||
}
|
||||
|
||||
type nativeGamepadImpl struct {
|
||||
dinputDevice *_IDirectInputDevice8W
|
||||
dinputObjects []dinputObject
|
||||
dinputGUID windows.GUID
|
||||
dinputAxes []float64
|
||||
dinputButtons []bool
|
||||
dinputHats []int
|
||||
|
||||
xinputIndex int
|
||||
xinputState _XINPUT_STATE
|
||||
}
|
||||
|
||||
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) usesDInput() bool {
|
||||
return g.dinputDevice != nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) 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.usesDInput() {
|
||||
if err := g.dinputDevice.Poll(); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var state _DIJOYSTATE
|
||||
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
// Acquire can return an error just after a gamepad is disconnected. Ignore the error.
|
||||
g.dinputDevice.Acquire()
|
||||
if err := g.dinputDevice.Poll(); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
|
||||
if !errors.Is(err, directInputError(_DIERR_NOTACQUIRED)) && !errors.Is(err, directInputError(_DIERR_INPUTLOST)) {
|
||||
return err
|
||||
}
|
||||
disconnected = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var ai, bi, hi int
|
||||
for _, obj := range g.dinputObjects {
|
||||
switch obj.objectType {
|
||||
case dinputObjectTypeAxis:
|
||||
var v int32
|
||||
switch obj.index {
|
||||
case 0:
|
||||
v = state.lX
|
||||
case 1:
|
||||
v = state.lY
|
||||
case 2:
|
||||
v = state.lZ
|
||||
case 3:
|
||||
v = state.lRx
|
||||
case 4:
|
||||
v = state.lRy
|
||||
case 5:
|
||||
v = state.lRz
|
||||
}
|
||||
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
|
||||
ai++
|
||||
case dinputObjectTypeSlider:
|
||||
v := state.rglSlider[obj.index]
|
||||
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
|
||||
ai++
|
||||
case dinputObjectTypeButton:
|
||||
v := (state.rgbButtons[obj.index] & 0x80) != 0
|
||||
g.dinputButtons[bi] = v
|
||||
bi++
|
||||
case dinputObjectTypePOV:
|
||||
stateIndex := state.rgdwPOV[obj.index] / (45 * _DI_DEGREES)
|
||||
v := hatCentered
|
||||
switch stateIndex {
|
||||
case 0:
|
||||
v = hatUp
|
||||
case 1:
|
||||
v = hatRightUp
|
||||
case 2:
|
||||
v = hatRight
|
||||
case 3:
|
||||
v = hatRightDown
|
||||
case 4:
|
||||
v = hatDown
|
||||
case 5:
|
||||
v = hatLeftDown
|
||||
case 6:
|
||||
v = hatLeft
|
||||
case 7:
|
||||
v = hatLeftUp
|
||||
}
|
||||
g.dinputHats[hi] = v
|
||||
hi++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var state _XINPUT_STATE
|
||||
if err := gamepads.native.(*nativeGamepadsImpl).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 *nativeGamepadImpl) axisCount() int {
|
||||
if g.usesDInput() {
|
||||
return len(g.dinputAxes)
|
||||
}
|
||||
return 6
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) buttonCount() int {
|
||||
if g.usesDInput() {
|
||||
return len(g.dinputButtons)
|
||||
}
|
||||
return len(xinputButtons)
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) hatCount() int {
|
||||
if g.usesDInput() {
|
||||
return len(g.dinputHats)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) 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 *nativeGamepadImpl) 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 *nativeGamepadImpl) buttonValue(button int) float64 {
|
||||
panic("gamepad: buttonValue is not implemented")
|
||||
}
|
||||
|
||||
func (g *nativeGamepadImpl) 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 *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
if microsoftgdk.IsXbox() {
|
||||
return &nativeGamepadsXbox{}
|
||||
}
|
||||
return &nativeGamepadsDesktop{}
|
||||
}
|
||||
|
28
internal/gamepad/gamepad_xbox_windows.go
Normal file
28
internal/gamepad/gamepad_xbox_windows.go
Normal file
@ -0,0 +1,28 @@
|
||||
// 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 !ebitencbackend
|
||||
// +build !ebitencbackend
|
||||
|
||||
package gamepad
|
||||
|
||||
type nativeGamepadsXbox struct{}
|
||||
|
||||
func (n *nativeGamepadsXbox) init(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nativeGamepadsXbox) update(gamepads *gamepads) error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user