zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

gamepaddb.go (12751B)


      1 // Copyright 2021 The Ebiten Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 // gamecontrollerdb.txt is downloaded at https://github.com/gabomdq/SDL_GameControllerDB.
     16 
     17 // To update the database file, run:
     18 //
     19 //     curl --location --remote-name https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt
     20 
     21 //go:generate file2byteslice -package gamepaddb -input=./gamecontrollerdb.txt -output=./gamecontrollerdb.txt.go -var=gamecontrollerdbTxt
     22 
     23 package gamepaddb
     24 
     25 import (
     26 	"bufio"
     27 	"bytes"
     28 	"fmt"
     29 	"io"
     30 	"runtime"
     31 	"strconv"
     32 	"strings"
     33 	"sync"
     34 
     35 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     36 )
     37 
     38 type platform int
     39 
     40 const (
     41 	platformUnknown platform = iota
     42 	platformWindows
     43 	platformMacOS
     44 	platformUnix
     45 	platformAndroid
     46 	platformIOS
     47 )
     48 
     49 var currentPlatform platform
     50 
     51 func init() {
     52 	if runtime.GOOS == "windows" {
     53 		currentPlatform = platformWindows
     54 		return
     55 	}
     56 
     57 	if runtime.GOOS == "aix" ||
     58 		runtime.GOOS == "dragonfly" ||
     59 		runtime.GOOS == "freebsd" ||
     60 		runtime.GOOS == "hurd" ||
     61 		runtime.GOOS == "illumos" ||
     62 		runtime.GOOS == "linux" ||
     63 		runtime.GOOS == "netbsd" ||
     64 		runtime.GOOS == "openbsd" ||
     65 		runtime.GOOS == "solaris" {
     66 		currentPlatform = platformUnix
     67 		return
     68 	}
     69 
     70 	if runtime.GOOS == "android" {
     71 		currentPlatform = platformAndroid
     72 		return
     73 	}
     74 
     75 	if isIOS {
     76 		currentPlatform = platformIOS
     77 		return
     78 	}
     79 
     80 	if runtime.GOOS == "darwin" {
     81 		currentPlatform = platformMacOS
     82 		return
     83 	}
     84 }
     85 
     86 var additionalGLFWGamepads = []byte(`
     87 78696e70757401000000000000000000,XInput Gamepad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
     88 78696e70757402000000000000000000,XInput Wheel (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
     89 78696e70757403000000000000000000,XInput Arcade Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
     90 78696e70757404000000000000000000,XInput Flight Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
     91 78696e70757405000000000000000000,XInput Dance Pad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
     92 78696e70757406000000000000000000,XInput Guitar (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
     93 78696e70757408000000000000000000,XInput Drum Kit (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
     94 `)
     95 
     96 func init() {
     97 	if _, err := Update(gamecontrollerdbTxt); err != nil {
     98 		panic(err)
     99 	}
    100 	if _, err := Update(additionalGLFWGamepads); err != nil {
    101 		panic(err)
    102 	}
    103 }
    104 
    105 type mappingType int
    106 
    107 const (
    108 	mappingTypeButton mappingType = iota
    109 	mappingTypeAxis
    110 	mappingTypeHat
    111 )
    112 
    113 const (
    114 	HatUp    = 1
    115 	HatRight = 2
    116 	HatDown  = 4
    117 	HatLeft  = 8
    118 )
    119 
    120 type mapping struct {
    121 	Type       mappingType
    122 	Index      int
    123 	AxisScale  int
    124 	AxisOffset int
    125 	HatState   int
    126 }
    127 
    128 var (
    129 	gamepadButtonMappings = map[string]map[driver.StandardGamepadButton]*mapping{}
    130 	gamepadAxisMappings   = map[string]map[driver.StandardGamepadAxis]*mapping{}
    131 	mappingsM             sync.RWMutex
    132 )
    133 
    134 func processLine(line string, platform platform) error {
    135 	line = strings.TrimSpace(line)
    136 	if len(line) == 0 {
    137 		return nil
    138 	}
    139 	if line[0] == '#' {
    140 		return nil
    141 	}
    142 	tokens := strings.Split(line, ",")
    143 	id := tokens[0]
    144 	for _, token := range tokens[2:] {
    145 		if len(token) == 0 {
    146 			continue
    147 		}
    148 		tks := strings.Split(token, ":")
    149 		if tks[0] == "platform" {
    150 			switch tks[1] {
    151 			case "Windows":
    152 				if platform != platformWindows {
    153 					return nil
    154 				}
    155 			case "Mac OS X":
    156 				if platform != platformMacOS {
    157 					return nil
    158 				}
    159 			case "Linux":
    160 				if platform != platformUnix {
    161 					return nil
    162 				}
    163 			case "Android":
    164 				if platform != platformAndroid {
    165 					return nil
    166 				}
    167 			case "iOS":
    168 				if platform != platformIOS {
    169 					return nil
    170 				}
    171 			default:
    172 				return fmt.Errorf("gamepaddb: unexpected platform: %s", tks[1])
    173 			}
    174 			continue
    175 		}
    176 
    177 		gb, err := parseMappingElement(tks[1])
    178 		if err != nil {
    179 			return err
    180 		}
    181 
    182 		if b, ok := toStandardGamepadButton(tks[0]); ok {
    183 			m, ok := gamepadButtonMappings[id]
    184 			if !ok {
    185 				m = map[driver.StandardGamepadButton]*mapping{}
    186 				gamepadButtonMappings[id] = m
    187 			}
    188 			m[b] = gb
    189 			continue
    190 		}
    191 
    192 		if a, ok := toStandardGamepadAxis(tks[0]); ok {
    193 			m, ok := gamepadAxisMappings[id]
    194 			if !ok {
    195 				m = map[driver.StandardGamepadAxis]*mapping{}
    196 				gamepadAxisMappings[id] = m
    197 			}
    198 			m[a] = gb
    199 			continue
    200 		}
    201 
    202 		// The buttons like "misc1" are ignored so far.
    203 		// There is no corresponding button in the Web standard gamepad layout.
    204 	}
    205 
    206 	return nil
    207 }
    208 
    209 func parseMappingElement(str string) (*mapping, error) {
    210 	switch {
    211 	case str[0] == 'a' || strings.HasPrefix(str, "+a") || strings.HasPrefix(str, "-a"):
    212 		var tilda bool
    213 		if str[len(str)-1] == '~' {
    214 			str = str[:len(str)-1]
    215 			tilda = true
    216 		}
    217 
    218 		min := -1
    219 		max := 1
    220 		numstr := str[1:]
    221 
    222 		if str[0] == '+' {
    223 			numstr = str[2:]
    224 			min = 0
    225 		} else if str[0] == '-' {
    226 			numstr = str[2:]
    227 			max = 0
    228 		}
    229 
    230 		scale := 2 / (max - min)
    231 		offset := -(max + min)
    232 		if tilda {
    233 			scale = -scale
    234 			offset = -offset
    235 		}
    236 
    237 		index, err := strconv.Atoi(numstr)
    238 		if err != nil {
    239 			return nil, err
    240 		}
    241 
    242 		return &mapping{
    243 			Type:       mappingTypeAxis,
    244 			Index:      index,
    245 			AxisScale:  scale,
    246 			AxisOffset: offset,
    247 		}, nil
    248 
    249 	case str[0] == 'b':
    250 		index, err := strconv.Atoi(str[1:])
    251 		if err != nil {
    252 			return nil, err
    253 		}
    254 		return &mapping{
    255 			Type:  mappingTypeButton,
    256 			Index: index,
    257 		}, nil
    258 
    259 	case str[0] == 'h':
    260 		tokens := strings.Split(str[1:], ".")
    261 		if len(tokens) < 2 {
    262 			return nil, fmt.Errorf("gamepaddb: unexpected hat: %s", str)
    263 		}
    264 		index, err := strconv.Atoi(tokens[0])
    265 		if err != nil {
    266 			return nil, err
    267 		}
    268 		hat, err := strconv.Atoi(tokens[1])
    269 		if err != nil {
    270 			return nil, err
    271 		}
    272 		return &mapping{
    273 			Type:     mappingTypeHat,
    274 			Index:    index,
    275 			HatState: hat,
    276 		}, nil
    277 	}
    278 
    279 	return nil, fmt.Errorf("gamepaddb: unepxected mapping: %s", str)
    280 }
    281 
    282 func toStandardGamepadButton(str string) (driver.StandardGamepadButton, bool) {
    283 	switch str {
    284 	case "a":
    285 		return driver.StandardGamepadButtonRightBottom, true
    286 	case "b":
    287 		return driver.StandardGamepadButtonRightRight, true
    288 	case "x":
    289 		return driver.StandardGamepadButtonRightLeft, true
    290 	case "y":
    291 		return driver.StandardGamepadButtonRightTop, true
    292 	case "back":
    293 		return driver.StandardGamepadButtonCenterLeft, true
    294 	case "start":
    295 		return driver.StandardGamepadButtonCenterRight, true
    296 	case "guide":
    297 		return driver.StandardGamepadButtonCenterCenter, true
    298 	case "leftshoulder":
    299 		return driver.StandardGamepadButtonFrontTopLeft, true
    300 	case "rightshoulder":
    301 		return driver.StandardGamepadButtonFrontTopRight, true
    302 	case "leftstick":
    303 		return driver.StandardGamepadButtonLeftStick, true
    304 	case "rightstick":
    305 		return driver.StandardGamepadButtonRightStick, true
    306 	case "dpup":
    307 		return driver.StandardGamepadButtonLeftTop, true
    308 	case "dpright":
    309 		return driver.StandardGamepadButtonLeftRight, true
    310 	case "dpdown":
    311 		return driver.StandardGamepadButtonLeftBottom, true
    312 	case "dpleft":
    313 		return driver.StandardGamepadButtonLeftLeft, true
    314 	case "lefttrigger":
    315 		return driver.StandardGamepadButtonFrontBottomLeft, true
    316 	case "righttrigger":
    317 		return driver.StandardGamepadButtonFrontBottomRight, true
    318 	default:
    319 		return 0, false
    320 	}
    321 }
    322 
    323 func toStandardGamepadAxis(str string) (driver.StandardGamepadAxis, bool) {
    324 	switch str {
    325 	case "leftx":
    326 		return driver.StandardGamepadAxisLeftStickHorizontal, true
    327 	case "lefty":
    328 		return driver.StandardGamepadAxisLeftStickVertical, true
    329 	case "rightx":
    330 		return driver.StandardGamepadAxisRightStickHorizontal, true
    331 	case "righty":
    332 		return driver.StandardGamepadAxisRightStickVertical, true
    333 	default:
    334 		return 0, false
    335 	}
    336 }
    337 
    338 func HasStandardLayoutMapping(id string) bool {
    339 	mappingsM.RLock()
    340 	defer mappingsM.RUnlock()
    341 
    342 	if _, ok := gamepadButtonMappings[id]; ok {
    343 		return true
    344 	}
    345 	if _, ok := gamepadAxisMappings[id]; ok {
    346 		return true
    347 	}
    348 	return false
    349 }
    350 
    351 type GamepadState interface {
    352 	Axis(index int) float64
    353 	Button(index int) bool
    354 	Hat(index int) int
    355 }
    356 
    357 func AxisValue(id string, axis driver.StandardGamepadAxis, state GamepadState) float64 {
    358 	mappingsM.RLock()
    359 	defer mappingsM.RUnlock()
    360 
    361 	mappings, ok := gamepadAxisMappings[id]
    362 	if !ok {
    363 		return 0
    364 	}
    365 
    366 	mapping := mappings[axis]
    367 	if mapping == nil {
    368 		return 0
    369 	}
    370 
    371 	switch mapping.Type {
    372 	case mappingTypeAxis:
    373 		v := state.Axis(mapping.Index)*float64(mapping.AxisScale) + float64(mapping.AxisOffset)
    374 		if v > 1 {
    375 			return 1
    376 		} else if v < -1 {
    377 			return -1
    378 		}
    379 		return v
    380 	case mappingTypeButton:
    381 		if state.Button(mapping.Index) {
    382 			return 1
    383 		} else {
    384 			return -1
    385 		}
    386 	case mappingTypeHat:
    387 		if state.Hat(mapping.Index)&mapping.HatState != 0 {
    388 			return 1
    389 		} else {
    390 			return -1
    391 		}
    392 	}
    393 
    394 	return 0
    395 }
    396 
    397 func ButtonValue(id string, button driver.StandardGamepadButton, state GamepadState) float64 {
    398 	mappingsM.RLock()
    399 	defer mappingsM.RUnlock()
    400 
    401 	return buttonValue(id, button, state)
    402 }
    403 
    404 func buttonValue(id string, button driver.StandardGamepadButton, state GamepadState) float64 {
    405 	mappings, ok := gamepadButtonMappings[id]
    406 	if !ok {
    407 		return 0
    408 	}
    409 
    410 	mapping := mappings[button]
    411 	if mapping == nil {
    412 		return 0
    413 	}
    414 
    415 	switch mapping.Type {
    416 	case mappingTypeAxis:
    417 		v := state.Axis(mapping.Index)*float64(mapping.AxisScale) + float64(mapping.AxisOffset)
    418 		if v > 1 {
    419 			v = 1
    420 		} else if v < -1 {
    421 			v = -1
    422 		}
    423 		// Adjust [-1, 1] to [0, 1]
    424 		return (v + 1) / 2
    425 	case mappingTypeButton:
    426 		if state.Button(mapping.Index) {
    427 			return 1
    428 		}
    429 		return 0
    430 	case mappingTypeHat:
    431 		if state.Hat(mapping.Index)&mapping.HatState != 0 {
    432 			return 1
    433 		}
    434 		return 0
    435 	}
    436 
    437 	return 0
    438 }
    439 
    440 func IsButtonPressed(id string, button driver.StandardGamepadButton, state GamepadState) bool {
    441 	// Use XInput's trigger dead zone.
    442 	// See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/public/cpp/gamepad.h;l=22-23;drc=6997f8a177359bb99598988ed5e900841984d242
    443 	const threshold = 30.0 / 255.0
    444 
    445 	mappingsM.RLock()
    446 	defer mappingsM.RUnlock()
    447 
    448 	mappings, ok := gamepadButtonMappings[id]
    449 	if !ok {
    450 		return false
    451 	}
    452 
    453 	mapping := mappings[button]
    454 	if mapping == nil {
    455 		return false
    456 	}
    457 
    458 	switch mapping.Type {
    459 	case mappingTypeAxis:
    460 		v := buttonValue(id, button, state)
    461 		return v > threshold
    462 	case mappingTypeButton:
    463 		return state.Button(mapping.Index)
    464 	case mappingTypeHat:
    465 		return state.Hat(mapping.Index)&mapping.HatState != 0
    466 	}
    467 
    468 	return false
    469 }
    470 
    471 // Update adds new gamepad mappings.
    472 // The string must be in the format of SDL_GameControllerDB.
    473 func Update(mapping []byte) (bool, error) {
    474 	if currentPlatform == platformUnknown {
    475 		return false, nil
    476 	}
    477 
    478 	// TODO: Implement this (#1557)
    479 	if currentPlatform == platformAndroid || currentPlatform == platformIOS {
    480 		// Note: NOT returning an error, as mappings also do not matter right now.
    481 		return false, nil
    482 	}
    483 
    484 	mappingsM.Lock()
    485 	defer mappingsM.Unlock()
    486 
    487 	buf := bytes.NewBuffer(mapping)
    488 	r := bufio.NewReader(buf)
    489 	for {
    490 		line, err := r.ReadString('\n')
    491 		if err != nil && err != io.EOF {
    492 			return false, err
    493 		}
    494 		if err := processLine(line, currentPlatform); err != nil {
    495 			return false, err
    496 		}
    497 		if err == io.EOF {
    498 			break
    499 		}
    500 	}
    501 
    502 	return true, nil
    503 }