Merge pull request #7352 from hashicorp/research_7299
use a tty to ui.Ask
This commit is contained in:
commit
6e442e616c
2
go.mod
2
go.mod
|
@ -115,6 +115,7 @@ require (
|
||||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 // indirect
|
github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767 // indirect
|
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767 // indirect
|
||||||
|
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f
|
||||||
github.com/miekg/dns v1.1.1 // indirect
|
github.com/miekg/dns v1.1.1 // indirect
|
||||||
github.com/mitchellh/cli v0.0.0-20170908181043-65fcae5817c8
|
github.com/mitchellh/cli v0.0.0-20170908181043-65fcae5817c8
|
||||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||||
|
@ -175,6 +176,7 @@ require (
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
|
||||||
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5 // indirect
|
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5 // indirect
|
||||||
google.golang.org/api v0.0.0-20180818000503-e21acd801f91
|
google.golang.org/api v0.0.0-20180818000503-e21acd801f91
|
||||||
|
google.golang.org/appengine v1.4.0 // indirect
|
||||||
google.golang.org/grpc v1.17.0 // indirect
|
google.golang.org/grpc v1.17.0 // indirect
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -256,6 +256,8 @@ github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 h1:owMyzMR4QR+jSdl
|
||||||
github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767 h1:Nk2R0tWpD2RdkQ+53zE6kWnSGuhQyDlnOs2MPiqVubE=
|
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767 h1:Nk2R0tWpD2RdkQ+53zE6kWnSGuhQyDlnOs2MPiqVubE=
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f h1:4P7Ul+TAnk92vTeVkXs6VLjmf1EhrYtDRa03PCYY6VM=
|
||||||
|
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
|
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
|
||||||
|
@ -403,6 +405,7 @@ google.golang.org/api v0.0.0-20180818000503-e21acd801f91 h1:MgYYgjaWMS2qQiDwCznf
|
||||||
google.golang.org/api v0.0.0-20180818000503-e21acd801f91/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180818000503-e21acd801f91/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
@ -437,5 +440,6 @@ gopkg.in/vmihailenco/msgpack.v2 v2.9.1 h1:kb0VV7NuIojvRfzwslQeP3yArBqJHW9tOl4t38
|
||||||
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
|
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
31
main.go
31
main.go
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/hashicorp/packer/packer/plugin"
|
"github.com/hashicorp/packer/packer/plugin"
|
||||||
"github.com/hashicorp/packer/packer/tmp"
|
"github.com/hashicorp/packer/packer/tmp"
|
||||||
"github.com/hashicorp/packer/version"
|
"github.com/hashicorp/packer/version"
|
||||||
|
"github.com/mattn/go-tty"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/panicwrap"
|
"github.com/mitchellh/panicwrap"
|
||||||
"github.com/mitchellh/prefixedio"
|
"github.com/mitchellh/prefixedio"
|
||||||
|
@ -144,12 +145,6 @@ func wrappedMain() int {
|
||||||
|
|
||||||
inPlugin := os.Getenv(plugin.MagicCookieKey) == plugin.MagicCookieValue
|
inPlugin := os.Getenv(plugin.MagicCookieKey) == plugin.MagicCookieValue
|
||||||
|
|
||||||
// Prepare stdin for plugin usage by switching it to a pipe
|
|
||||||
// But do not switch to pipe in plugin
|
|
||||||
if !inPlugin {
|
|
||||||
setupStdin()
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := loadConfig()
|
config, err := loadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
|
fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
|
||||||
|
@ -185,13 +180,9 @@ func wrappedMain() int {
|
||||||
|
|
||||||
defer plugin.CleanupClients()
|
defer plugin.CleanupClients()
|
||||||
|
|
||||||
// Setup the UI if we're being machine-readable
|
var ui packer.Ui
|
||||||
var ui packer.Ui = &packer.BasicUi{
|
|
||||||
Reader: os.Stdin,
|
|
||||||
Writer: os.Stdout,
|
|
||||||
ErrorWriter: os.Stdout,
|
|
||||||
}
|
|
||||||
if machineReadable {
|
if machineReadable {
|
||||||
|
// Setup the UI as we're being machine-readable
|
||||||
ui = &packer.MachineReadableUi{
|
ui = &packer.MachineReadableUi{
|
||||||
Writer: os.Stdout,
|
Writer: os.Stdout,
|
||||||
}
|
}
|
||||||
|
@ -202,8 +193,22 @@ func wrappedMain() int {
|
||||||
fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err)
|
fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
var TTY packer.TTY
|
||||||
|
if !inPlugin {
|
||||||
|
var err error
|
||||||
|
TTY, err = tty.Open()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "No tty available: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui = &packer.BasicUi{
|
||||||
|
Reader: os.Stdin,
|
||||||
|
Writer: os.Stdout,
|
||||||
|
ErrorWriter: os.Stdout,
|
||||||
|
TTY: TTY,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the CLI meta
|
// Create the CLI meta
|
||||||
CommandMeta = &command.Meta{
|
CommandMeta = &command.Meta{
|
||||||
CoreConfig: &packer.CoreConfig{
|
CoreConfig: &packer.CoreConfig{
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
type TTY interface {
|
||||||
|
ReadString() (string, error)
|
||||||
|
}
|
16
packer/ui.go
16
packer/ui.go
|
@ -1,7 +1,6 @@
|
||||||
package packer
|
package packer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -81,7 +80,7 @@ type BasicUi struct {
|
||||||
ErrorWriter io.Writer
|
ErrorWriter io.Writer
|
||||||
l sync.Mutex
|
l sync.Mutex
|
||||||
interrupted bool
|
interrupted bool
|
||||||
scanner *bufio.Scanner
|
TTY TTY
|
||||||
StackableProgressBar
|
StackableProgressBar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,8 +208,8 @@ func (rw *BasicUi) Ask(query string) (string, error) {
|
||||||
return "", errors.New("interrupted")
|
return "", errors.New("interrupted")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rw.scanner == nil {
|
if rw.TTY == nil {
|
||||||
rw.scanner = bufio.NewScanner(rw.Reader)
|
return "", errors.New("no available tty")
|
||||||
}
|
}
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||||
|
@ -225,15 +224,12 @@ func (rw *BasicUi) Ask(query string) (string, error) {
|
||||||
|
|
||||||
result := make(chan string, 1)
|
result := make(chan string, 1)
|
||||||
go func() {
|
go func() {
|
||||||
var line string
|
line, err := rw.TTY.ReadString()
|
||||||
if rw.scanner.Scan() {
|
if err != nil {
|
||||||
line = rw.scanner.Text()
|
|
||||||
}
|
|
||||||
if err := rw.scanner.Err(); err != nil {
|
|
||||||
log.Printf("ui: scan err: %s", err)
|
log.Printf("ui: scan err: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result <- line
|
result <- strings.TrimSpace(line)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -34,9 +34,18 @@ func testUi() *BasicUi {
|
||||||
Reader: new(bytes.Buffer),
|
Reader: new(bytes.Buffer),
|
||||||
Writer: new(bytes.Buffer),
|
Writer: new(bytes.Buffer),
|
||||||
ErrorWriter: new(bytes.Buffer),
|
ErrorWriter: new(bytes.Buffer),
|
||||||
|
TTY: new(testTTY),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testTTY struct {
|
||||||
|
say string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *testTTY) ReadString() (string, error) {
|
||||||
|
return tty.say, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestColoredUi(t *testing.T) {
|
func TestColoredUi(t *testing.T) {
|
||||||
bufferUi := testUi()
|
bufferUi := testUi()
|
||||||
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi}
|
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi}
|
||||||
|
@ -217,7 +226,7 @@ func TestBasicUi_Ask(t *testing.T) {
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
// Because of the internal bufio we can't easily reset the input, so create a new one each time
|
// Because of the internal bufio we can't easily reset the input, so create a new one each time
|
||||||
bufferUi := testUi()
|
bufferUi := testUi()
|
||||||
writeReader(bufferUi, testCase.Input)
|
bufferUi.TTY = &testTTY{testCase.Input}
|
||||||
|
|
||||||
actual, err = bufferUi.Ask(testCase.Prompt)
|
actual, err = bufferUi.Ask(testCase.Prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
38
stdin.go
38
stdin.go
|
@ -1,38 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// setupStdin switches out stdin for a pipe. We do this so that we can
|
|
||||||
// close the writer end of the pipe when we receive an interrupt so plugins
|
|
||||||
// blocked on reading from stdin are unblocked.
|
|
||||||
func setupStdin() {
|
|
||||||
// Create the pipe and swap stdin for the reader end
|
|
||||||
r, w, _ := os.Pipe()
|
|
||||||
originalStdin := os.Stdin
|
|
||||||
os.Stdin = r
|
|
||||||
|
|
||||||
// Create a goroutine that copies data from the original stdin
|
|
||||||
// into the writer end of the pipe forever.
|
|
||||||
go func() {
|
|
||||||
defer w.Close()
|
|
||||||
io.Copy(w, originalStdin)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Register a signal handler for interrupt in order to close the
|
|
||||||
// writer end of our pipe so that readers get EOF downstream.
|
|
||||||
ch := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer signal.Stop(ch)
|
|
||||||
defer w.Close()
|
|
||||||
<-ch
|
|
||||||
log.Println("Closing stdin because interrupt received.")
|
|
||||||
}()
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -repotoken lAKAWPzcGsD3A8yBX3BGGtRUdJ6CaGERL
|
|
@ -0,0 +1,4 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- tip
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Yasuhiro Matsumoto
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,49 @@
|
||||||
|
# go-tty
|
||||||
|
|
||||||
|
Simple tty utility
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
tty, err := tty.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tty.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
r, err := tty.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// handle key event
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
if you are on windows and want to display ANSI colors, use <a href="https://github.com/mattn/go-colorable">go-colorable</a>.
|
||||||
|
|
||||||
|
```go
|
||||||
|
tty, err := tty.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tty.Close()
|
||||||
|
|
||||||
|
out := colorable.NewColorable(tty.Output())
|
||||||
|
|
||||||
|
fmt.Fprintln(out, "\x1b[2J")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get github.com/mattn/go-tty
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Yasuhiro Matsumoto (a.k.a mattn)
|
|
@ -0,0 +1,120 @@
|
||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Open() (*TTY, error) {
|
||||||
|
return open()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) Raw() (func() error, error) {
|
||||||
|
return tty.raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) MustRaw() func() error {
|
||||||
|
f, err := tty.raw()
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) Buffered() bool {
|
||||||
|
return tty.buffered()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) ReadRune() (rune, error) {
|
||||||
|
return tty.readRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) Close() error {
|
||||||
|
return tty.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) Size() (int, int, error) {
|
||||||
|
return tty.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) Input() *os.File {
|
||||||
|
return tty.input()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) Output() *os.File {
|
||||||
|
return tty.output()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display types.
|
||||||
|
const (
|
||||||
|
displayNone = iota
|
||||||
|
displayRune
|
||||||
|
displayMask
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tty *TTY) readString(displayType int) (string, error) {
|
||||||
|
rs := []rune{}
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
r, err := tty.readRune()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case 13:
|
||||||
|
break loop
|
||||||
|
case 8, 127:
|
||||||
|
if len(rs) > 0 {
|
||||||
|
rs = rs[:len(rs)-1]
|
||||||
|
if displayType != displayNone {
|
||||||
|
tty.Output().WriteString("\b \b")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if unicode.IsPrint(r) {
|
||||||
|
rs = append(rs, r)
|
||||||
|
switch displayType {
|
||||||
|
case displayRune:
|
||||||
|
tty.Output().WriteString(string(r))
|
||||||
|
case displayMask:
|
||||||
|
tty.Output().WriteString("*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(rs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) ReadString() (string, error) {
|
||||||
|
defer tty.Output().WriteString("\n")
|
||||||
|
return tty.readString(displayRune)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) ReadPassword() (string, error) {
|
||||||
|
defer tty.Output().WriteString("\n")
|
||||||
|
return tty.readString(displayMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) ReadPasswordNoEcho() (string, error) {
|
||||||
|
defer tty.Output().WriteString("\n")
|
||||||
|
return tty.readString(displayNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) ReadPasswordClear() (string, error) {
|
||||||
|
s, err := tty.readString(displayMask)
|
||||||
|
tty.Output().WriteString(
|
||||||
|
strings.Repeat("\b", len(s)) +
|
||||||
|
strings.Repeat(" ", len(s)) +
|
||||||
|
strings.Repeat("\b", len(s)))
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type WINSIZE struct {
|
||||||
|
W int
|
||||||
|
H int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) SIGWINCH() chan WINSIZE {
|
||||||
|
return tty.sigwinch()
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
ioctlWriteTermios = syscall.TIOCSETA
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package tty
|
||||||
|
|
||||||
|
const (
|
||||||
|
ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||||
|
ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
||||||
|
)
|
|
@ -0,0 +1,63 @@
|
||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TTY struct {
|
||||||
|
in *os.File
|
||||||
|
bin *bufio.Reader
|
||||||
|
out *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func open() (*TTY, error) {
|
||||||
|
tty := new(TTY)
|
||||||
|
|
||||||
|
in, err := os.Open("/dev/cons")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tty.in = in
|
||||||
|
tty.bin = bufio.NewReader(in)
|
||||||
|
|
||||||
|
out, err := os.OpenFile("/dev/cons", syscall.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tty.out = out
|
||||||
|
|
||||||
|
return tty, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) buffered() bool {
|
||||||
|
return tty.bin.Buffered() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) readRune() (rune, error) {
|
||||||
|
r, _, err := tty.bin.ReadRune()
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) close() (err error) {
|
||||||
|
if err2 := tty.in.Close(); err2 != nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
if err2 := tty.out.Close(); err2 != nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) size() (int, int, error) {
|
||||||
|
return 80, 24, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) input() *os.File {
|
||||||
|
return tty.in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) output() *os.File {
|
||||||
|
return tty.out
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
// +build !windows
|
||||||
|
// +build !plan9
|
||||||
|
|
||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TTY struct {
|
||||||
|
in *os.File
|
||||||
|
bin *bufio.Reader
|
||||||
|
out *os.File
|
||||||
|
termios syscall.Termios
|
||||||
|
ws chan WINSIZE
|
||||||
|
ss chan os.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func open() (*TTY, error) {
|
||||||
|
tty := new(TTY)
|
||||||
|
|
||||||
|
in, err := os.Open("/dev/tty")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tty.in = in
|
||||||
|
tty.bin = bufio.NewReader(in)
|
||||||
|
|
||||||
|
out, err := os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tty.out = out
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&tty.termios)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newios := tty.termios
|
||||||
|
newios.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF
|
||||||
|
newios.Lflag &^= syscall.ECHO | syscall.ICANON /*| syscall.ISIG*/
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newios)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tty.ws = make(chan WINSIZE)
|
||||||
|
tty.ss = make(chan os.Signal, 1)
|
||||||
|
signal.Notify(tty.ss, syscall.SIGWINCH)
|
||||||
|
go func() {
|
||||||
|
for sig := range tty.ss {
|
||||||
|
switch sig {
|
||||||
|
case syscall.SIGWINCH:
|
||||||
|
if w, h, err := tty.size(); err == nil {
|
||||||
|
tty.ws <- WINSIZE{
|
||||||
|
W: w,
|
||||||
|
H: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return tty, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) buffered() bool {
|
||||||
|
return tty.bin.Buffered() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) readRune() (rune, error) {
|
||||||
|
r, _, err := tty.bin.ReadRune()
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) close() error {
|
||||||
|
close(tty.ss)
|
||||||
|
close(tty.ws)
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&tty.termios)), 0, 0, 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) size() (int, int, error) {
|
||||||
|
var dim [4]uint16
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.out.Fd()), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dim)), 0, 0, 0); err != 0 {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return int(dim[1]), int(dim[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) input() *os.File {
|
||||||
|
return tty.in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) output() *os.File {
|
||||||
|
return tty.out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) raw() (func() error, error) {
|
||||||
|
termios, err := unix.IoctlGetTermios(int(tty.in.Fd()), ioctlReadTermios)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||||
|
termios.Oflag &^= unix.OPOST
|
||||||
|
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||||
|
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||||
|
termios.Cflag |= unix.CS8
|
||||||
|
termios.Cc[unix.VMIN] = 1
|
||||||
|
termios.Cc[unix.VTIME] = 0
|
||||||
|
if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, termios); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() error {
|
||||||
|
if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, termios); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) sigwinch() chan WINSIZE {
|
||||||
|
return tty.ws
|
||||||
|
}
|
|
@ -0,0 +1,324 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rightAltPressed = 1
|
||||||
|
leftAltPressed = 2
|
||||||
|
rightCtrlPressed = 4
|
||||||
|
leftCtrlPressed = 8
|
||||||
|
shiftPressed = 0x0010
|
||||||
|
ctrlPressed = rightCtrlPressed | leftCtrlPressed
|
||||||
|
altPressed = rightAltPressed | leftAltPressed
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
enableProcessedInput = 0x1
|
||||||
|
enableLineInput = 0x2
|
||||||
|
enableEchoInput = 0x4
|
||||||
|
enableWindowInput = 0x8
|
||||||
|
enableMouseInput = 0x10
|
||||||
|
enableInsertMode = 0x20
|
||||||
|
enableQuickEditMode = 0x40
|
||||||
|
enableExtendedFlag = 0x80
|
||||||
|
|
||||||
|
enableProcessedOutput = 1
|
||||||
|
enableWrapAtEolOutput = 2
|
||||||
|
|
||||||
|
keyEvent = 0x1
|
||||||
|
mouseEvent = 0x2
|
||||||
|
windowBufferSizeEvent = 0x4
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
procAllocConsole = kernel32.NewProc("AllocConsole")
|
||||||
|
procSetStdHandle = kernel32.NewProc("SetStdHandle")
|
||||||
|
procGetStdHandle = kernel32.NewProc("GetStdHandle")
|
||||||
|
procSetConsoleScreenBufferSize = kernel32.NewProc("SetConsoleScreenBufferSize")
|
||||||
|
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
|
||||||
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
procWriteConsoleOutputCharacter = kernel32.NewProc("WriteConsoleOutputCharacterW")
|
||||||
|
procWriteConsoleOutputAttribute = kernel32.NewProc("WriteConsoleOutputAttribute")
|
||||||
|
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||||
|
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||||
|
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||||
|
procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||||
|
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||||
|
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
|
||||||
|
procScrollConsoleScreenBuffer = kernel32.NewProc("ScrollConsoleScreenBufferW")
|
||||||
|
)
|
||||||
|
|
||||||
|
type wchar uint16
|
||||||
|
type short int16
|
||||||
|
type dword uint32
|
||||||
|
type word uint16
|
||||||
|
|
||||||
|
type coord struct {
|
||||||
|
x short
|
||||||
|
y short
|
||||||
|
}
|
||||||
|
|
||||||
|
type smallRect struct {
|
||||||
|
left short
|
||||||
|
top short
|
||||||
|
right short
|
||||||
|
bottom short
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleScreenBufferInfo struct {
|
||||||
|
size coord
|
||||||
|
cursorPosition coord
|
||||||
|
attributes word
|
||||||
|
window smallRect
|
||||||
|
maximumWindowSize coord
|
||||||
|
}
|
||||||
|
|
||||||
|
type consoleCursorInfo struct {
|
||||||
|
size dword
|
||||||
|
visible int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type inputRecord struct {
|
||||||
|
eventType word
|
||||||
|
_ [2]byte
|
||||||
|
event [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyEventRecord struct {
|
||||||
|
keyDown int32
|
||||||
|
repeatCount word
|
||||||
|
virtualKeyCode word
|
||||||
|
virtualScanCode word
|
||||||
|
unicodeChar wchar
|
||||||
|
controlKeyState dword
|
||||||
|
}
|
||||||
|
|
||||||
|
type windowBufferSizeRecord struct {
|
||||||
|
size coord
|
||||||
|
}
|
||||||
|
|
||||||
|
type mouseEventRecord struct {
|
||||||
|
mousePos coord
|
||||||
|
buttonState dword
|
||||||
|
controlKeyState dword
|
||||||
|
eventFlags dword
|
||||||
|
}
|
||||||
|
|
||||||
|
type charInfo struct {
|
||||||
|
unicodeChar wchar
|
||||||
|
attributes word
|
||||||
|
}
|
||||||
|
|
||||||
|
type TTY struct {
|
||||||
|
in *os.File
|
||||||
|
out *os.File
|
||||||
|
st uint32
|
||||||
|
rs []rune
|
||||||
|
ws chan WINSIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConsoleInput(fd uintptr, record *inputRecord) (err error) {
|
||||||
|
var w uint32
|
||||||
|
r1, _, err := procReadConsoleInput.Call(fd, uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&w)))
|
||||||
|
if r1 == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func open() (*TTY, error) {
|
||||||
|
tty := new(TTY)
|
||||||
|
if false && isatty.IsTerminal(os.Stdin.Fd()) {
|
||||||
|
tty.in = os.Stdin
|
||||||
|
} else {
|
||||||
|
in, err := syscall.Open("CONIN$", syscall.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tty.in = os.NewFile(uintptr(in), "/dev/tty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
|
tty.out = os.Stdout
|
||||||
|
} else {
|
||||||
|
procAllocConsole.Call()
|
||||||
|
out, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tty.out = os.NewFile(uintptr(out), "/dev/tty")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := tty.in.Fd()
|
||||||
|
var st uint32
|
||||||
|
r1, _, err := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&st)))
|
||||||
|
if r1 == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tty.st = st
|
||||||
|
|
||||||
|
st &^= enableEchoInput
|
||||||
|
st &^= enableInsertMode
|
||||||
|
st &^= enableLineInput
|
||||||
|
st &^= enableMouseInput
|
||||||
|
st &^= enableWindowInput
|
||||||
|
st &^= enableExtendedFlag
|
||||||
|
st &^= enableQuickEditMode
|
||||||
|
st &^= enableProcessedInput
|
||||||
|
|
||||||
|
// ignore error
|
||||||
|
procSetConsoleMode.Call(h, uintptr(st))
|
||||||
|
|
||||||
|
tty.ws = make(chan WINSIZE)
|
||||||
|
|
||||||
|
return tty, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) buffered() bool {
|
||||||
|
return len(tty.rs) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) readRune() (rune, error) {
|
||||||
|
if len(tty.rs) > 0 {
|
||||||
|
r := tty.rs[0]
|
||||||
|
tty.rs = tty.rs[1:]
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
var ir inputRecord
|
||||||
|
err := readConsoleInput(tty.in.Fd(), &ir)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ir.eventType {
|
||||||
|
case windowBufferSizeEvent:
|
||||||
|
wr := (*windowBufferSizeRecord)(unsafe.Pointer(&ir.event))
|
||||||
|
tty.ws <- WINSIZE{
|
||||||
|
W: int(wr.size.x),
|
||||||
|
H: int(wr.size.y),
|
||||||
|
}
|
||||||
|
case keyEvent:
|
||||||
|
kr := (*keyEventRecord)(unsafe.Pointer(&ir.event))
|
||||||
|
if kr.keyDown != 0 {
|
||||||
|
if kr.controlKeyState&altPressed != 0 && kr.unicodeChar > 0 {
|
||||||
|
tty.rs = []rune{rune(kr.unicodeChar)}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
}
|
||||||
|
if kr.unicodeChar > 0 {
|
||||||
|
if kr.controlKeyState&shiftPressed != 0 {
|
||||||
|
switch kr.unicodeChar {
|
||||||
|
case 0x09:
|
||||||
|
tty.rs = []rune{0x5b, 0x5a}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rune(kr.unicodeChar), nil
|
||||||
|
}
|
||||||
|
vk := kr.virtualKeyCode
|
||||||
|
switch vk {
|
||||||
|
case 0x21: // page-up
|
||||||
|
tty.rs = []rune{0x5b, 0x35, 0x7e}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x22: // page-down
|
||||||
|
tty.rs = []rune{0x5b, 0x36, 0x7e}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x23: // end
|
||||||
|
tty.rs = []rune{0x5b, 0x46}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x24: // home
|
||||||
|
tty.rs = []rune{0x5b, 0x48}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x25: // left
|
||||||
|
tty.rs = []rune{0x5b, 0x44}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x26: // up
|
||||||
|
tty.rs = []rune{0x5b, 0x41}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x27: // right
|
||||||
|
tty.rs = []rune{0x5b, 0x43}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x28: // down
|
||||||
|
tty.rs = []rune{0x5b, 0x42}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x2e: // delete
|
||||||
|
tty.rs = []rune{0x5b, 0x33, 0x7e}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x70, 0x71, 0x72, 0x73: // F1,F2,F3,F4
|
||||||
|
tty.rs = []rune{0x5b, 0x4f, rune(vk) - 0x20}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x074, 0x75, 0x76, 0x77: // F5,F6,F7,F8
|
||||||
|
tty.rs = []rune{0x5b, 0x31, rune(vk) - 0x3f, 0x7e}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x78, 0x79: // F9,F10
|
||||||
|
tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x48, 0x7e}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
case 0x7a, 0x7b: // F11,F12
|
||||||
|
tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x47, 0x7e}
|
||||||
|
return rune(0x1b), nil
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) close() error {
|
||||||
|
close(tty.ws)
|
||||||
|
procSetConsoleMode.Call(tty.in.Fd(), uintptr(tty.st))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) size() (int, int, error) {
|
||||||
|
var csbi consoleScreenBufferInfo
|
||||||
|
r1, _, err := procGetConsoleScreenBufferInfo.Call(tty.out.Fd(), uintptr(unsafe.Pointer(&csbi)))
|
||||||
|
if r1 == 0 {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) input() *os.File {
|
||||||
|
return tty.in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) output() *os.File {
|
||||||
|
return tty.out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) raw() (func() error, error) {
|
||||||
|
var st uint32
|
||||||
|
r1, _, err := procGetConsoleMode.Call(tty.in.Fd(), uintptr(unsafe.Pointer(&st)))
|
||||||
|
if r1 == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mode := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||||
|
r1, _, err = procSetConsoleMode.Call(tty.in.Fd(), uintptr(mode))
|
||||||
|
if r1 == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func() error {
|
||||||
|
r1, _, err := procSetConsoleMode.Call(tty.in.Fd(), uintptr(st))
|
||||||
|
if r1 == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tty *TTY) sigwinch() chan WINSIZE {
|
||||||
|
return tty.ws
|
||||||
|
}
|
Loading…
Reference in New Issue