diff --git a/README.md b/README.md
index e5af537..52b6dcc 100644
--- a/README.md
+++ b/README.md
@@ -46,40 +46,12 @@
-## Description
-
-Package keyboard can be used to read key presses from the keyboard, while in a
-terminal application. It's crossplatform and keypresses can be combined to check
-for ctrl+c, alt+4, ctrl-shift, alt+ctrl+right, etc.
-
-Works nicely with https://atomicgo.dev/cursor
-
-```go
-
- keyboard.StartListener()
- defer keyboard.StopListener()
-
- for {
- keyInfo, _ := keyboard.GetKey()
- key := keyInfo.Code
-
- if key == keys.CtrlC {
- break
- }
-
- fmt.Println("\r", keyInfo.String())
- }
-
-```
-
-## Install
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ------------------------------------------------------------------------------------------------------------------------------
|
@@ -91,91 +63,115 @@ Works nicely with https://atomicgo.dev/cursor
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ------------------------------------------------------------------------------------------------------------------------------
|
-```go
-// Add this to your imports
-import "atomicgo.dev/keyboard"
-```
+## Description
-## Usage
+Package keyboard can be used to read key presses from the keyboard, while in a
+terminal application. It's crossplatform and keypresses can be combined to check
+for ctrl+c, alt+4, ctrl-shift, alt+ctrl+right, etc. It can also be used to
+simulate (mock) keypresses for CI testing.
-#### func GetKey
+Works nicely with https://atomicgo.dev/cursor
-```go
-func GetKey() (keys.Key, error)
-```
-GetKey blocks until a key is pressed and returns the key info.
+## Simple Usage
-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
+ }
- keyboard.StartListener()
+ fmt.Println("\r" + key.String()) // Print every key press
+ return false, nil // Return false to continue listening
+ })
+
+## Advanced Usage
+
+ // Stop keyboard listener on Escape key press or CTRL+C.
+ // Exit application on "q" key press.
+ // Print every rune key press.
+ // Print every other key press.
+ keyboard.Listen(func(key keys.Key) (stop bool, err error) {
+ switch key.Code {
+ case keys.CtrlC, keys.Escape:
+ return true, nil // Return true to stop listener
+ case keys.RuneKey: // Check if key is a rune key (a, b, c, 1, 2, 3, ...)
+ if key.String() == "q" { // Check if key is "q"
+ fmt.Println("\rQuitting application")
+ os.Exit(0) // Exit application
+ }
+ fmt.Printf("\rYou pressed the rune key: %s\n", key)
+ default:
+ fmt.Printf("\rYou pressed: %s\n", key)
+ }
- for {
- keyInfo, _ := keyboard.GetKey()
- key := keyInfo.Code
+ return false, nil // Return false to continue listening
+ })
- if key == keys.CtrlC {
- break
- }
+## Simulate Key Presses (for mocking in tests)
- fmt.Println("\r", keyInfo.String())
- }
+ 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
- keyboard.StopListener()
+ keyboard.SimulateKeyPress(keys.Escape) // Simulate key press for Escape, which quits the program
+ }()
-#### func StartListener
+ keyboard.Listen(func(key keys.Key) (stop bool, err error) {
+ if key.Code == keys.Escape || key.Code == keys.CtrlC {
+ os.Exit(0) // Exit program on Escape
+ }
-```go
-func StartListener() error
-```
-StartListener starts the keyboard listener
+ fmt.Println("\r" + key.String()) // Print every key press
+ return false, nil // Return false to continue listening
+ })
-Example:
- keyboard.StartListener()
+## Usage
+
+#### func Listen
- for {
- keyInfo, _ := keyboard.GetKey()
- key := keyInfo.Code
+```go
+func Listen(onKeyPress func(key keys.Key) (stop bool, err error)) error
+```
+Listen calls a callback function when a key is pressed.
- if key == keys.CtrlC {
- break
- }
+Simple example:
- fmt.Println("\r", keyInfo.String())
- }
+ 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
+ }
- keyboard.StopListener()
+ fmt.Println("\r" + key.String()) // Print every key press
+ return false, nil // Return false to continue listening
+ })
-#### func StopListener
+#### func SimulateKeyPress
```go
-func StopListener() error
+func SimulateKeyPress(input ...interface{}) error
```
-StopListener stops the keyboard listener
+SimulateKeyPress simulate a key press. It can be used to mock user input and
+test your application.
Example:
- keyboard.StartListener()
-
- for {
- keyInfo, _ := keyboard.GetKey()
- key := keyInfo.Code
-
- if key == keys.CtrlC {
- break
- }
-
- fmt.Println("\r", keyInfo.String())
- }
-
- keyboard.StopListener()
+ 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
+ }()
---
diff --git a/doc.go b/doc.go
index f9cb66a..b9bc706 100644
--- a/doc.go
+++ b/doc.go
@@ -1,24 +1,64 @@
/*
Package keyboard can be used to read key presses from the keyboard, while in a terminal application. It's crossplatform and keypresses can be combined to check for ctrl+c, alt+4, ctrl-shift, alt+ctrl+right, etc.
+It can also be used to simulate (mock) keypresses for CI testing.
Works nicely with https://atomicgo.dev/cursor
-```go
+## Simple Usage
+
+ 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
+ })
- keyboard.StartListener()
- defer keyboard.StopListener()
- for {
- keyInfo, _ := keyboard.GetKey()
- key := keyInfo.Code
+## Advanced Usage
- if key == keys.CtrlC {
- break
+ // Stop keyboard listener on Escape key press or CTRL+C.
+ // Exit application on "q" key press.
+ // Print every rune key press.
+ // Print every other key press.
+ keyboard.Listen(func(key keys.Key) (stop bool, err error) {
+ switch key.Code {
+ case keys.CtrlC, keys.Escape:
+ return true, nil // Return true to stop listener
+ case keys.RuneKey: // Check if key is a rune key (a, b, c, 1, 2, 3, ...)
+ if key.String() == "q" { // Check if key is "q"
+ fmt.Println("\rQuitting application")
+ os.Exit(0) // Exit application
+ }
+ fmt.Printf("\rYou pressed the rune key: %s\n", key)
+ default:
+ fmt.Printf("\rYou pressed: %s\n", key)
}
- fmt.Println("\r", keyInfo.String())
- }
+ return false, nil // Return false to continue listening
+ })
+
+
+## Simulate Key Presses (for mocking in tests)
+
+ 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
+
+ keyboard.SimulateKeyPress(keys.Escape) // Simulate key press for Escape, which quits the program
+ }()
+
+ keyboard.Listen(func(key keys.Key) (stop bool, err error) {
+ if key.Code == keys.Escape || key.Code == keys.CtrlC {
+ os.Exit(0) // Exit program on Escape
+ }
-```
+ fmt.Println("\r" + key.String()) // Print every key press
+ return false, nil // Return false to continue listening
+ })
*/
package keyboard
diff --git a/doc_test.go b/doc_test.go
new file mode 100644
index 0000000..2db133e
--- /dev/null
+++ b/doc_test.go
@@ -0,0 +1,63 @@
+package keyboard
+
+import (
+ "fmt"
+ "os"
+
+ "atomicgo.dev/keyboard/keys"
+)
+
+func ExampleSimple() {
+ 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 ExampleAdvanced() {
+ // Stop keyboard listener on Escape key press or CTRL+C.
+ // Exit application on "q" key press.
+ // Print every rune key press.
+ // Print every other key press.
+ Listen(func(key keys.Key) (stop bool, err error) {
+ switch key.Code {
+ case keys.CtrlC, keys.Escape:
+ return true, nil // Return true to stop listener
+ case keys.RuneKey: // Check if key is a rune key (a, b, c, 1, 2, 3, ...)
+ if key.String() == "q" { // Check if key is "q"
+ fmt.Println("\rQuitting application")
+ os.Exit(0) // Exit application
+ }
+ fmt.Printf("\rYou pressed the rune key: %s\n", key)
+ default:
+ fmt.Printf("\rYou pressed: %s\n", key)
+ }
+
+ return false, nil // Return false to continue listening
+ })
+}
+
+func ExampleMocking() {
+ go func() {
+ SimulateKeyPress("Hello") // Simulate key press for every letter in string
+ SimulateKeyPress(keys.Enter) // Simulate key press for Enter
+ SimulateKeyPress(keys.CtrlShiftRight) // Simulate key press for Ctrl+Shift+Right
+ SimulateKeyPress('x') // Simulate key press for a single rune
+ SimulateKeyPress('x', keys.Down, 'a') // Simulate key presses for multiple inputs
+
+ SimulateKeyPress(keys.Escape) // Simulate key press for Escape, which quits the program
+ }()
+
+ Listen(func(key keys.Key) (stop bool, err error) {
+ if key.Code == keys.Escape || key.Code == keys.CtrlC {
+ os.Exit(0) // Exit program on Escape
+ }
+
+ fmt.Println("\r" + key.String()) // Print every key press
+ return false, nil // Return false to continue listening
+ })
+}
diff --git a/keyboard.go b/keyboard.go
index 6582424..b64d4b4 100644
--- a/keyboard.go
+++ b/keyboard.go
@@ -4,33 +4,18 @@ import (
"fmt"
"os"
- "atomicgo.dev/keyboard/keys"
"github.com/containerd/console"
+
+ "atomicgo.dev/keyboard/keys"
)
var windowsStdin *os.File
var con console.Console
var input = os.Stdin
var inputTTY *os.File
+var mockChannel = make(chan keys.Key)
-// StartListener starts the keyboard listener
-//
-// Example:
-// keyboard.StartListener()
-//
-// for {
-// keyInfo, _ := keyboard.GetKey()
-// key := keyInfo.Code
-//
-// if key == keys.CtrlC {
-// break
-// }
-//
-// fmt.Println("\r", keyInfo.String())
-// }
-//
-// keyboard.StopListener()
-func StartListener() error {
+func startListener() error {
err := initInput()
if err != nil {
return err
@@ -51,24 +36,7 @@ func StartListener() error {
return nil
}
-// StopListener stops the keyboard listener
-//
-// Example:
-// keyboard.StartListener()
-//
-// for {
-// keyInfo, _ := keyboard.GetKey()
-// key := keyInfo.Code
-//
-// if key == keys.CtrlC {
-// break
-// }
-//
-// fmt.Println("\r", keyInfo.String())
-// }
-//
-// keyboard.StopListener()
-func StopListener() error {
+func stopListener() error {
if con != nil {
err := con.Reset()
if err != nil {
@@ -80,23 +48,110 @@ func StopListener() error {
return restoreInput()
}
-// GetKey blocks until a key is pressed and returns the key info.
-//
-// Example:
-// keyboard.StartListener()
-//
-// for {
-// keyInfo, _ := keyboard.GetKey()
-// key := keyInfo.Code
+// Listen calls a callback function when a key is pressed.
//
-// if key == keys.CtrlC {
-// break
-// }
+// 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", keyInfo.String())
-// }
+// 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)
+
+ err := startListener()
+ if err != nil {
+ return err
+ }
+
+ go func() {
+ for {
+ select {
+ case c := <-cancel:
+ if c {
+ return
+ }
+ case keyInfo := <-mockChannel:
+ onKeyPress(keyInfo)
+ }
+ }
+ }()
+
+ for {
+ key, err := getKeyPress(inputTTY)
+ if err != nil {
+ return err
+ }
+
+ stop, err := onKeyPress(key)
+ if err != nil {
+ return err
+ }
+
+ if stop {
+ break
+ }
+ }
+
+ err = stopListener()
+ if err != nil {
+ return err
+ }
+
+ cancel <- true
+
+ return nil
+}
+
+// SimulateKeyPress simulate a key press. It can be used to mock user input and test your application.
//
-// keyboard.StopListener()
-func GetKey() (keys.Key, error) {
- return getKeyPress(inputTTY)
+// 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
}