Search code examples
gocross-platformjoystickgamepad

How to get a list of available joystick ids in Go


I'm trying to find joystick state in a cross platform way - developing on windows - using Golang. I'm currently looking to use the joystick API at https://github.com/0xcafed00d/joystick

However, for Go on windows, I don't know how to find the id that I need for joystick.Open(joy_id).

Does anyone know of a Go API that allows me to get a list of currently plugged in joysticks/gamepads so I can iterate through their ids? I don't mind if this is just for Windows. N.B. I'm happy to use standard windows libraries or Go packages, but don't want to have to install anything, e.g. SDL 2, since the target users are non technical.

Note 2: I've originally did this with the browser Gamepad API - but the gamepad API is suspended when the browser window is in the background/minimized.


Solution

  • On Windows, I have found that you use an int from 0..3 for xinput controllers (possibly higher for directinput).

    Note that the id is allocated when a controller is plugged in and kept (possibly until the OS is restarted). This means that if a controller is disconnected then reconnects, you should get the same id. There is likely an issue if you plug in >4 controllers - I haven't been able to test this.

    To get the joysticks states, I did the following (code at the bottom of this post - called with go gamepad.CheckChanged()):

    1. 2 times per second, iterate through 0 to 3 (or more) calling joystick.Open for not connected ids - adding them to an array of gamepads.
    2. 60 times a second, check any valid joysticks, getting the state (I actually only check id 0 and 1 below) and showing any change in the buttons mask.

    Note that since the Media API will be used for windows, this detects DualShock 4 and X360 controllers but has the button masks in a different order and also a different number of axes.

    I have currently decided not to continue down this route, but hope this helps anyone working on a similar issue.

    package gamepad
    
    import (
        "fmt"
        "time"
    
        "github.com/0xcafed00d/joystick"
    )
    
    const MAX_GAMEPAD = 8
    
    type Gamepad struct {
        last_buttons uint32
        last_axes    []int
        joystick     joystick.Joystick
    }
    
    var gamepads [MAX_GAMEPAD]Gamepad
    
    func gamepadUpdate(num int) {
        gamepad := gamepads[num]
        if joystick := gamepad.joystick; joystick != nil {
            state, err := joystick.Read()
            if err == nil {
                buttons := state.Buttons
                if gamepad.last_buttons != buttons {
                    fmt.Println(state.Buttons)
                    gamepads[num].last_buttons = buttons
                }
            } else { // dropped
                gamepads[num].last_buttons = 0
                gamepads[num].joystick = nil // to avoid reading
                fmt.Println(num, " Lost...")
            }
        }
    }
    
    func checkNewGamepads() {
        for {
            for num, gamepad := range gamepads {
                if gamepad.joystick == nil {
                    js, err := joystick.Open(num)
                    if err == nil {
                        _, err := js.Read()
                        if err == nil {
                            gamepads[num].joystick = js
                            gamepads[num].last_buttons = 0
                            fmt.Println("Opened gamepad ", num)
                            fmt.Println("  Name: ", js.Name())
                            fmt.Println("  # buttons : ", js.ButtonCount())
                            fmt.Println("  # axes : ", js.AxisCount())
                        }
                    }
                }
                // else do nothing since we already have a handle
            }
            time.Sleep(time.Second / 2) // check 2 times a second for new joysticks
        }
    }
    
    func CheckChanged() {
        go checkNewGamepads() // runs until application exits
        for {
            // fmt.Print(".")
            gamepadUpdate(0)
            gamepadUpdate(1)
            time.Sleep(time.Second / 60) // 60 times a second
        }
    }