link getchlib
May 2, 2001; Richard L. Goerwitz
Requires: UNIX
This file is in the public domain.
Implementing getch() is a much, much more complex affair under UNIX than it is under, say, MS-DOS. This library represents one, solution to the problem - one which can be run as a library, and need not be compiled into the run-time system. Note that it will not work on all systems. In particular, certain Suns (with a screwy stty command) and the NeXT 1.0 OS (lacking the -g option for stty) do not run getchlib properly. See the bugs section below for workarounds. Four basic utilities are included here: getch() - waits until a keystroke is available & returns it without displaying it on the screen getche() - same as getch() only with echo getse(s) - like getche() only for strings. The optional argument s gives getse() something to start with. Use this if, say, you want to read single characters in cbreak mode, but get more input if the character read is the first part of a longer command. If the user backspaces over everything that has been input, getse() fails. Returns on \r or \n. reset_tty() - absolutely vital routine for putting the cur- rent tty line back into cooked mode; call it before exiting or you will find yourself with a locked-up terminal; use it also if you must temporarily restore the terminal to cooked mode Note that getse() *must* be used in place of read(&input) if you are planning on using getch() or getche(), since read(&input) assumes a tty with "sane" settings. Warning: The routines below do not do any sophisticated output processing. As noted above, they also put your tty line in raw mode. I know, I know: "Raw is overkill - use cbreak." But in a world that includes SysV, one must pick a lowest common denomi- nator. And no, icanon != cbreak. BUGS: These routines will not work on systems that do not imple- ment the -g option for the stty command. The NeXT workstation is an example of such a system. Tisk, tisk. If you are on a BSD system where the network configuration makes stty | more impossible, then substitute /usr/5bin/stty (or whatever your system calls the System V stty command) for /bin/stty in this file. If you have no SysV stty command online, then you can try replacing every instance of "stty -g 2>&1" below with "stty -g 2>&1 1> /dev/tty" or something similar. ____________________________________________________________ Example program: The following program is a simple file viewer. To run, it needs to be linked with itlib.icn, iscreen.icn, and this file (getchlib.icn). procedure main(a) # Simple pager/file searcher for UNIX systems. Must be linked # with itlib.icn and iscreen.icn. local intext, c, s # Open input file intext := open(a[1],"r") | { write(&errout,"Can't open input file.") exit(1) } # Initialize screen clear() print_screen(intext) | exit(0) # Prompt & read input repeat { iputs(igoto(getval("cm"), 1, getval("li"))) emphasize() writes("More? (y/n or /search):") write_ce(" ") case c := getche() of { "y" : print_screen(intext) | break " " : print_screen(intext) | break "n" : break "q" : break "/" : { iputs(igoto(getval("cm"), 1, getval("li"))) emphasize() writes("Enter search string:") write_ce(" ") pattern := GetMoreInput() /pattern | "" == pattern & next # For more complex patterns, use findre() (IPL findre.icn) if not find(pattern, s := !intext) then { iputs(igoto(getval("cm"), 1, getval("li"))) emphasize() write_ce("String not found.") break } else print_screen(intext, s) | break } } } reset_tty() write() exit(0) end procedure GetMoreInput(c) local input_string static BS initial BS := getval("bc") | "\b" /c := "" if any('\n\r', chr := getch()) then return c else { chr == BS & fail writes(chr) input_string := getse(c || chr) | fail if any('\n\r', input_string) then fail else (return input_string) } end procedure print_screen(f,s) if /s then begin := 1 # Print top line, if one is supplied else { iputs(igoto(getval("cm"), 1, 1)) write_ce(s ? tab(getval("co") | 0)) begin := 2 } # Fill the screen with lines from f; clear and fail on EOF. every i := begin to getval("li") - 1 do { iputs(igoto(getval("cm"), 1, i)) if not write_ce(read(f) ? tab(getval("co") | 0)) then { # Clear remaining lines on the screen. every j := i to getval("li") do { iputs(igoto(getval("cm"), 1, j)) iputs(getval("ce")) } iputs(igoto(getval("cm"), 1, i)) fail } } return end procedure write_ce(s) normal() iputs(getval("ce")) | writes(repl(" ",getval("co") - *s)) writes(s) return end