/*
* This software is Licensed under the Lesser GNU Public License, a
* copy of which can be found at
* http://opensource.org/licenses/lgpl-license.php
*/
package bmsi.fsp;
import java.io.*;
import java.util.StringTokenizer;
/** Read SysV terminfo files.
@author Stuart D. Gathman
Copyright (C) 2000 Business Management Systems, Inc.
*/
public class TermInfo {
private final static short MAGIC = 0x011A;
private String[] name;
private boolean[] bools;
private int pad;
private int padbaud;
private short[] nums;
private byte[][] strs;
/** Used for computing delays. */
private int baud = 9600;
private String[] path = {
"/bms/terminfo", "/bms/bin/terminfo", "/usr/lib/terminfo" };
private final static int[] keyranges = {
TI.KEY_BACKSPACE, TI.KEY_UP,
TI.KEY_A1, TI.KEY_C3,
TI.KEY_BACK_TAB, TI.KEY_ACTION,
TI.KEY_BTAB, TI.KEY_BTAB,
TI.KEY_BEG, TI.KEY_SUNDO
};
/** Return an array of indexes for the byte sequences sent by
function keys. */
public static int[] getKeys() {
int n = 0;
for (int i = 0; i < keyranges.length; i += 2)
n += keyranges[i+1] - keyranges[i] + 1;
int[] a = new int[n];
n = 0;
for (int i = 0; i < keyranges.length; i += 2) {
for (int k = keyranges[i]; k <= keyranges[i+1]; ++k)
a[n++] = k;
}
return a;
}
public String[] getNames() { return name; }
/** Return the primary term name.
*/
public String getName() {
return name[0];
}
public String getAltName() {
return name[1];
}
public String getDesc() {
return name[name.length-1];
}
public int getBaud() { return baud; }
/** Set the baud rate used for computing delays.
@see #tputs()
*/
public void setBaud(int baud) { this.baud = baud; }
public boolean getBool(int idx) {
try { return bools[idx]; }
catch (IndexOutOfBoundsException e) { return false; }
}
public short getNum(int idx) {
try { return nums[idx]; }
catch (IndexOutOfBoundsException e) { return (short)-1; }
}
public byte[] getStr(int idx) {
try { return strs[idx]; }
catch (IndexOutOfBoundsException e) { return null; }
}
/** Interpret parameterized strings. A parameterized string is a mini
program with operations introduced by '%'. See the terminfo
documentation. This implementation does not support string
operations, or general printf formatting. FIXME: at least support
%2d
and %02d
.
@param str the terminfo string to interpret
@param val the parameters
@return the output string with parameters substituted
@exception ArrayIndexOutOfBoundsException for missing parameters or
stack underflow or overflow
*/
public static byte[] tparm(final byte[] str,final int[] val) {
final int[] stack = new int[8];
int[] var = null; // variables are seldom used, allocate only when needed
int stkptr = -1;
int v = 0;
/* Since there are no loops, the interpreted string is always at least
as big as the result. */
final byte[] b = new byte[str.length];
int len = 0;
for (int i = 0; i < str.length; ++i) {
int c = str[i];
if (c == '%') {
switch (str[++i]) {
case '\'': // push quoted characters
while (str[++i] != '\'')
stack[++stkptr] = str[i];
break;
case 'i': // increment first two parameters
++val[0];
++val[1];
break;
case 'p': // push parameter
v = str[++i] - '1';
stack[++stkptr] = val[v];
break;
case 'P': // set variable
if (var == null)
var = new int[26];
v = str[++i] - 'a';
var[v] = stack[stkptr--];
break;
case 'g': // get variable
v = str[++i] - 'a';
stack[++stkptr] = (var == null) ? 0 : var[v];
break;
case '{':
int d = str[++i];
v = 0;
while (d != '}') {
v = v * 10 + d - '0';
d = str[++i];
}
stack[++stkptr] = v;
break;
case '|':
v = stack[stkptr--];
stack[stkptr] |= v;
break;
case '&':
v = stack[stkptr--];
stack[stkptr] &= v;
break;
case '^':
v = stack[stkptr--];
stack[stkptr] ^= v;
break;
case '~':
stack[stkptr] ^= ~0;
break;
case '+':
v = stack[stkptr--];
stack[stkptr] += v;
break;
case '-':
v = stack[stkptr--];
stack[stkptr] -= v;
break;
case '*':
v = stack[stkptr--];
stack[stkptr] *= v;
break;
case '/':
v = stack[stkptr--];
stack[stkptr] /= v;
break;
case 'm':
v = stack[stkptr--];
stack[stkptr] %= v;
break;
case '=':
v = stack[stkptr--];
stack[stkptr] = (stack[stkptr] == v) ? 1 : 0;
break;
case '>':
v = stack[stkptr--];
stack[stkptr] = (stack[stkptr] > v) ? 1 : 0;
break;
case '<':
v = stack[stkptr--];
stack[stkptr] = (stack[stkptr] < v) ? 1 : 0;
break;
case '!':
stack[stkptr] = (stack[stkptr] != 0) ? 1 : 0;
break;
case '%':
b[len++] = (byte)'%';
break;
case 'c':
v = stack[stkptr--];
b[len++] = (byte)v;
break;
case 'd':
v = stack[stkptr--];
if (v < 0) {
b[len++] = (byte)'-';
v *= -1;
}
int j = len; // save beginning of digits
do {
b[len++] = (byte)(v % 10 + '0');
v /= 10;
} while (v > 0);
// reverse digits
for (int k = len; --k > j; ++j) {
byte t = b[j];
b[j] = b[k];
b[k] = t;
}
break;
case '?': case ';':
break;
case 't':
if (stack[stkptr--] == 0) {
// skip to matching %e or %;
int lev = 0;
findElse: for (;;) {
if (str[++i] == '%') {
switch (str[++i]) {
case '?': ++lev; break;
case ';': if (--lev < 0) break findElse; break;
case 'e': if (lev == 0) break findElse;
}
}
}
}
break;
case 'e':
// skip to matching %;
int lev = 0;
findEnd: for (;;) {
if (str[++i] == '%') {
switch (str[++i]) {
case '?': ++lev; break;
case ';': if (--lev < 0) break findEnd;
}
}
}
break;
default:
throw new IllegalArgumentException("Unsupported % escape");
}
}
else
b[len++] = (byte)c;
}
if (len == b.length) return b;
byte[] out = new byte[len];
System.arraycopy(b,0,out,0,len);
return out;
}
/** Transmit a terminfo string and interpret timing delays. Delays are
inserted by enclosing a number of milliseconds in $< >
.
Up to 1 digit precision can be used (for tenths of a millisecond).
Following the number with '*' multiplies by the number lines
affected. Delays are implemented by sending an appropriate number
of NULs to the output as computed from the baud rate.
@param out the output stream
@param str the terminfo string
@param lines the number of lines affected
*/
public void tputs(OutputStream out,byte[] str,int lines) throws IOException {
loop: for (int i = 0; i < str.length; ++i) {
int c = str[i];
if (c == '$' && i + 3 < str.length && str[i + 1] == '<') {
int j = i + 2;
int v = 0;
int fix = 0;
int dec = 0;
for (;;) {
c = str[j++];
if (c == '>') break;
if (c == '*')
v *= lines;
else if (c == '.')
dec = 1;
else if (c >= '0' && c <= '9' && fix == 0) {
v = v * 10 + c - '0';
fix += dec;
}
else {
out.write('$');
continue loop; // wasn't a delay escape
}
}
if (baud < padbaud) continue loop; // no padding needed
if (fix < 1)
v *= 10;
i = j - 1;
v = (baud * v + 99999) / 100000;
// create delay by sending pad characters
while (v-- > 0)
out.write(pad);
}
else
out.write(c);
}
}
/** Read a short from an InputStream, LSB first. */
private static short getShort(InputStream in) throws IOException {
int c1 = in.read();
if (c1 < 0) return -1;
int c2 = in.read();
if (c2 < 0) return -1;
return (short)(c1 + (c2 << 8));
}
public void readTinfo(String file) throws IOException {
DataInputStream bi = new DataInputStream(
new BufferedInputStream(new FileInputStream(file))
);
if (getShort(bi) != MAGIC)
throw new IOException("Bad terminfo magic: " + file);
int namlen = getShort(bi);
int boolsize = getShort(bi);
int numsize = getShort(bi);
int strsize = getShort(bi);
int strtblsize = getShort(bi);
byte[] buf = new byte[namlen - 1];
bi.readFully(buf);
bi.readByte(); // skip null terminator
StringTokenizer tok = new StringTokenizer(new String(buf,"ASCII"),"|");
name = new String[tok.countTokens()];
for (int i = 0; i < name.length; ++i)
name[i] = tok.nextToken();
bools = new boolean[boolsize];
for (int i = 0; i < boolsize; ++i)
bools[i] = bi.readByte() != 0;
if ((namlen + boolsize & 1) != 0)
bi.readByte(); // read nums on even byte
nums = new short[numsize];
for (int i = 0; i < numsize; ++i)
nums[i] = getShort(bi);
short[] strbuf = new short[strsize];
for (int i = 0; i < strsize; ++i)
strbuf[i] = getShort(bi);
buf = new byte[strtblsize];
bi.readFully(buf);
strs = new byte[strsize][];
for (int i = 0; i < strsize; ++i) {
int pos = strbuf[i];
if (pos >= 0) {
int end = pos;
while (buf[end] != 0) ++end;
strs[i] = new byte[end - pos];
System.arraycopy(buf,pos,strs[i],0,end - pos);
//System.err.println("stridx = " + i);
}
}
bi.close();
byte[] padc = getStr(TI.PAD_CHAR);
if (padc != null)
pad = padc[0];
else
pad = 0;
padbaud = getNum(TI.PADDING_BAUD_RATE);
}
public void setName(String term) throws IOException {
for (int i = 0; i < path.length; ++i) {
File f = new File(path[i] + File.separator + term.charAt(0),term);
if (f.exists() && f.isFile()) {
readTinfo(f.getPath());
return;
}
System.err.println("Tried " + f);
}
throw new IOException("Term type not found: " + term);
}
}