Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use pty with no command? #153

Open
robinovitch61 opened this issue Jun 25, 2022 · 3 comments
Open

Use pty with no command? #153

robinovitch61 opened this issue Jun 25, 2022 · 3 comments

Comments

@robinovitch61
Copy link

Thanks @creack for the great library and community support!

I have a question that I hope makes sense.

I am using a websocket api that sends stdin messages and receives stdout and stderr output from the command as well as exit codes.

For example, this API is used in a web UI, with the following string of messages sent and received
2022-06-25_09-35-14

This web UI uses xterm.js to provide a terminal-like input ui and to interpret the responses, including ansi escape sequences, into terminal output.

I am building a terminal application that would like to leverage this same API, so a "terminal-in-a-terminal" like thing, where stdin is sent to the API and responses received are rendered in my application.

I would like to use creack/pty as the response interpreter, handling ansi escape sequences and the like, and holding a view of the terminal session that I can read into a string and render to the screen of my application.

So the flow is roughly like:

  • stdin sent to websocket connection
  • response received
  • write response to pty
  • read entire pty to string
  • render string to screen
  • repeat

If I use creack/pty this way, I don't actually have a command to start - it's just a nice box that interprets ansi escape sequences for me and allows me to retrieve the current "string view" of the terminal.

Here is my attempt to get a command-less pty, write to it, and read from it:

package main

import (
	"bytes"
	"fmt"
	"github.com/creack/pty"
	"io"
	"os"
)

func getPtyWithoutCommand() (*os.File, error) {
	// this function just pty.StartWithAttrs with command-specific stuff commented out
	pty, tty, err := pty.Open()
	if err != nil {
		return nil, err
	}
	defer func() { _ = tty.Close() }() // Best effort.

	// if sz != nil {
	// 	if err := Setsize(pty, sz); err != nil {
	// 		_ = pty.Close() // Best effort.
	// 		return nil, err
	// 	}
	// }
	// if c.Stdout == nil {
	// 	c.Stdout = tty
	// }
	// if c.Stderr == nil {
	// 	c.Stderr = tty
	// }
	// if c.Stdin == nil {
	// 	c.Stdin = tty
	// }
	//
	// c.SysProcAttr = attrs
	//
	// if err := c.Start(); err != nil {
	// 	_ = pty.Close() // Best effort.
	// 	return nil, err
	// }
	return pty, err
}

func main() {
	myPty, err := getPtyWithoutCommand()
	if err != nil {
		panic(err)
	}

	_, err = myPty.Write([]byte("test\n"))
	if err != nil {
		panic(err)
	}
	_, err = myPty.Write([]byte{4}) // EOT
	if err != nil {
		panic(err)
	}

	buf := new(bytes.Buffer)
	_, err = io.Copy(buf, myPty)
	if err != nil {
		panic(err)
	}
	fmt.Println(buf.String())
}

I get the following error

❯ go run test.go
panic: write /dev/ptmx: input/output error

goroutine 1 [running]:
main.main()
        test.go:52 +0x19c
exit status 2

Is what I'm trying to do sane at all? Is there a better way to achieve my goal here?

Thank you!
Leo

@lgmugnier
Copy link

lgmugnier commented Jul 21, 2022

Hi! I was tackling a similar problem and creack's pty might be a bit too abstracted for what you want. I just used the golang standard xterm terminal emulator https://pkg.go.dev/golang.org/x/term and a pipe.

Here's a partial snippet as an example of what I mean. Your stdin stream is fed into the stdin_writer and your stdout is written to the writer, don't forget to Flush()!

stdin_reader, stdin_writer := io.Pipe()
reader := bufio.NewReader(stdin_reader)

stdout_writer := bytes.Buffer{}
writer := bufio.NewWriter(&stdout_writer)

rw := bufio.NewReadWriter(reader, writer)
t := term.NewTerminal(rw, prompt)

// constantly be reading lines
go func() {
	for {
		line, err := t.ReadLine()
		if err == io.EOF {
			log.Printf("got EOF")
		}
		if err != nil {
			log.Printf("got err")
		}
		if line == "" {
			continue
		}
		log.Printf("LINE: %s", line)
	}
}()

@robinovitch61
Copy link
Author

Oh wow, thank you so much for this info @lgmugnier ! Somehow never came across the golang xterm emulator, this seems very likely to be exactly what I need...

If you copy/paste the above to my according stack overflow question here, it might reach more folks and I'd be happy to accept it as the answer for some internet points for you

@lgmugnier
Copy link

lgmugnier commented Jul 21, 2022

Somehow never came across the golang xterm emulator, this seems very likely to be exactly what I need...

It took me a while to find it as well

If you copy/paste the above to my according stack overflow question here, it might reach more folks and I'd be happy to accept it as the answer for some internet points for you

already done : )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants