如何在Golang循环中捕获按键而不需要回车

5

我有一个循环,根据它运行的状态(手动/自动/学习),会发生一些事情。现在我想通过按键盘上相应的字母(“m”代表手动,“a”代表自动,“l”代表学习)来让程序在这些状态之间切换。

为了做到这一点,在循环期间需要能够捕捉按键操作,并相应地改变变量状态。我现在有以下代码,可以捕捉按键操作,但需要按下回车键才能生效:

ch := make(chan string)
go func(ch chan string) {
    reader := bufio.NewReader(os.Stdin)
    for {
        s, _ := reader.ReadString('\n')
        ch <- s
    }
}(ch)

for {
    select {
        case stdin, _ := <-ch:
            fmt.Println("Keys pressed:", stdin)
        default:
            fmt.Println("Working..")
    }
    time.Sleep(time.Second)
}

但是,需要按回车键才能完成操作是不可接受的。

有没有一种非阻塞的方式来捕获普通字母(而不是SIGINT)的按键事件,而不需要之后再按回车键呢?


2
你可以使用 os.Stdin.Read() 从线路上读取字节。你的代码显式地将其包装并绕过它,以便缓冲到下一次用户按回车键。 - Adrian
3个回答

5

在阅读有关os.Stdin.Read()的内容并查找此答案后,我创建了以下代码:

package main

import (
    "fmt"
    "os"
    "time"
    "os/exec"
)

func main() {
    ch := make(chan string)
    go func(ch chan string) {
        // disable input buffering
        exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
        // do not display entered characters on the screen
        exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
        var b []byte = make([]byte, 1)
        for {
            os.Stdin.Read(b)
            ch <- string(b)
        }
    }(ch)

    for {
        select {
            case stdin, _ := <-ch:
                fmt.Println("Keys pressed:", stdin)
            default:
                fmt.Println("Working..")
        }
        time.Sleep(time.Millisecond * 100)
    }
}

这个非常好用。

依赖于外部工具,肯定不能在Windows上运行。但感谢提供代码。 - PePa

1
如果您想在不阻塞进程的情况下检查某个键是否被按下,应该使用类似以下代码:
import (
   ...
   "golang.org/x/sys/windows"
)

var user32_dll  = windows.NewLazyDLL("user32.dll")
var GetKeyState = user32_dll.NewProc("GetKeyState")

func wasESCKeyPressed() bool {
    r1, _, _ := GetKeyState.Call(27) // Call API to get ESC key state.
    return r1 == 65409               // Code for KEY_UP event of ESC key.
}

func loop() {
    for {
       // Do something...
       if wasESCKeyPressed() {
           break
       }
       // Do something...
       time.Sleep(time.Millisecond * 10)
    }
}

0
因为您正在使用ReadString,它期望您提供任何参数,而在您的情况下是return键。根据文档:

ReadString 会读取输入中第一个定界符之前的所有内容,并返回包含该数据和定界符的字符串。

这意味着该方法不会返回,直到您按下return键。
您可以使用常规的Read方法来读取所需的字符。有关参考,请参阅此Stackoverflow问题

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接