1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
package keyboard
import (
"fmt"
"os"
"github.com/containerd/console"
"atomicgo.dev/keyboard/keys"
)
var windowsStdin *os.File
var con console.Console
var stdin = os.Stdin
var inputTTY *os.File
var mockChannel = make(chan keys.Key)
var mocking = false
func startListener() error {
err := initInput()
if err != nil {
return err
}
if mocking {
return nil
}
if con != nil {
err := con.SetRaw()
if err != nil {
return fmt.Errorf("failed to set raw mode: %w", err)
}
}
inputTTY, err = openInputTTY()
if err != nil {
return err
}
return nil
}
func stopListener() error {
if con != nil {
err := con.Reset()
if err != nil {
return fmt.Errorf("failed to reset console: %w", err)
}
}
return restoreInput()
}
// Listen calls a callback function when a key is pressed.
//
// Simple example:
//
// keyboard.Listen(func(key keys.Key) (stop bool, err error) {
// if key.Code == keys.CtrlC {
// return true, nil // Stop listener by returning true on Ctrl+C
// }
//
// fmt.Println("\r" + key.String()) // Print every key press
// return false, nil // Return false to continue listening
// })
func Listen(onKeyPress func(key keys.Key) (stop bool, err error)) error {
cancel := make(chan bool)
stopRoutine := false
go func() {
for {
select {
case c := <-cancel:
if c {
return
}
case keyInfo := <-mockChannel:
stopRoutine, _ = onKeyPress(keyInfo)
if stopRoutine {
closeInput()
inputTTY.Close()
}
}
}
}()
err := startListener()
if err != nil {
if err.Error() != "provided file is not a console" {
return err
}
}
for !stopRoutine {
key, err := getKeyPress()
if err != nil {
return err
}
// check if returned key is empty
// if reflect.DeepEqual(key, keys.Key{}) {
// return nil
// }
stop, err := onKeyPress(key)
if err != nil {
return err
}
if stop {
closeInput()
inputTTY.Close()
break
}
}
err = stopListener()
if err != nil {
return err
}
cancel <- true
return nil
}
// SimulateKeyPress simulate a key press. It can be used to mock user stdin and test your application.
//
// Example:
//
// go func() {
// keyboard.SimulateKeyPress("Hello") // Simulate key press for every letter in string
// keyboard.SimulateKeyPress(keys.Enter) // Simulate key press for Enter
// keyboard.SimulateKeyPress(keys.CtrlShiftRight) // Simulate key press for Ctrl+Shift+Right
// keyboard.SimulateKeyPress('x') // Simulate key press for a single rune
// keyboard.SimulateKeyPress('x', keys.Down, 'a') // Simulate key presses for multiple inputs
// }()
func SimulateKeyPress(input ...interface{}) error {
for _, key := range input {
// Check if key is a keys.Key
if key, ok := key.(keys.Key); ok {
mockChannel <- key
return nil
}
// Check if key is a rune
if key, ok := key.(rune); ok {
mockChannel <- keys.Key{
Code: keys.RuneKey,
Runes: []rune{key},
}
return nil
}
// Check if key is a string
if key, ok := key.(string); ok {
for _, r := range key {
mockChannel <- keys.Key{
Code: keys.RuneKey,
Runes: []rune{r},
}
}
return nil
}
// Check if key is a KeyCode
if key, ok := key.(keys.KeyCode); ok {
mockChannel <- keys.Key{
Code: key,
}
return nil
}
}
return nil
}
|