internal/uidriver/glfw: use an original implementation for macOS

Updates #1452
This commit is contained in:
Hajime Hoshi 2022-01-10 22:10:29 +09:00
parent 210036a5a5
commit ff24f7718c
5 changed files with 1017 additions and 225 deletions

230
internal/gamepad/gamepad.go Normal file
View File

@ -0,0 +1,230 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gamepad
import (
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
const (
hatCentered = 0
hatUp = 1
hatRight = 2
hatDown = 4
hatLeft = 8
hatRightUp = hatRight | hatUp
hatRightDown = hatRight | hatDown
hatLeftUp = hatLeft | hatUp
hatLeftDown = hatLeft | hatDown
)
type gamepads struct {
gamepads []*Gamepad
m sync.Mutex
nativeGamepads
}
var theGamepads gamepads
func init() {
theGamepads.nativeGamepads.init()
}
func AppendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
return theGamepads.appendGamepadIDs(ids)
}
func Update() {
theGamepads.update()
}
func Get(id driver.GamepadID) *Gamepad {
return theGamepads.get(id)
}
func (g *gamepads) appendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
g.m.Lock()
defer g.m.Unlock()
for i, gp := range g.gamepads {
if gp != nil && gp.present() {
ids = append(ids, driver.GamepadID(i))
}
}
return ids
}
func (g *gamepads) update() {
g.m.Lock()
defer g.m.Unlock()
for _, gp := range g.gamepads {
if gp != nil {
gp.update()
}
}
}
func (g *gamepads) get(id driver.GamepadID) *Gamepad {
g.m.Lock()
defer g.m.Unlock()
if id < 0 || int(id) >= len(g.gamepads) {
return nil
}
return g.gamepads[id]
}
func (g *gamepads) find(cond func(*Gamepad) bool) *Gamepad {
g.m.Lock()
defer g.m.Unlock()
for _, gp := range g.gamepads {
if gp == nil {
continue
}
if cond(gp) {
return gp
}
}
return nil
}
func (g *gamepads) add(name, sdlID string) *Gamepad {
g.m.Lock()
defer g.m.Unlock()
for i, gp := range g.gamepads {
if gp == nil {
gp := &Gamepad{
name: name,
sdlID: sdlID,
}
g.gamepads[i] = gp
return gp
}
}
gp := &Gamepad{
name: name,
sdlID: sdlID,
}
g.gamepads = append(g.gamepads, gp)
return gp
}
func (g *gamepads) remove(cond func(*Gamepad) bool) {
g.m.Lock()
defer g.m.Unlock()
for i, gp := range g.gamepads {
if gp == nil {
continue
}
if cond(gp) {
g.gamepads[i] = nil
}
}
}
type Gamepad struct {
name string
sdlID string
m sync.Mutex
nativeGamepad
}
func (g *Gamepad) update() {
g.m.Lock()
defer g.m.Unlock()
g.nativeGamepad.update()
}
func (g *Gamepad) Name() string {
// This is immutable and doesn't have to be protected by a mutex.
return g.name
}
func (g *Gamepad) SDLID() string {
// This is immutable and doesn't have to be protected by a mutex.
return g.sdlID
}
func (g *Gamepad) AxisNum() int {
g.m.Lock()
defer g.m.Unlock()
return g.nativeGamepad.axisNum()
}
func (g *Gamepad) ButtonNum() int {
g.m.Lock()
defer g.m.Unlock()
return g.nativeGamepad.buttonNum()
}
func (g *Gamepad) HatNum() int {
g.m.Lock()
defer g.m.Unlock()
return g.nativeGamepad.hatNum()
}
func (g *Gamepad) Axis(axis int) float64 {
g.m.Lock()
defer g.m.Unlock()
return g.nativeGamepad.axisValue(axis)
}
func (g *Gamepad) Button(button int) bool {
g.m.Lock()
defer g.m.Unlock()
return g.nativeGamepad.isButtonPressed(button)
}
func (g *Gamepad) Hat(hat int) int {
g.m.Lock()
defer g.m.Unlock()
return g.nativeGamepad.hatState(hat)
}
func (g *Gamepad) IsStandardLayoutAvailable() bool {
g.m.Lock()
defer g.m.Unlock()
return gamepaddb.HasStandardLayoutMapping(g.sdlID)
}
func (g *Gamepad) StandardAxisValue(axis driver.StandardGamepadAxis) float64 {
return gamepaddb.AxisValue(g.sdlID, axis, g)
}
func (g *Gamepad) StandardButtonValue(button driver.StandardGamepadButton) float64 {
return gamepaddb.ButtonValue(g.sdlID, button, g)
}
func (g *Gamepad) IsStandardButtonPressed(button driver.StandardGamepadButton) bool {
return gamepaddb.IsButtonPressed(g.sdlID, button, g)
}

View File

@ -0,0 +1,396 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !ios
// +build !ios
package gamepad
import (
"fmt"
"sort"
"unsafe"
)
// #cgo LDFLAGS: -framework CoreFoundation -framework IOKit
//
// #include <ForceFeedback/ForceFeedback.h>
// #include <IOKit/hid/IOHIDLib.h>
//
// static CFStringRef cfStringRefIOHIDVendorIDKey() {
// return CFSTR(kIOHIDVendorIDKey);
// }
//
// static CFStringRef cfStringRefIOHIDProductIDKey() {
// return CFSTR(kIOHIDProductIDKey);
// }
//
// static CFStringRef cfStringRefIOHIDVersionNumberKey() {
// return CFSTR(kIOHIDVersionNumberKey);
// }
//
// static CFStringRef cfStringRefIOHIDProductKey() {
// return CFSTR(kIOHIDProductKey);
// }
//
// static CFStringRef cfStringRefIOHIDDeviceUsagePageKey() {
// return CFSTR(kIOHIDDeviceUsagePageKey);
// }
//
// static CFStringRef cfStringRefIOHIDDeviceUsageKey() {
// return CFSTR(kIOHIDDeviceUsageKey);
// }
//
// void ebitenGamepadMatchingCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef device);
// void ebitenGamepadRemovalCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef device);
import "C"
type nativeGamepads struct {
hidManager C.IOHIDManagerRef
}
type nativeGamepad struct {
device C.IOHIDDeviceRef
axes elements
buttons elements
hats elements
axisValues []float64
buttonValues []bool
hatValues []int
}
type element struct {
native C.IOHIDElementRef
usage int
index int
minimum int
maximum int
}
type elements []element
func (e elements) Len() int {
return len(e)
}
func (e elements) Less(i, j int) bool {
if e[i].usage != e[j].usage {
return e[i].usage < e[j].usage
}
if e[i].index != e[j].index {
return e[i].index < e[j].index
}
return false
}
func (e elements) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func (g *nativeGamepad) present() bool {
return g.device != 0
}
func (g *nativeGamepad) elementValue(e *element) int {
if g.device == 0 {
return 0
}
var valueRef C.IOHIDValueRef
if C.IOHIDDeviceGetValue(g.device, e.native, &valueRef) == C.kIOReturnSuccess {
return int(C.IOHIDValueGetIntegerValue(valueRef))
}
return 0
}
func (g *nativeGamepad) update() {
if cap(g.axisValues) < len(g.axes) {
g.axisValues = make([]float64, len(g.axes))
}
g.axisValues = g.axisValues[:len(g.axes)]
if cap(g.buttonValues) < len(g.buttons) {
g.buttonValues = make([]bool, len(g.buttons))
}
g.buttonValues = g.buttonValues[:len(g.buttons)]
if cap(g.hatValues) < len(g.hats) {
g.hatValues = make([]int, len(g.hats))
}
g.hatValues = g.hatValues[:len(g.hats)]
for i, a := range g.axes {
raw := g.elementValue(&a)
if raw < a.minimum {
a.minimum = raw
}
if raw > a.maximum {
a.maximum = raw
}
var value float64
if size := a.maximum - a.minimum; size != 0 {
value = 2*float64(raw-a.minimum)/float64(size) - 1
}
g.axisValues[i] = value
}
for i, b := range g.buttons {
g.buttonValues[i] = g.elementValue(&b) > 0
}
hatStates := []int{
hatUp,
hatRightUp,
hatRight,
hatRightDown,
hatDown,
hatLeftDown,
hatLeft,
hatLeftUp,
}
for i, h := range g.hats {
if state := g.elementValue(&h); state < 0 || state >= len(hatStates) {
g.hatValues[i] = hatCentered
} else {
g.hatValues[i] = hatStates[state]
}
}
}
func (g *nativeGamepad) axisNum() int {
return len(g.axisValues)
}
func (g *nativeGamepad) buttonNum() int {
return len(g.buttonValues)
}
func (g *nativeGamepad) hatNum() int {
return len(g.hatValues)
}
func (g *nativeGamepad) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axisValues) {
return 0
}
return g.axisValues[axis]
}
func (g *nativeGamepad) isButtonPressed(button int) bool {
if button < 0 || button >= len(g.buttonValues) {
return false
}
return g.buttonValues[button]
}
func (g *nativeGamepad) hatState(hat int) int {
if hat < 0 || hat >= len(g.hatValues) {
return hatCentered
}
return g.hatValues[hat]
}
func (g *nativeGamepads) init() {
var dicts []unsafe.Pointer
page := C.kHIDPage_GenericDesktop
for _, usage := range []uint{
C.kHIDUsage_GD_Joystick,
C.kHIDUsage_GD_GamePad,
C.kHIDUsage_GD_MultiAxisController,
} {
pageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&page))
if pageRef == 0 {
panic("gamepad: CFNumberCreate returned nil")
}
defer C.CFRelease(C.CFTypeRef(pageRef))
usageRef := C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberIntType, unsafe.Pointer(&usage))
if usageRef == 0 {
panic("gamepad: CFNumberCreate returned nil")
}
defer C.CFRelease(C.CFTypeRef(usageRef))
keys := []unsafe.Pointer{
unsafe.Pointer(C.cfStringRefIOHIDDeviceUsagePageKey()),
unsafe.Pointer(C.cfStringRefIOHIDDeviceUsageKey()),
}
values := []unsafe.Pointer{
unsafe.Pointer(pageRef),
unsafe.Pointer(usageRef),
}
dict := C.CFDictionaryCreate(C.kCFAllocatorDefault, &keys[0], &values[0], C.CFIndex(len(keys)), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks)
if dict == 0 {
panic("gamepad: CFDictionaryCreate returned nil")
}
defer C.CFRelease(C.CFTypeRef(unsafe.Pointer(dict)))
dicts = append(dicts, unsafe.Pointer(dict))
}
matching := C.CFArrayCreate(C.kCFAllocatorDefault, &dicts[0], C.CFIndex(len(dicts)), &C.kCFTypeArrayCallBacks)
if matching == 0 {
panic("gamepad: CFArrayCreateMutable returned nil")
}
defer C.CFRelease(C.CFTypeRef(matching))
g.hidManager = C.IOHIDManagerCreate(C.kCFAllocatorDefault, C.kIOHIDOptionsTypeNone)
if C.IOHIDManagerOpen(g.hidManager, C.kIOHIDOptionsTypeNone) != C.kIOReturnSuccess {
panic("gamepad: IOHIDManagerOpen failed")
}
C.IOHIDManagerSetDeviceMatchingMultiple(g.hidManager, matching)
C.IOHIDManagerRegisterDeviceMatchingCallback(g.hidManager, C.IOHIDDeviceCallback(C.ebitenGamepadMatchingCallback), nil)
C.IOHIDManagerRegisterDeviceRemovalCallback(g.hidManager, C.IOHIDDeviceCallback(C.ebitenGamepadRemovalCallback), nil)
C.IOHIDManagerScheduleWithRunLoop(g.hidManager, C.CFRunLoopGetMain(), C.kCFRunLoopDefaultMode)
// Execute the run loop once in order to register any initially-attached gamepads.
C.CFRunLoopRunInMode(C.kCFRunLoopDefaultMode, 0, 0 /* false */)
}
//export ebitenGamepadMatchingCallback
func ebitenGamepadMatchingCallback(ctx unsafe.Pointer, res C.IOReturn, sender unsafe.Pointer, device C.IOHIDDeviceRef) {
if theGamepads.find(func(g *Gamepad) bool {
return g.device == device
}) != nil {
return
}
name := "Unknown"
if prop := C.IOHIDDeviceGetProperty(device, C.cfStringRefIOHIDProductKey()); prop != 0 {
var cstr [256]C.char
C.CFStringGetCString(C.CFStringRef(prop), &cstr[0], C.CFIndex(len(cstr)), C.kCFStringEncodingUTF8)
name = C.GoString(&cstr[0])
}
var vendor uint32
if prop := C.IOHIDDeviceGetProperty(device, C.cfStringRefIOHIDVendorIDKey()); prop != 0 {
C.CFNumberGetValue(C.CFNumberRef(prop), C.kCFNumberSInt32Type, unsafe.Pointer(&vendor))
}
var product uint32
if prop := C.IOHIDDeviceGetProperty(device, C.cfStringRefIOHIDProductIDKey()); prop != 0 {
C.CFNumberGetValue(C.CFNumberRef(prop), C.kCFNumberSInt32Type, unsafe.Pointer(&product))
}
var version uint32
if prop := C.IOHIDDeviceGetProperty(device, C.cfStringRefIOHIDVersionNumberKey()); prop != 0 {
C.CFNumberGetValue(C.CFNumberRef(prop), C.kCFNumberSInt32Type, unsafe.Pointer(&version))
}
var sdlID string
if vendor != 0 && product != 0 {
sdlID = fmt.Sprintf("03000000%02x%02x0000%02x%02x0000%02x%02x0000",
byte(vendor), byte(vendor>>8),
byte(product), byte(product>>8),
byte(version), byte(version>>8))
} else {
bs := []byte(name)
if len(bs) < 12 {
bs = append(bs, make([]byte, 12-len(bs))...)
}
sdlID = fmt.Sprintf("05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
}
elements := C.IOHIDDeviceCopyMatchingElements(device, 0, C.kIOHIDOptionsTypeNone)
defer C.CFRelease(C.CFTypeRef(elements))
g := theGamepads.add(name, sdlID)
g.device = device
for i := C.CFIndex(0); i < C.CFArrayGetCount(elements); i++ {
native := (C.IOHIDElementRef)(C.CFArrayGetValueAtIndex(elements, i))
if C.CFGetTypeID(C.CFTypeRef(native)) != C.IOHIDElementGetTypeID() {
continue
}
typ := C.IOHIDElementGetType(native)
if typ != C.kIOHIDElementTypeInput_Axis &&
typ != C.kIOHIDElementTypeInput_Button &&
typ != C.kIOHIDElementTypeInput_Misc {
continue
}
usage := C.IOHIDElementGetUsage(native)
page := C.IOHIDElementGetUsagePage(native)
switch page {
case C.kHIDPage_GenericDesktop:
switch usage {
case C.kHIDUsage_GD_X, C.kHIDUsage_GD_Y, C.kHIDUsage_GD_Z,
C.kHIDUsage_GD_Rx, C.kHIDUsage_GD_Ry, C.kHIDUsage_GD_Rz,
C.kHIDUsage_GD_Slider, C.kHIDUsage_GD_Dial, C.kHIDUsage_GD_Wheel:
g.axes = append(g.axes, element{
native: native,
usage: int(usage),
index: len(g.axes),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
case C.kHIDUsage_GD_Hatswitch:
g.hats = append(g.hats, element{
native: native,
usage: int(usage),
index: len(g.hats),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
case C.kHIDUsage_GD_DPadUp, C.kHIDUsage_GD_DPadRight, C.kHIDUsage_GD_DPadDown, C.kHIDUsage_GD_DPadLeft,
C.kHIDUsage_GD_SystemMainMenu, C.kHIDUsage_GD_Select, C.kHIDUsage_GD_Start:
g.buttons = append(g.buttons, element{
native: native,
usage: int(usage),
index: len(g.buttons),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
}
case C.kHIDPage_Simulation:
switch usage {
case C.kHIDUsage_Sim_Accelerator, C.kHIDUsage_Sim_Brake, C.kHIDUsage_Sim_Throttle, C.kHIDUsage_Sim_Rudder, C.kHIDUsage_Sim_Steering:
g.axes = append(g.axes, element{
native: native,
usage: int(usage),
index: len(g.axes),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
}
case C.kHIDPage_Button, C.kHIDPage_Consumer:
g.buttons = append(g.buttons, element{
native: native,
usage: int(usage),
index: len(g.buttons),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
}
}
sort.Stable(g.axes)
sort.Stable(g.buttons)
sort.Stable(g.hats)
}
//export ebitenGamepadRemovalCallback
func ebitenGamepadRemovalCallback(ctx unsafe.Pointer, res C.IOReturn, sender unsafe.Pointer, device C.IOHIDDeviceRef) {
theGamepads.remove(func(g *Gamepad) bool {
return g.device == device
})
}

View File

@ -0,0 +1,255 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !android && !js && !darwin
// +build !android,!js,!darwin
package glfw
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
)
type nativeGamepads struct {
}
func (i *Input) updateGamepads() {
for id := glfw.Joystick(0); id < glfw.Joystick(len(i.gamepads)); id++ {
i.gamepads[id].valid = false
if !id.Present() {
continue
}
buttons := id.GetButtons()
// A gamepad can be detected even though there are not. Apparently, some special devices are
// recognized as gamepads by GLFW. In this case, the number of the 'buttons' can exceeds the
// maximum. Skip such devices as a tentative solution (#1173).
if len(buttons) > driver.GamepadButtonNum {
continue
}
i.gamepads[id].valid = true
i.gamepads[id].buttonNum = len(buttons)
for b := 0; b < len(i.gamepads[id].buttonPressed); b++ {
if len(buttons) <= b {
i.gamepads[id].buttonPressed[b] = false
continue
}
i.gamepads[id].buttonPressed[b] = glfw.Action(buttons[b]) == glfw.Press
}
axes32 := id.GetAxes()
i.gamepads[id].axisNum = len(axes32)
for a := 0; a < len(i.gamepads[id].axes); a++ {
if len(axes32) <= a {
i.gamepads[id].axes[a] = 0
continue
}
i.gamepads[id].axes[a] = float64(axes32[a])
}
hats := id.GetHats()
i.gamepads[id].hatsNum = len(hats)
for h := 0; h < len(i.gamepads[id].hats); h++ {
if len(hats) <= h {
i.gamepads[id].hats[h] = 0
continue
}
i.gamepads[id].hats[h] = int(hats[h])
}
// Note that GLFW's gamepad GUID follows SDL's GUID.
i.gamepads[id].guid = id.GetGUID()
i.gamepads[id].name = id.GetName()
}
}
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
if !i.ui.isRunning() {
return nil
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for id, g := range i.gamepads {
if g.valid {
gamepadIDs = append(gamepadIDs, driver.GamepadID(id))
}
}
return gamepadIDs
}
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
if !i.ui.isRunning() {
return ""
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return ""
}
return i.gamepads[id].guid
}
func (i *Input) GamepadName(id driver.GamepadID) string {
if !i.ui.isRunning() {
return ""
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return ""
}
return i.gamepads[id].name
}
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
if !i.ui.isRunning() {
return 0
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return 0
}
return i.gamepads[id].axisNum
}
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
if !i.ui.isRunning() {
return 0
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return 0
}
return i.gamepads[id].axes[axis]
}
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
if !i.ui.isRunning() {
return 0
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return 0
}
return i.gamepads[id].buttonNum
}
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
if !i.ui.isRunning() {
return false
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return false
}
return i.gamepads[id].buttonPressed[button]
}
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return false
}
g := i.gamepads[int(id)]
return gamepaddb.HasStandardLayoutMapping(g.guid)
}
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return 0
}
g := i.gamepads[int(id)]
return gamepaddb.AxisValue(g.guid, axis, gamepadState{&g})
}
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return 0
}
g := i.gamepads[int(id)]
return gamepaddb.ButtonValue(g.guid, button, gamepadState{&g})
}
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return false
}
g := i.gamepads[int(id)]
return gamepaddb.IsButtonPressed(g.guid, button, gamepadState{&g})
}
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}
func init() {
// Confirm that all the hat state values are the same.
if gamepaddb.HatUp != glfw.HatUp {
panic("glfw: gamepaddb.HatUp must equal to glfw.HatUp but not")
}
if gamepaddb.HatRight != glfw.HatRight {
panic("glfw: gamepaddb.HatRight must equal to glfw.HatRight but not")
}
if gamepaddb.HatDown != glfw.HatDown {
panic("glfw: gamepaddb.HatDown must equal to glfw.HatDown but not")
}
if gamepaddb.HatLeft != glfw.HatLeft {
panic("glfw: gamepaddb.HatLeft must equal to glfw.HatLeft but not")
}
}
type gamepadState struct {
g *gamepad
}
func (s gamepadState) Axis(index int) float64 {
return s.g.axes[index]
}
func (s gamepadState) Button(index int) bool {
return s.g.buttonPressed[index]
}
func (s gamepadState) Hat(index int) int {
return s.g.hats[index]
}

View File

@ -0,0 +1,133 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build darwin && !ios
// +build darwin,!ios
package glfw
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
gamepadpkg "github.com/hajimehoshi/ebiten/v2/internal/gamepad"
)
type nativeGamepads struct{}
func (i *Input) updateGamepads() {
gamepadpkg.Update()
}
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
return gamepadpkg.AppendGamepadIDs(gamepadIDs)
}
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
g := gamepadpkg.Get(id)
if g == nil {
return ""
}
return g.SDLID()
}
func (i *Input) GamepadName(id driver.GamepadID) string {
g := gamepadpkg.Get(id)
if g == nil {
return ""
}
return g.Name()
}
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
return g.AxisNum()
}
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
return g.Axis(axis)
}
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
// For backward compatibility, hats are treated as buttons in GLFW.
return g.ButtonNum() + g.HatNum()*4
}
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
g := gamepadpkg.Get(id)
if g == nil {
return false
}
nbuttons := g.ButtonNum()
if int(button) < nbuttons {
return g.Button(int(button))
}
// For backward compatibility, hats are treated as buttons in GLFW.
if hat := (int(button) - nbuttons) / 4; hat < g.HatNum() {
dir := (int(button) - nbuttons) % 4
return g.Hat(hat)&(1<<dir) != 0
}
return false
}
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
g := gamepadpkg.Get(id)
if g == nil {
return false
}
return g.IsStandardLayoutAvailable()
}
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
return g.StandardAxisValue(axis)
}
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
return g.StandardButtonValue(button)
}
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
g := gamepadpkg.Get(id)
if g == nil {
return false
}
return g.IsStandardButtonPressed(button)
}
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}

View File

@ -20,11 +20,9 @@ package glfw
import (
"math"
"sync"
"time"
"unicode"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
)
@ -52,6 +50,8 @@ type Input struct {
touches map[driver.TouchID]pos // TODO: Implement this (#417)
runeBuffer []rune
ui *UserInterface
nativeGamepads
}
type pos struct {
@ -69,99 +69,6 @@ func (i *Input) CursorPosition() (x, y int) {
return i.cursorX, i.cursorY
}
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
if !i.ui.isRunning() {
return nil
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for id, g := range i.gamepads {
if g.valid {
gamepadIDs = append(gamepadIDs, driver.GamepadID(id))
}
}
return gamepadIDs
}
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
if !i.ui.isRunning() {
return ""
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return ""
}
return i.gamepads[id].guid
}
func (i *Input) GamepadName(id driver.GamepadID) string {
if !i.ui.isRunning() {
return ""
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return ""
}
return i.gamepads[id].name
}
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
if !i.ui.isRunning() {
return 0
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return 0
}
return i.gamepads[id].axisNum
}
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
if !i.ui.isRunning() {
return 0
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return 0
}
return i.gamepads[id].axes[axis]
}
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
if !i.ui.isRunning() {
return 0
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return 0
}
return i.gamepads[id].buttonNum
}
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
if !i.ui.isRunning() {
return false
}
i.ui.m.RLock()
defer i.ui.m.RUnlock()
if len(i.gamepads) <= int(id) {
return false
}
return i.gamepads[id].buttonPressed[button]
}
func (i *Input) AppendTouchIDs(touchIDs []driver.TouchID) []driver.TouchID {
if !i.ui.isRunning() {
return nil
@ -311,134 +218,5 @@ func (i *Input) update(window *glfw.Window, context driver.UIContext) {
i.cursorX, i.cursorY = int(cx), int(cy)
}
for id := glfw.Joystick(0); id < glfw.Joystick(len(i.gamepads)); id++ {
i.gamepads[id].valid = false
if !id.Present() {
continue
}
buttons := id.GetButtons()
// A gamepad can be detected even though there are not. Apparently, some special devices are
// recognized as gamepads by GLFW. In this case, the number of the 'buttons' can exceeds the
// maximum. Skip such devices as a tentative solution (#1173).
if len(buttons) > driver.GamepadButtonNum {
continue
}
i.gamepads[id].valid = true
i.gamepads[id].buttonNum = len(buttons)
for b := 0; b < len(i.gamepads[id].buttonPressed); b++ {
if len(buttons) <= b {
i.gamepads[id].buttonPressed[b] = false
continue
}
i.gamepads[id].buttonPressed[b] = glfw.Action(buttons[b]) == glfw.Press
}
axes32 := id.GetAxes()
i.gamepads[id].axisNum = len(axes32)
for a := 0; a < len(i.gamepads[id].axes); a++ {
if len(axes32) <= a {
i.gamepads[id].axes[a] = 0
continue
}
i.gamepads[id].axes[a] = float64(axes32[a])
}
hats := id.GetHats()
i.gamepads[id].hatsNum = len(hats)
for h := 0; h < len(i.gamepads[id].hats); h++ {
if len(hats) <= h {
i.gamepads[id].hats[h] = 0
continue
}
i.gamepads[id].hats[h] = int(hats[h])
}
// Note that GLFW's gamepad GUID follows SDL's GUID.
i.gamepads[id].guid = id.GetGUID()
i.gamepads[id].name = id.GetName()
}
}
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return false
}
g := i.gamepads[int(id)]
return gamepaddb.HasStandardLayoutMapping(g.guid)
}
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return 0
}
g := i.gamepads[int(id)]
return gamepaddb.AxisValue(g.guid, axis, gamepadState{&g})
}
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return 0
}
g := i.gamepads[int(id)]
return gamepaddb.ButtonValue(g.guid, button, gamepadState{&g})
}
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
i.ui.m.Lock()
defer i.ui.m.Unlock()
if len(i.gamepads) <= int(id) {
return false
}
g := i.gamepads[int(id)]
return gamepaddb.IsButtonPressed(g.guid, button, gamepadState{&g})
}
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}
func init() {
// Confirm that all the hat state values are the same.
if gamepaddb.HatUp != glfw.HatUp {
panic("glfw: gamepaddb.HatUp must equal to glfw.HatUp but not")
}
if gamepaddb.HatRight != glfw.HatRight {
panic("glfw: gamepaddb.HatRight must equal to glfw.HatRight but not")
}
if gamepaddb.HatDown != glfw.HatDown {
panic("glfw: gamepaddb.HatDown must equal to glfw.HatDown but not")
}
if gamepaddb.HatLeft != glfw.HatLeft {
panic("glfw: gamepaddb.HatLeft must equal to glfw.HatLeft but not")
}
}
type gamepadState struct {
g *gamepad
}
func (s gamepadState) Axis(index int) float64 {
return s.g.axes[index]
}
func (s gamepadState) Button(index int) bool {
return s.g.buttonPressed[index]
}
func (s gamepadState) Hat(index int) int {
return s.g.hats[index]
i.updateGamepads()
}