/* * 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); } }