mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-12 20:18:59 +01:00
parent
d2afbd43cc
commit
ef45058037
116
internal/gamepad/api_linux.go
Normal file
116
internal/gamepad/api_linux.go
Normal file
@ -0,0 +1,116 @@
|
||||
// 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
|
||||
// +build !android
|
||||
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
_ABS_HAT0X = 0x10
|
||||
_ABS_HAT3Y = 0x17
|
||||
_ABS_MAX = 0x3f
|
||||
_ABS_CNT = _ABS_MAX + 1
|
||||
|
||||
_BTN_MISC = 0x100
|
||||
|
||||
_IOC_NONE = 0
|
||||
_IOC_WRITE = 1
|
||||
_IOC_READ = 2
|
||||
|
||||
_IOC_NRBITS = 8
|
||||
_IOC_TYPEBITS = 8
|
||||
_IOC_SIZEBITS = 14
|
||||
_IOC_DIRBITS = 2
|
||||
|
||||
_IOC_NRSHIFT = 0
|
||||
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
|
||||
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
|
||||
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
|
||||
|
||||
_KEY_MAX = 0x2ff
|
||||
_KEY_CNT = _KEY_MAX + 1
|
||||
|
||||
_SYN_REPORT = 0
|
||||
_SYN_DROPPED = 3
|
||||
)
|
||||
|
||||
func _IOC(dir, typ, nr, size uint) uint {
|
||||
return dir<<_IOC_DIRSHIFT | typ<<_IOC_TYPESHIFT | nr<<_IOC_NRSHIFT | size<<_IOC_SIZESHIFT
|
||||
}
|
||||
|
||||
func _IOR(typ, nr, size uint) uint {
|
||||
return _IOC(_IOC_READ, typ, nr, size)
|
||||
}
|
||||
|
||||
func _EVIOCGABS(abs uint) uint {
|
||||
return _IOR('E', 0x40+abs, uint(unsafe.Sizeof(input_absinfo{})))
|
||||
}
|
||||
|
||||
func _EVIOCGBIT(ev, len uint) uint {
|
||||
return _IOC(_IOC_READ, 'E', 0x20+ev, len)
|
||||
}
|
||||
|
||||
func _EVIOCGID() uint {
|
||||
return _IOR('E', 0x02, uint(unsafe.Sizeof(input_id{})))
|
||||
}
|
||||
|
||||
func _EVIOCGNAME(len uint) uint {
|
||||
return _IOC(_IOC_READ, 'E', 0x06, len)
|
||||
}
|
||||
|
||||
type inotify_event struct {
|
||||
wd int32 // TODO: The original type is C's int. Is it OK to use int32?
|
||||
mask uint32
|
||||
cookie uint32
|
||||
len uint32
|
||||
name string
|
||||
}
|
||||
|
||||
type input_absinfo struct {
|
||||
value int32
|
||||
minimum int32
|
||||
maximum int32
|
||||
fuzz int32
|
||||
flat int32
|
||||
resolution int32
|
||||
}
|
||||
|
||||
type input_event struct {
|
||||
time unix.Timeval
|
||||
typ uint16
|
||||
code uint16
|
||||
value int32
|
||||
}
|
||||
|
||||
type input_id struct {
|
||||
bustype uint16
|
||||
vendor uint16
|
||||
product uint16
|
||||
version uint16
|
||||
}
|
||||
|
||||
func ioctl(fd int, request uint, ptr unsafe.Pointer) error {
|
||||
r, _, e := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(request), uintptr(ptr))
|
||||
if r < 0 {
|
||||
return unix.Errno(e)
|
||||
}
|
||||
return nil
|
||||
}
|
438
internal/gamepad/gamepad_linux.go
Normal file
438
internal/gamepad/gamepad_linux.go
Normal file
@ -0,0 +1,438 @@
|
||||
// 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
|
||||
// +build !android
|
||||
|
||||
package gamepad
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const dirName = "/dev/input"
|
||||
|
||||
var reEvent = regexp.MustCompile(`^event[0-9]+$`)
|
||||
|
||||
func isBitSet(s []byte, bit int) bool {
|
||||
return s[bit/8]&(1<<(bit%8)) != 0
|
||||
}
|
||||
|
||||
type nativeGamepads struct {
|
||||
inotify int
|
||||
watch int
|
||||
}
|
||||
|
||||
func (g *nativeGamepads) init(gamepads *gamepads) error {
|
||||
// Check the existence of the directory `dirName`.
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Stat(dirName, &stat); err != nil {
|
||||
if err == unix.ENOENT {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Stat failed: %w", err)
|
||||
}
|
||||
if stat.Mode&unix.S_IFDIR == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inotify, err := unix.InotifyInit1(unix.IN_NONBLOCK | unix.IN_CLOEXEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamepad: InotifyInit1 failed: %w", err)
|
||||
}
|
||||
g.inotify = inotify
|
||||
|
||||
if g.inotify > 0 {
|
||||
// Register for IN_ATTRIB to get notified when udev is done.
|
||||
// This works well in practice but the true way is libudev.
|
||||
watch, err := unix.InotifyAddWatch(g.inotify, dirName, unix.IN_CREATE|unix.IN_ATTRIB|unix.IN_DELETE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamepad: InotifyAddWatch failed: %w", err)
|
||||
}
|
||||
g.watch = watch
|
||||
}
|
||||
|
||||
ents, err := ioutil.ReadDir(dirName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamepad: ReadDir(%s) failed: %w", dirName, err)
|
||||
}
|
||||
for _, ent := range ents {
|
||||
if ent.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !reEvent.MatchString(ent.Name()) {
|
||||
continue
|
||||
}
|
||||
if err := g.openGamepad(gamepads, filepath.Join(dirName, ent.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*nativeGamepads) openGamepad(gamepads *gamepads, path string) (err error) {
|
||||
if gamepads.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.path == path
|
||||
}) != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
if err == unix.EACCES {
|
||||
return nil
|
||||
}
|
||||
// This happens just after a disconnection.
|
||||
if err == unix.ENOENT {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Open failed: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
unix.Close(fd)
|
||||
}
|
||||
}()
|
||||
|
||||
evBits := make([]byte, (unix.EV_CNT+7)/8)
|
||||
keyBits := make([]byte, (_KEY_CNT+7)/8)
|
||||
absBits := make([]byte, (_ABS_CNT+7)/8)
|
||||
var id input_id
|
||||
if err := ioctl(fd, _EVIOCGBIT(0, uint(len(evBits))), unsafe.Pointer(&evBits[0])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for evBits failed: %w", err)
|
||||
}
|
||||
if err := ioctl(fd, _EVIOCGBIT(unix.EV_KEY, uint(len(keyBits))), unsafe.Pointer(&keyBits[0])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for keyBits failed: %w", err)
|
||||
}
|
||||
if err := ioctl(fd, _EVIOCGBIT(unix.EV_ABS, uint(len(absBits))), unsafe.Pointer(&absBits[0])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for absBits failed: %w", err)
|
||||
}
|
||||
if err := ioctl(fd, _EVIOCGID(), unsafe.Pointer(&id)); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for an ID failed: %w", err)
|
||||
}
|
||||
|
||||
if !isBitSet(evBits, unix.EV_KEY) {
|
||||
unix.Close(fd)
|
||||
return nil
|
||||
}
|
||||
if !isBitSet(evBits, unix.EV_ABS) {
|
||||
unix.Close(fd)
|
||||
return nil
|
||||
}
|
||||
|
||||
cname := make([]byte, 256)
|
||||
name := "Unknown"
|
||||
// TODO: Is it OK to ignore the error here?
|
||||
if err := ioctl(fd, uint(_EVIOCGNAME(uint(len(name)))), unsafe.Pointer(&cname[0])); err == nil {
|
||||
name = unix.ByteSliceToString(cname)
|
||||
}
|
||||
|
||||
var sdlID string
|
||||
if id.vendor != 0 && id.product != 0 && id.version != 0 {
|
||||
sdlID = fmt.Sprintf("%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000",
|
||||
byte(id.bustype), byte(id.bustype>>8),
|
||||
byte(id.vendor), byte(id.vendor>>8),
|
||||
byte(id.product), byte(id.product>>8),
|
||||
byte(id.version), byte(id.version>>8))
|
||||
} else {
|
||||
bs := []byte(name)
|
||||
if len(bs) < 12 {
|
||||
bs = append(bs, make([]byte, 12-len(bs))...)
|
||||
}
|
||||
sdlID = fmt.Sprintf("%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
byte(id.bustype), byte(id.bustype>>8),
|
||||
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.path = path
|
||||
gp.fd = fd
|
||||
runtime.SetFinalizer(gp, func(gp *Gamepad) {
|
||||
gp.close()
|
||||
})
|
||||
|
||||
var axisCount int
|
||||
var buttonCount int
|
||||
var hatCount int
|
||||
for code := _BTN_MISC; code < _KEY_CNT; code++ {
|
||||
if !isBitSet(keyBits, code) {
|
||||
continue
|
||||
}
|
||||
gp.keyMap[code-_BTN_MISC] = buttonCount
|
||||
buttonCount++
|
||||
}
|
||||
for code := 0; code < _ABS_CNT; code++ {
|
||||
gp.absMap[code] = -1
|
||||
if !isBitSet(absBits, code) {
|
||||
continue
|
||||
}
|
||||
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
||||
gp.absMap[code] = hatCount
|
||||
hatCount++
|
||||
// Skip Y.
|
||||
code++
|
||||
continue
|
||||
}
|
||||
if err := ioctl(gp.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&gp.absInfo[code])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for an abs at openGamepad failed: %w", err)
|
||||
}
|
||||
gp.absMap[code] = axisCount
|
||||
axisCount++
|
||||
}
|
||||
|
||||
gp.axisCount_ = axisCount
|
||||
gp.buttonCount_ = buttonCount
|
||||
gp.hatCount_ = hatCount
|
||||
|
||||
if err := gp.pollAbsState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepads) update(gamepads *gamepads) error {
|
||||
if g.inotify <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, 16384)
|
||||
n, err := unix.Read(g.inotify, buf[:])
|
||||
if err != nil {
|
||||
if err == unix.EAGAIN {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Read failed: %w", err)
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
for len(buf) > 0 {
|
||||
e := inotify_event{
|
||||
wd: int32(buf[0]) | int32(buf[1])<<8 | int32(buf[2])<<16 | int32(buf[3])<<24,
|
||||
mask: uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16 | uint32(buf[7])<<24,
|
||||
cookie: uint32(buf[8]) | uint32(buf[9])<<8 | uint32(buf[10])<<16 | uint32(buf[11])<<24,
|
||||
len: uint32(buf[12]) | uint32(buf[13])<<8 | uint32(buf[14])<<16 | uint32(buf[15])<<24,
|
||||
}
|
||||
e.name = unix.ByteSliceToString(buf[16 : 16+e.len-1]) // len includes the null termiinate.
|
||||
buf = buf[16+e.len:]
|
||||
if !reEvent.MatchString(e.name) {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(dirName, e.name)
|
||||
if e.mask&(unix.IN_CREATE|unix.IN_ATTRIB) != 0 {
|
||||
if err := g.openGamepad(gamepads, path); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if e.mask&unix.IN_DELETE != 0 {
|
||||
if gp := gamepads.find(func(gamepad *Gamepad) bool {
|
||||
return gamepad.path == path
|
||||
}); gp != nil {
|
||||
gp.close()
|
||||
gamepads.remove(func(gamepad *Gamepad) bool {
|
||||
return gamepad == gp
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type nativeGamepad struct {
|
||||
fd int
|
||||
path string
|
||||
keyMap [_KEY_CNT - _BTN_MISC]int
|
||||
absMap [_ABS_CNT]int
|
||||
absInfo [_ABS_CNT]input_absinfo
|
||||
dropped bool
|
||||
|
||||
axes [_ABS_CNT]float64
|
||||
buttons [_KEY_CNT - _BTN_MISC]bool
|
||||
hats [4]int
|
||||
|
||||
axisCount_ int
|
||||
buttonCount_ int
|
||||
hatCount_ int
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) close() {
|
||||
if g.fd != 0 {
|
||||
unix.Close(g.fd)
|
||||
}
|
||||
g.fd = 0
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) update(gamepad *gamepads) error {
|
||||
if g.fd == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
buf := make([]byte, unsafe.Sizeof(input_event{}))
|
||||
// TODO: Should the returned byte count be cared?
|
||||
if _, err := unix.Read(g.fd, buf); err != nil {
|
||||
if err == unix.EAGAIN {
|
||||
break
|
||||
}
|
||||
// Disconnected
|
||||
if err == unix.ENODEV {
|
||||
g.close()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("gamepad: Read failed: %w", err)
|
||||
}
|
||||
|
||||
// time is not used.
|
||||
e := input_event{
|
||||
typ: uint16(buf[16]) | uint16(buf[17])<<8,
|
||||
code: uint16(buf[18]) | uint16(buf[19])<<8,
|
||||
value: int32(buf[20]) | int32(buf[21])<<8 | int32(buf[22])<<16 | int32(buf[23])<<24,
|
||||
}
|
||||
|
||||
if e.typ == unix.EV_SYN {
|
||||
switch e.code {
|
||||
case _SYN_DROPPED:
|
||||
g.dropped = true
|
||||
case _SYN_REPORT:
|
||||
g.dropped = false
|
||||
g.pollAbsState()
|
||||
}
|
||||
}
|
||||
if g.dropped {
|
||||
continue
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
case unix.EV_KEY:
|
||||
idx := g.keyMap[e.code-_BTN_MISC]
|
||||
g.buttons[idx] = e.value != 0
|
||||
case unix.EV_ABS:
|
||||
g.handleAbsEvent(int(e.code), e.value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) pollAbsState() error {
|
||||
for code := 0; code < _ABS_CNT; code++ {
|
||||
if g.absMap[code] < 0 {
|
||||
continue
|
||||
}
|
||||
if err := ioctl(g.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&g.absInfo[code])); err != nil {
|
||||
return fmt.Errorf("gamepad: ioctl for an abs at pollAbsState failed: %w", err)
|
||||
}
|
||||
g.handleAbsEvent(code, g.absInfo[code].value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) handleAbsEvent(code int, value int32) {
|
||||
index := g.absMap[code]
|
||||
|
||||
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
|
||||
axis := (code - _ABS_HAT0X) % 2
|
||||
|
||||
switch axis {
|
||||
case 0:
|
||||
switch {
|
||||
case value < 0:
|
||||
g.hats[index] |= hatLeft
|
||||
g.hats[index] &^= hatRight
|
||||
case value > 0:
|
||||
g.hats[index] &^= hatLeft
|
||||
g.hats[index] |= hatRight
|
||||
default:
|
||||
g.hats[index] &^= hatLeft | hatRight
|
||||
}
|
||||
case 1:
|
||||
switch {
|
||||
case value < 0:
|
||||
g.hats[index] |= hatUp
|
||||
g.hats[index] &^= hatDown
|
||||
case value > 0:
|
||||
g.hats[index] &^= hatUp
|
||||
g.hats[index] |= hatDown
|
||||
default:
|
||||
g.hats[index] &^= hatUp | hatDown
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
info := g.absInfo[code]
|
||||
v := float64(value)
|
||||
if r := float64(info.maximum) - float64(info.minimum); r != 0 {
|
||||
v = (v - float64(info.minimum)) / r
|
||||
v = v*2 - 1
|
||||
}
|
||||
g.axes[index] = v
|
||||
}
|
||||
|
||||
func (*nativeGamepad) hasOwnStandardLayoutMapping() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) axisCount() int {
|
||||
return g.axisCount_
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) buttonCount() int {
|
||||
return g.buttonCount_
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) hatCount() int {
|
||||
return g.hatCount_
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) axisValue(axis int) float64 {
|
||||
if axis < 0 || axis >= g.axisCount_ {
|
||||
return 0
|
||||
}
|
||||
return g.axes[axis]
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) isButtonPressed(button int) bool {
|
||||
if button < 0 || button >= g.buttonCount_ {
|
||||
return false
|
||||
}
|
||||
return g.buttons[button]
|
||||
}
|
||||
|
||||
func (*nativeGamepad) buttonValue(button int) float64 {
|
||||
panic("gamepad: buttonValue is not implemented")
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) hatState(hat int) int {
|
||||
if hat < 0 || hat >= g.hatCount_ {
|
||||
return hatCentered
|
||||
}
|
||||
return g.hats[hat]
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
@ -12,9 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (!darwin || ios) && !js && !windows
|
||||
//go:build (!darwin || ios) && !js && (!linux || android) && !windows
|
||||
// +build !darwin ios
|
||||
// +build !js
|
||||
// +build !linux android
|
||||
// +build !windows
|
||||
|
||||
package gamepad
|
||||
@ -72,5 +73,4 @@ func (*nativeGamepad) hatState(hat int) int {
|
||||
}
|
||||
|
||||
func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
|
||||
// TODO: Implement this (#1452)
|
||||
}
|
||||
|
@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (darwin && !ios) || windows
|
||||
// +build darwin,!ios windows
|
||||
//go:build !android && !js && !ios
|
||||
// +build !android,!js,!ios
|
||||
|
||||
package glfw
|
||||
|
@ -1,260 +0,0 @@
|
||||
// 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 && !windows
|
||||
// +build !android,!js,!darwin,!windows
|
||||
|
||||
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() error {
|
||||
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()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
||||
if name := gamepaddb.Name(i.gamepads[id].guid); name != "" {
|
||||
return name
|
||||
}
|
||||
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]
|
||||
}
|
Loading…
Reference in New Issue
Block a user