// 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 } 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 } 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 dinputGUID windows.GUID 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 } // Treat invalid combinations as neither being pressed // while preserving what data can be preserved if (v&hatRight) != 0 && (v&hatLeft) != 0 { v &^= hatRight | hatLeft } if (v&hatUp) != 0 && (v&hatDown) != 0 { v &^= hatUp | hatDown } return v } func (g *nativeGamepadDesktop) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) { // TODO: Implement this (#1452) }