#!/usr/bin/perl -w

use strict;

package TtyTermios;

use POSIX qw(:termios_h :unistd_h);
use Ascii qw(CTRL DEL);
use Data::Dumper;

#use Exporter 5.57 'import';
#our @EXPORT      = ();
#our @EXPORT_OK   = ();
#our %EXPORT_TAGS = ();

our %disp;

########################################
# struct termios c_cc[] control characters

# /usr/include/asm-generic/termbits.h
# missing c_cc bits
sub VSWTC()    {  7 }
sub VREPRINT() { 12 }
sub VDISCARD() { 13 }
sub VWERASE()  { 14 }
sub VLNEXT()   { 15 }
sub VEOL2()    { 16 }

# missing c_iflag bits
sub IUCLC()    { 0001000 }
sub IXANY()    { 0004000 }
sub IMAXBEL()  { 0020000 }
sub IUTF8()    { 0040000 }

# missing c_oflag bits
sub OLCUC()    { 0000002 }
sub ONLCR()    { 0000004 }
sub OCRNL()    { 0000010 }
sub ONOCR()    { 0000020 }
sub ONLRET()   { 0000040 }
sub OFILL()    { 0000100 }
sub OFDEL()    { 0000200 }
sub NLDLY()    { 0000400 }
sub   NL0()    { 0000000 }
sub   NL1()    { 0000400 }
sub CRDLY()    { 0003000 }
sub   CR0()    { 0000000 }
sub   CR1()    { 0001000 }
sub   CR2()    { 0002000 }
sub   CR3()    { 0003000 }
sub TABDLY()   { 0014000 }
sub   TAB0()   { 0000000 }
sub   TAB1()   { 0004000 }
sub   TAB2()   { 0010000 }
sub   TAB3()   { 0014000 }
sub   XTABS()  { 0014000 }
sub BSDLY()    { 0020000 }
sub   BS0()    { 0000000 }
sub   BS1()    { 0020000 }
sub VTDLY()    { 0040000 }
sub   VT0()    { 0000000 }
sub   VT1()    { 0040000 }
sub FFDLY()    { 0100000 }
sub   FF0()    { 0000000 }
sub   FF1()    { 0100000 }

# missing c_cflag bits
sub CBAUD      { 0010017 }
sub EXTA       { B19200 }
sub EXTB       { B38400 }
sub CBAUDEX    { 0010000 }
sub BOTHER     { 0010000 }
sub B57600     { 0o10001 }
sub B115200    { 0o10002 }
sub B230400    { 0o10003 }
sub B460800    { 0o10004 }
sub B500000    { 0o10005 }
sub B576000    { 0o10006 }
sub B921600    { 0o10007 }
sub B1000000   { 0o10010 }
sub B1152000   { 0o10011 }
sub B1500000   { 0o10012 }
sub B2000000   { 0o10013 }
sub B2500000   { 0o10014 }
sub B3000000   { 0o10015 }
sub B3500000   { 0o10016 }
sub B4000000   { 0o10017 }
sub CIBAUD     { 002003600000 }
sub CMSPAR()   { 010000000000 } # mark or space (stick) parity
sub CRTSCTS()  { 020000000000 }

sub MAX_BAUD   { B4000000 }

# missing c_lflag bits
sub XCASE     { 0000004 }
sub ECHOCTL   { 0001000 }
sub ECHOPRT   { 0002000 }
sub ECHOKE    { 0004000 }
sub FLUSHO    { 0010000 }
sub PENDIN    { 0040000 }
sub EXTPROC   { 0200000 }

# tcflow() and TCXONC use these
#sub TCOOFF    { 0 }
#sub TCOON     { 1 }
#sub TCIOFF    { 2 }
#sub TCION     { 3 }

# tcflush() and TCFLSH use these
#sub TCIFLUSH  { 0 }
#sub TCOFLUSH  { 1 }
#sub TCIOFLUSH { 2 }

# tcsetattr uses these
#sub TCSANOW   { 0 }
#sub TCSADRAIN { 1 }
#sub TCSAFLUSH { 2 }

########################################
# same order as in /usr/include/asm-generic/termbits.h
our %flag = (
    "IGNBRK"     => [ "iflag", IGNBRK ],
    "BRKINT"     => [ "iflag", BRKINT ],
    "IGNPAR"     => [ "iflag", IGNPAR ],
    "PARMRK"     => [ "iflag", PARMRK ],
    "INPCK"      => [ "iflag", INPCK ],
    "ISTRIP"     => [ "iflag", ISTRIP ],
    "INLCR"      => [ "iflag", INLCR ],
    "IGNCR"      => [ "iflag", IGNCR ],
    "ICRNL"      => [ "iflag", ICRNL ],
    "IUCLC"      => [ "iflag", IUCLC ],
    "IXON"       => [ "iflag", IXON ],
    "IXANY"      => [ "iflag", IXANY ],
    "IXOFF"      => [ "iflag", IXOFF ],
    "IMAXBEL"    => [ "iflag", IMAXBEL ],
    "IUTF8"      => [ "iflag", IUTF8 ],

    "OPOST"      => [ "oflag", OPOST ],
    "OLCUC"      => [ "oflag", OLCUC ],
    "ONLCR"      => [ "oflag", ONLCR ],
    "OCRNL"      => [ "oflag", OCRNL ],
    "ONOCR"      => [ "oflag", ONOCR ],
    "ONLRET"     => [ "oflag", ONLRET ],
    "OFILL"      => [ "oflag", OFILL ],
    "OFDEL"      => [ "oflag", OFDEL ],
    "NLDLY"      => [ "oflag", NLDLY ],
    "NL0"        => [ "oflag", NL0 ],
    "NL1"        => [ "oflag", NL1 ],
    "CRDLY"      => [ "oflag", CRDLY ],
    "CR0"        => [ "oflag", CR0 ],
    "CR1"        => [ "oflag", CR1 ],
    "CR2"        => [ "oflag", CR2 ],
    "CR3"        => [ "oflag", CR3 ],
    "TABDLY"     => [ "oflag", TABDLY ],
    "TAB0"       => [ "oflag", TAB0 ],
    "TAB1"       => [ "oflag", TAB1 ],
    "TAB2"       => [ "oflag", TAB2 ],
    "TAB3"       => [ "oflag", TAB3 ],
    "XTABS"      => [ "oflag", XTABS ],
    "BSDLY"      => [ "oflag", BSDLY ],
    "BS0"        => [ "oflag", BS0 ],
    "BS1"        => [ "oflag", BS1 ],
    "VTDLY"      => [ "oflag", VTDLY ],
    "VT0"        => [ "oflag", VT0 ],
    "VT1"        => [ "oflag", VT1 ],
    "FFDLY"      => [ "oflag", FFDLY ],
    "FF0"        => [ "oflag", FF0 ],
    "FF1"        => [ "oflag", FF1 ],
#    "ONOEOT"     => [ "oflag", ONOEOT ],

    "CBAUD"      => [ "cflag", CBAUD ],
    "B0"         => [ "cflag", B0 ],
    "B50"        => [ "cflag", B50 ],
    "B75"        => [ "cflag", B75 ],
    "B110"       => [ "cflag", B110 ],
    "B134"       => [ "cflag", B134 ],
    "B150"       => [ "cflag", B150 ],
    "B200"       => [ "cflag", B200 ],
    "B300"       => [ "cflag", B300 ],
    "B600"       => [ "cflag", B600 ],
    "B1200"      => [ "cflag", B1200 ],
    "B1800"      => [ "cflag", B1800 ],
    "B2400"      => [ "cflag", B2400 ],
    "B4800"      => [ "cflag", B4800 ],
    "B9600"      => [ "cflag", B9600 ],
    "B19200"     => [ "cflag", B19200 ],
    "B38400"     => [ "cflag", B38400 ],
    "EXTA"       => [ "cflag", EXTA ],
    "EXTB"       => [ "cflag", EXTB ],
    "CSIZE"      => [ "cflag", CSIZE ],
    "CS5"        => [ "cflag", CS5 ],
    "CS6"        => [ "cflag", CS6 ],
    "CS7"        => [ "cflag", CS7 ],
    "CS8"        => [ "cflag", CS8 ],
    "CSTOPB"     => [ "cflag", CSTOPB ],
    "CREAD"      => [ "cflag", CREAD ],
    "PARENB"     => [ "cflag", PARENB ],
    "PARODD"     => [ "cflag", PARODD ],
    "HUPCL"      => [ "cflag", HUPCL ],
    "CLOCAL"     => [ "cflag", CLOCAL ],
    "CBAUDEX"    => [ "cflag", CBAUDEX ],
    "BOTHER"     => [ "cflag", BOTHER ],
    "B57600"     => [ "cflag", B57600 ],
    "B115200"    => [ "cflag", B115200 ],
    "B230400"    => [ "cflag", B230400 ],
    "B460800"    => [ "cflag", B460800 ],
    "B500000"    => [ "cflag", B500000 ],
    "B576000"    => [ "cflag", B576000 ],
    "B921600"    => [ "cflag", B921600 ],
    "B1000000"   => [ "cflag", B1000000 ],
    "B1152000"   => [ "cflag", B1152000 ],
    "B1500000"   => [ "cflag", B1500000 ],
    "B2000000"   => [ "cflag", B2000000 ],
    "B2500000"   => [ "cflag", B2500000 ],
    "B3000000"   => [ "cflag", B3000000 ],
    "B3500000"   => [ "cflag", B3500000 ],
    "B4000000"   => [ "cflag", B4000000 ],
    "CIBAUD"     => [ "cflag", CIBAUD ],
    "CMSPAR"     => [ "cflag", CMSPAR ],
    "CRTSCTS"    => [ "cflag", CRTSCTS ],
#    "CCTS_OFLOW" => [ "cflag", CCTS_OFLOW ],
#    "CIGNORE"    => [ "cflag", CIGNORE ],
#    "LOBLK"      => [ "cflag", LOBLK ],
#    "MDMBUF"     => [ "cflag", MDMBUF ],
#    "CRTS_IFLOW"       => [ "cflag", CRTS_IFLOW ],

    "ISIG"       => [ "lflag", ISIG ],
    "ICANON"     => [ "lflag", ICANON ],
    "XCASE"      => [ "lflag", XCASE ],
    "ECHO"       => [ "lflag", ECHO ],
    "ECHOE"      => [ "lflag", ECHOE ],
    "ECHOK"      => [ "lflag", ECHOK ],
    "ECHONL"     => [ "lflag", ECHONL ],
    "NOFLSH"     => [ "lflag", NOFLSH ],
    "TOSTOP"     => [ "lflag", TOSTOP ],
    "ECHOCTL"    => [ "lflag", ECHOCTL ],
    "ECHOPRT"    => [ "lflag", ECHOPRT ],
    "ECHOKE"     => [ "lflag", ECHOKE ],
    "FLUSHO"     => [ "lflag", FLUSHO ],
    "PENDIN"     => [ "lflag", PENDIN ],
    "IEXTEN"     => [ "lflag", IEXTEN ],
    "EXTPROC"    => [ "lflag", EXTPROC ],
#    "NOKERNINFO" => [ "lflag", NOKERNINFO ],
#    "ALTWERASE"  => [ "lflag", ALTWERASE ],
#    "DEFECHO"    => [ "lflag", DEFECHO ],
);

########################################
sub BLINE($) {
    my $num = shift;
    my $bval = eval("B$num");
    my @res = ();
    if (substr($bval,0,1) ne "B") { @res = ($bval => $num); }

    @res;
}
my %Bbaud; # as in language c's speed_t bits => integer value
my %Ibaud; # integer value => speed_t bits
BEGIN{
    %Bbaud = (
	BLINE(0),
	BLINE(50),
	BLINE(75),
	BLINE(110),
	BLINE(134),
	BLINE(150),
	BLINE(200),
	BLINE(300),
	BLINE(600),
	BLINE(1200),
	BLINE(1800),
	BLINE(2400),
	BLINE(4800),
	BLINE(9600),
	BLINE(19200),
	BLINE(38400),

	BLINE(57600),
	BLINE(115200),
	BLINE(230400),
	BLINE(460800),
	BLINE(500000),
	BLINE(576000),
	BLINE(921600),
	BLINE(1000000),
	BLINE(1152000),
	BLINE(1500000),
	BLINE(2000000),
	BLINE(2500000),
	BLINE(3000000),
	BLINE(3500000),
	BLINE(4000000),
    );
    for my $k (keys %Bbaud) {
	my $v = $Bbaud{$k};
	$Ibaud{$v} = $k;
    }
}

sub Print(@) {
    my @arr = @_;
    if (@arr == 0) {
	printf "%8s %8s %8s %8s\n", "baudr.", "octal", "hex", "decimal";
	for my $k (sort { $a <=> $b } keys %Ibaud) {
	    my $v = $Ibaud{$k};
	    printf "%8d %#8o %#8x %8d\n", $k, $v, $v, $v;
	}
    } else {
	printf "%8s %8s\n", "speed", "bits";
	for my $r (@arr) {
	    if (@$r == 2) {
		printf "%8d %#8o %#8x %8d\n", $$r[0], $$r[1], $$r[1], $$r[1];
	    } else {
		printf "---\n";
	    }
	}
    }
}

sub Bits2Num($) {
    my $speed = shift; # speed_t, bit pattern to be inserted into a termios

    my $val = $Bbaud{$speed};
    my @arr;
    if (defined($val)) {
	@arr = ( $val, $speed );
    } else { # find the largest value less than the given one
	for my $k (sort { $b <=> $a } keys %Bbaud) {
	    $val = $Bbaud{$k};
	    if ($k < $speed) {
		@arr = ( $val, $k );
		last;
	    }
	}
    }
    $arr[0];
}
sub Num2Bits($) {
    my $num = shift;

    my $Speed = $Ibaud{$num};
    my @arr;
    if (defined($Speed)) {
	@arr = ( $num, $Speed );
    } else { # find the largest value less than the given one
	for my $k (sort { $b <=> $a } keys %Bbaud) {
	    my $val = $Bbaud{$k};
	    if ($val < $num) {
		@arr = ( $val, $k );
		last;
	    }
	}
    }
    $arr[1];
}

########################################

# assumes ascii character set
# setcc() chars given in /usr/include/asm-generic/termbits.h
sub Defchar($) {
    my $ts = shift;
    return undef unless (defined($ts));

    $ts->setcc(&POSIX::VINTR,    CTRL('C'));
    $ts->setcc(&POSIX::VQUIT,    CTRL('\\'));
    $ts->setcc(&POSIX::VERASE,   DEL);
    $ts->setcc(&POSIX::VKILL,    CTRL('U'));
    $ts->setcc(&POSIX::VEOF,     CTRL('D'));
    $ts->setcc(&POSIX::VTIME,    0);
    $ts->setcc(&POSIX::VMIN,     1);
    $ts->setcc(        VSWTC,    _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VSTART,   CTRL('Q'));
    $ts->setcc(&POSIX::VSTOP,    CTRL('S'));
    $ts->setcc(&POSIX::VSUSP,    CTRL('Z'));
    $ts->setcc(&POSIX::VEOL,     _POSIX_VDISABLE);
    $ts->setcc(        VREPRINT, CTRL('R'));
    $ts->setcc(        VDISCARD, CTRL('O'));
    $ts->setcc(        VWERASE,  CTRL('W'));
    $ts->setcc(        VLNEXT,   CTRL('V'));
    $ts->setcc(        VEOL2,    _POSIX_VDISABLE);

    return $ts;
}
sub Rawchar($) {
    my $ts = shift;
    return undef unless (defined($ts));

    $ts->setcc(&POSIX::VINTR,    _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VQUIT,    _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VERASE,   _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VKILL,    _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VEOF,     _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VTIME,    0);
    $ts->setcc(&POSIX::VMIN,     1);
    $ts->setcc(        VSWTC,    _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VSTART,   _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VSTOP,    _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VSUSP,    _POSIX_VDISABLE);
    $ts->setcc(&POSIX::VEOL,     _POSIX_VDISABLE);
    $ts->setcc(        VREPRINT, _POSIX_VDISABLE);
    $ts->setcc(        VDISCARD, _POSIX_VDISABLE);
    $ts->setcc(        VWERASE,  _POSIX_VDISABLE);
    $ts->setcc(        VLNEXT,   _POSIX_VDISABLE);
    $ts->setcc(        VEOL2,    _POSIX_VDISABLE);

    return $ts;
}

########################################

########################################
sub Def($) { # default linux terminal
    my $ts = shift;
    return undef unless (defined($ts));

    my $flag;
    $flag = 0;
    $flag &= ~(IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|IUCLC|IXANY|IMAXBEL);
    $flag |=  (ICRNL|IXON|IXOFF);
    $ts->setiflag($flag);

    $flag = 0;
    $flag &= ~(OLCUC|OCRNL|ONOCR|ONLRET|OFILL|OFDEL|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
    $flag |=  (OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0);
    $ts->setoflag($flag);

    $flag = 0;
    $flag &= ~(PARENB|PARODD|CSIZE|CSTOPB|CLOCAL|CRTSCTS);
    $flag |=  (CS8|HUPCL|CREAD);
    $ts->setcflag($flag);

    $flag = 0;
    $flag &= ~(IEXTEN|ECHONL|NOFLSH|XCASE|TOSTOP|ECHOPRT);
    $flag |=  (ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE);
    $ts->setlflag($flag);

    $ts->setispeed(B9600);
    $ts->setospeed(B9600);

    Defchar($ts);

    $ts;
}
$disp{def} = \&Def;

sub Sane($) { # as in stty sane
    my $ts = shift;
    return undef unless (defined($ts));

    my $flag;
    $flag = $ts->getiflag();
    $flag &= ~(IGNBRK|INLCR|IGNCR|IXOFF|IUCLC|IXANY|IUTF8);
    $flag |=  (BRKINT|ICRNL|IMAXBEL);
    $ts->setiflag($flag);

    $flag = $ts->getoflag();
    $flag &= ~(OLCUC|OCRNL|ONOCR|ONLRET|OFILL|OFDEL|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
    $flag |=  (OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0);
    $ts->setoflag($flag);

    $flag = $ts->getcflag();
#    $flag &= ~(0);
    $flag |=  (CREAD);
    $ts->setcflag($flag);

    $flag = $ts->getlflag();
    $flag &= ~(ECHONL|NOFLSH|XCASE|TOSTOP|ECHOPRT|EXTPROC|FLUSHO);
    $flag |=  (ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE);
    $ts->setlflag($flag);

    Defchar($ts);

    $ts;
}
$disp{sane} = \&Sane;

sub Coocked($) { # as in stty cocked
    my $ts = shift;
    return undef unless (defined($ts));

    my $flag;
    $flag = $ts->getiflag();
#    $flag &= ~(0);
    $flag |=  (BRKINT|IGNPAR|ISTRIP|ICRNL|IXON);
    $ts->setiflag($flag);

    $flag = $ts->getoflag();
#    $flag &= ~(0);
    $flag |=  (OPOST);
    $ts->setoflag($flag);

#    $flag = $ts->getcflag();
#    $flag &= ~(0);
#    $flag |=  (0);
#    $ts->setcflag($flag);

    $flag = $ts->getlflag();
#    $flag &= ~(0);
    $flag |=  (ISIG|ICANON);
    $ts->setlflag($flag);

    Defchar($ts);

    $ts;
}
$disp{cocked} = \&Cocked;

sub Raw($) { # as in stty raw
    my $ts = shift;
    return undef unless (defined($ts));

    my $flag;
    $flag = $ts->getiflag();
    $flag &= ~(IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF|IUCLC|IXANY|IMAXBEL);
#    $flag |=  (0);
    $ts->setiflag($flag);

    $flag = $ts->getoflag();
    $flag &= ~(OPOST);
#    $flag |=  (0);
    $ts->setoflag($flag);

#    $flag = $ts->getcflag();
#    $flag &= ~(0);
#    $flag |=  (0);
#    $ts->setcflag($flag);

    $flag = $ts->getlflag();
    $flag &= ~(ISIG|ICANON|XCASE);
#    $flag |=  (0);
    $ts->setlflag($flag);

    Defchar($ts);

    $ts;
}
$disp{cocked} = \&Cocked;

sub t7e1($) {
    my $ts = shift;
    return undef unless (defined($ts));

    my $flag;
    $flag = $ts->getcflag();
    $flag &= ~(CSIZE|PARODD|CSTOPB);
    $flag |=  (CS7|PARENB);
    $ts->setcflag($flag);

    $ts;
}

sub t8n1($) {
    my $ts = shift;
    return undef unless (defined($ts));

    my $flag;
    $flag = $ts->getcflag();
    $flag &= ~(CSIZE|PARENB|CSTOPB);
    $flag |=  (CS8);
    $ts->setcflag($flag);

    $ts;
}

sub Rawline($) {
    my $ts = shift;
    return undef unless (defined($ts));

    $ts->setiflag(IGNPAR|IGNCR);
    $ts->setoflag(0);
    $ts->setcflag( (ICRNL|CS8|CREAD|CLOCAL) & ~PARENB ); # |HUPCL
    $ts->setlflag(ICANON);

    Rawchar($ts);

    return $ts;
}
$disp{rawline} = \&Rawline;

1;

__END__
