From cb452ba9fc435caf79a8a2f76c3f7938114b17d9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 1 Sep 2022 18:58:35 +0530 Subject: [PATCH] Implement sen text from stdin for the tty backend --- tools/cmd/at/main.go | 13 ++++++++++--- tools/cmd/at/send_text.go | 38 ++++++++++++++++++++++++++++++++++++++ tools/cmd/at/tty_io.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/tools/cmd/at/main.go b/tools/cmd/at/main.go index 5fb9b5908..4172bb4e1 100644 --- a/tools/cmd/at/main.go +++ b/tools/cmd/at/main.go @@ -17,16 +17,18 @@ import ( "github.com/spf13/pflag" "golang.org/x/sys/unix" - "github.com/jamesruan/go-rfc1924/base85" "kitty" "kitty/tools/cli" "kitty/tools/crypto" "kitty/tools/tty" "kitty/tools/tui" + "kitty/tools/tui/loop" "kitty/tools/utils" + + "github.com/jamesruan/go-rfc1924/base85" ) -var ProtocolVersion [3]int = [3]int{0, 20, 0} +var ProtocolVersion [3]int = [3]int{0, 26, 0} func add_bool_set(cmd *cobra.Command, name string, short string, usage string) *bool { if short == "" { @@ -143,7 +145,7 @@ type rc_io_data struct { cmd *cobra.Command rc *utils.RemoteControlCmd serializer serializer_func - send_keypresses bool + on_key_event func(lp *loop.Loop, ke *loop.KeyEvent) error string_response_is_err bool timeout time.Duration multiple_payload_generator func(io_data *rc_io_data) (bool, error) @@ -184,6 +186,11 @@ func get_response(do_io func(io_data *rc_io_data) ([]byte, error), io_data *rc_i return } if len(serialized_response) == 0 { + if io_data.rc.NoResponse { + res := Response{Ok: true} + ans = &res + return + } err = fmt.Errorf("Received empty response from kitty") return } diff --git a/tools/cmd/at/send_text.go b/tools/cmd/at/send_text.go index c4d24f6b9..e04dcb6f1 100644 --- a/tools/cmd/at/send_text.go +++ b/tools/cmd/at/send_text.go @@ -6,11 +6,16 @@ import ( "encoding/base64" "errors" "io" + "kitty/tools/tui/loop" "os" "strings" ) +var end_reading_from_stdin = errors.New("end reading from STDIN") +var waiting_on_stdin = errors.New("wait for key events from STDIN") + func parse_send_text(io_data *rc_io_data, args []string) error { + io_data.rc.NoResponse = true generators := make([]func(io_data *rc_io_data) (bool, error), 0, 1) if len(args) > 0 { @@ -44,6 +49,39 @@ func parse_send_text(io_data *rc_io_data, args []string) error { generators = append(generators, file_gen) } + if options_send_text.stdin { + pending_key_events := make([]string, 0, 1) + + io_data.on_key_event = func(lp *loop.Loop, ke *loop.KeyEvent) error { + ke.Handled = true + if ke.MatchesPressOrRepeat("ctrl+d") { + return end_reading_from_stdin + } + bs := "kitty-key:" + base64.StdEncoding.EncodeToString([]byte(ke.AsCSI())) + pending_key_events = append(pending_key_events, bs) + if ke.Text != "" { + lp.QueueWriteString(ke.Text) + } else if ke.MatchesPressOrRepeat("backspace") { + lp.QueueWriteString("\x08\x1b[P") + } + return nil + } + + key_gen := func(io_data *rc_io_data) (bool, error) { + if len(pending_key_events) > 0 { + payload := io_data.rc.Payload.(send_text_json_type) + payload.Exclude_active = true + io_data.rc.Payload = payload + set_payload_data(io_data, pending_key_events[0]) + pending_key_events = pending_key_events[1:] + return false, nil + } + return false, waiting_on_stdin + } + generators = append(generators, key_gen) + + } + io_data.multiple_payload_generator = func(io_data *rc_io_data) (bool, error) { if len(generators) == 0 { set_payload_data(io_data, "text:") diff --git a/tools/cmd/at/tty_io.go b/tools/cmd/at/tty_io.go index 374eca198..3db985a85 100644 --- a/tools/cmd/at/tty_io.go +++ b/tools/cmd/at/tty_io.go @@ -46,6 +46,9 @@ func do_chunked_io(io_data *rc_io_data) (serialized_response []byte, err error) if state != WAITING_FOR_RESPONSE && state != WAITING_FOR_STREAMING_RESPONSE { return nil } + if io_data.on_key_event != nil { + return nil + } time_since_last_received_data := time.Now().Sub(last_received_data_at) if time_since_last_received_data >= io_data.timeout { return os.ErrDeadlineExceeded @@ -77,6 +80,9 @@ func do_chunked_io(io_data *rc_io_data) (serialized_response []byte, err error) chunk, err := io_data.next_chunk() wants_streaming = io_data.rc.Stream if err != nil { + if err == waiting_on_stdin { + return "", nil + } return "", err } queue_escape_code(chunk) @@ -93,6 +99,9 @@ func do_chunked_io(io_data *rc_io_data) (serialized_response []byte, err error) } chunk, err := io_data.next_chunk() if err != nil { + if err == waiting_on_stdin { + return nil + } return err } queue_escape_code(chunk) @@ -111,6 +120,29 @@ func do_chunked_io(io_data *rc_io_data) (serialized_response []byte, err error) return nil } + lp.OnKeyEvent = func(event *loop.KeyEvent) error { + if io_data.on_key_event == nil { + return nil + } + err := io_data.on_key_event(lp, event) + if err == end_reading_from_stdin { + lp.Quit(0) + return nil + } + if err != nil { + return err + } + chunk, err := io_data.next_chunk() + if err != nil { + if err == waiting_on_stdin { + return nil + } + return err + } + queue_escape_code(chunk) + return err + } + lp.OnRCResponse = func(raw []byte) error { if state == WAITING_FOR_STREAMING_RESPONSE && is_stream_response(raw) { state = SENDING