// 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 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) isStandardAxisAvailableInOwnMapping(axis gamepaddb.StandardAxis) bool { switch axis { case gamepaddb.StandardAxisLeftStickHorizontal, gamepaddb.StandardAxisLeftStickVertical, gamepaddb.StandardAxisRightStickHorizontal, gamepaddb.StandardAxisRightStickVertical: return true } return false } func (n *nativeGamepadXbox) isStandardButtonAvailableInOwnMapping(button gamepaddb.StandardButton) bool { switch button { case gamepaddb.StandardButtonFrontBottomLeft, gamepaddb.StandardButtonFrontBottomRight: return true } _, ok := standardButtonToGamepadInputGamepadButton(button) return ok } 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 (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 { // Use XInput's trigger dead zone. // See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/public/cpp/gamepad.h;l=22-23;drc=6997f8a177359bb99598988ed5e900841984d242 // TODO: Integrate this value with the same one in the package gamepaddb. const threshold = 30.0 / 255.0 switch gamepaddb.StandardButton(button) { case gamepaddb.StandardButtonFrontBottomLeft: return n.state.leftTrigger >= threshold case gamepaddb.StandardButtonFrontBottomRight: return n.state.rightTrigger >= threshold } 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) }