diff --git a/client.go b/client.go index e951c1f..47cf2b8 100644 --- a/client.go +++ b/client.go @@ -1,12 +1,16 @@ package main import ( + "bufio" "code.google.com/p/go.crypto/ssh/terminal" "crypto/rsa" "fmt" "io" "net" "os" + "strings" + "sync" + "unicode" ) type Auth struct { @@ -24,24 +28,34 @@ type ReadWriter struct { } type Client struct { - key *rsa.PrivateKey - host string - port int - nick string - conn net.Conn + key *rsa.PrivateKey + host string + port int + nick string + conn net.Conn + done chan interface{} + mu sync.Mutex + prompt string + line []rune + prev *terminal.State } func (c *Client) dial() error { - conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.host, c.port)) + addr := fmt.Sprintf("%s:%d", c.host, c.port) + c.info("dialing %s", addr) + conn, err := net.Dial("tcp", addr) if err != nil { return fmt.Errorf("client unable to connect: %v", err) } + c.info("connected to %s", addr) c.conn = conn + c.prompt = fmt.Sprintf("%s> ", addr) return nil } func (c *Client) handshake() error { r := &Auth{Nick: c.nick, Key: c.key.PublicKey} + c.info("authenticating as %s", c.nick) return c.sendRequest(r) } @@ -49,7 +63,33 @@ func (c *Client) sendRequest(r request) error { return writeRequest(c.conn, r) } +func (c *Client) info(template string, args ...interface{}) { + c.mu.Lock() + defer c.mu.Unlock() + + c.trunc() + fmt.Print("\033[90m# ") + fmt.Printf(template, args...) + if !strings.HasSuffix(template, "\n") { + fmt.Print("\n") + } + fmt.Printf("\033[0m") + c.renderLine() +} + +func (c *Client) trunc() { + fmt.Print("\r") +} + +func (c *Client) err(template string, args ...interface{}) { + c.mu.Lock() + defer c.mu.Unlock() + + fmt.Printf(template, args...) +} + func (c *Client) run() { + go c.term() if err := c.dial(); err != nil { exit(1, "%v", err) } @@ -57,7 +97,47 @@ func (c *Client) run() { if err := c.handshake(); err != nil { exit(1, "%v", err) } - c.term() + <-c.done + if c.prev != nil { + terminal.Restore(0, c.prev) + } + +} + +func (c *Client) renderLine() { + fmt.Printf("\r%s%s", c.prompt, string(c.line)) +} + +func (c *Client) control(r rune) { + switch r { + case 13: // enter + c.enter() + case 12: // ctrl+l + c.clear() + case 3: // ctrl+c + c.eof() + case 4: // EOF + c.eof() + default: + c.info("control: %v %d %c", r, r, r) + } +} + +func (c *Client) enter() { + fmt.Print("\n") + c.line = make([]rune, 0, 32) + c.renderLine() +} + +func (c *Client) eof() { + fmt.Print("\r") + c.done <- 1 +} + +func (c *Client) clear() { + fmt.Print("\033[2J") // clear the screen + fmt.Print("\033[0;0f") // move to 0, 0 + c.renderLine() } func (c *Client) term() { @@ -65,19 +145,26 @@ func (c *Client) term() { if err != nil { panic(err) } - defer terminal.Restore(0, old) + c.prev = old + defer close(c.done) - r := &ReadWriter{Reader: os.Stdin, Writer: os.Stdout} - term := terminal.NewTerminal(r, "> ") + in := bufio.NewReader(os.Stdin) + for { + r, _, err := in.ReadRune() + switch err { + case io.EOF: + return + case nil: + default: + c.err("error reading rune: %v", err) + } - line, err := term.ReadLine() - switch err { - case io.EOF: - return - case nil: - fmt.Println(line) - default: - exit(1, "error on line read: %v", err) + if unicode.IsGraphic(r) { + c.line = append(c.line, r) + c.renderLine() + } else { + c.control(r) + } } } @@ -96,6 +183,8 @@ func connect() { host: options.host, port: options.port, nick: options.nick, + done: make(chan interface{}), + line: make([]rune, 0, 32), } client.run() } diff --git a/server.go b/server.go index 7a3fd8a..2779cec 100644 --- a/server.go +++ b/server.go @@ -41,6 +41,7 @@ func serve() { if err != nil { exit(1, "couldn't open tcp port for listening: %v", err) } + info_log.Printf("server listening: %s:%d", options.host, options.port) for { conn, err := listener.Accept() if err != nil {