From ef450580370cdf84f8795181d6898cfbaebae6e2 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 1 Feb 2022 23:56:15 +0900 Subject: [PATCH] internal/gamepad: implement for Linux Updates #1452 --- internal/gamepad/api_linux.go | 116 +++++ internal/gamepad/gamepad_linux.go | 438 ++++++++++++++++++ internal/gamepad/gamepad_null.go | 4 +- .../glfw/{gamepad_native.go => gamepad.go} | 4 +- internal/uidriver/glfw/gamepad_glfw.go | 260 ----------- 5 files changed, 558 insertions(+), 264 deletions(-) create mode 100644 internal/gamepad/api_linux.go create mode 100644 internal/gamepad/gamepad_linux.go rename internal/uidriver/glfw/{gamepad_native.go => gamepad.go} (97%) delete mode 100644 internal/uidriver/glfw/gamepad_glfw.go diff --git a/internal/gamepad/api_linux.go b/internal/gamepad/api_linux.go new file mode 100644 index 000000000..8d53f8c2d --- /dev/null +++ b/internal/gamepad/api_linux.go @@ -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 +} diff --git a/internal/gamepad/gamepad_linux.go b/internal/gamepad/gamepad_linux.go new file mode 100644 index 000000000..38b8ae30b --- /dev/null +++ b/internal/gamepad/gamepad_linux.go @@ -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) +} diff --git a/internal/gamepad/gamepad_null.go b/internal/gamepad/gamepad_null.go index e75c510ab..2324cd57c 100644 --- a/internal/gamepad/gamepad_null.go +++ b/internal/gamepad/gamepad_null.go @@ -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) } diff --git a/internal/uidriver/glfw/gamepad_native.go b/internal/uidriver/glfw/gamepad.go similarity index 97% rename from internal/uidriver/glfw/gamepad_native.go rename to internal/uidriver/glfw/gamepad.go index b9c37622a..598f01756 100644 --- a/internal/uidriver/glfw/gamepad_native.go +++ b/internal/uidriver/glfw/gamepad.go @@ -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 diff --git a/internal/uidriver/glfw/gamepad_glfw.go b/internal/uidriver/glfw/gamepad_glfw.go deleted file mode 100644 index 81dd2ee3b..000000000 --- a/internal/uidriver/glfw/gamepad_glfw.go +++ /dev/null @@ -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] -}