#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
qq - quick terminal

Usage: %(progname)s [port [speed]]
    (default: /dev/ttyS0 115200)

qq is a quick and dirty terminal application for beagleboard.  I didn't like
cu (no CLOCAL that I could find) or minicom (terminal emulation, keyboard
shortcuts and configuration got in the way of real work)

Except for tilde-specials (similar to rsh, ssh and cu), qq just copies data
between the local terminal and the given tty.   Two tilde specials are
defined:
    <CR>~.: quit
    <CR>~b: send break (useful for "alt-sysrq" actions on beagleboard)


Copyright © 2009 Jeff Epler <jepler@unpythonic.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""

import atexit
import errno
import os
import select
import serial
import sys
import termios

PORT, RATE = "/dev/ttyS0", 115200

if len(sys.argv) > 1 and sys.argv[1] in ("-?", "-h", "--help"):
    print __doc__ % {'progname': sys.argv[0]}
    raise SystemExit, 0

if len(sys.argv) > 1: port = sys.argv[1]
if len(sys.argv) > 2: rate = int(sys.argv[2])

def do_quit(s):
    raise SystemExit, 0

def do_break(s):
    s.sendBreak()

specials = {
    '.': do_quit,
    'b': do_break,
}
    
class InputStateMachine:
    BOL, TILDE, MOL = range(3)
    def __init__(self):
        self.state = self.BOL

    def feed(self, data):
        result = ['']
        for c in data:
            if c == '\r':
                self.state = self.BOL
                self.add(result, c)
            elif self.state == self.BOL and c == '~':
                self.state = self.TILDE
            elif self.state == self.TILDE:
                if c in specials: self.add(result, specials[c])
                elif c == '~': self.add(result, "~")
                else: self.add(result, "~" + c)
                self.state = self.BOL
            else:
                self.state = self.MOL
                self.add(result, c)
        if not result[-1]: del result[-1]
        return result

    def add(self, result, data):
        if isinstance(data, basestring):
            result[0] = result[0] + data
        else:
            result.append(data)
            result.append('')

IFLAG, OFLAG, CFLAG, LFLAG, ISPEED, OSPEED, CC = range(7)

def makeraw(attr):
    attr = attr[:]
    attr[IFLAG] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK |
        termios.ISTRIP | termios.INLCR | termios.IGNCR | termios.ICRNL |
        termios.IXON);
    attr[OFLAG] &= ~termios.OPOST;
    attr[LFLAG] &= ~(termios.ECHO | termios.ECHONL | termios.ICANON |
        termios.ISIG | termios.IEXTEN);
    attr[CFLAG] &= ~(termios.CSIZE | termios.PARENB);
    attr[CFLAG] |= termios.CS8;
    return attr

original_attr = termios.tcgetattr(0)
atexit.register(termios.tcsetattr, 0, termios.TCSADRAIN, original_attr)

attr = makeraw(original_attr)
attr[CC] = ['\0'] * len(attr[6])
attr[CC][termios.VMIN] = 1

termios.tcsetattr(0, termios.TCSADRAIN, attr)

s = serial.Serial(PORT, RATE, timeout=0)
s.setRtsCts(0)
sf = s.fileno()

sm = InputStateMachine()

while 1:
    try:
        r, w, x = select.select([0, s], [], [])
    except select.error, detail:
        if detail.args[0] == errno.EINTR: continue
        raise
    if 0 in r:
        d = os.read(0, 1024)
        for o in sm.feed(d):
            if isinstance(o, basestring):
                s.write(o)
            else:
                o(s)

    if s in r:
        os.write(1, os.read(sf, 1024))
termios.tcflush(0, termios.TCIFLUSH)
