############################################################################ # # File: getchlib.icn # # Subject: Procedures for getch for UNIX # # Author: Richard L. Goerwitz # # Date: May 2, 2001 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # Version: 1.14 # ############################################################################ # # 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 # ############################################################################ # # Requires: UNIX # ############################################################################ # # Links: itlib # ############################################################################ link itlib global c_cc, current_mode # what mode are we in, raw or cooked? record termio_struct(vintr,vquit,verase,vkill) procedure getse(s) # getse() - like getche, only for strings instead of single chars # # This procedure *must* be used instead of read(&input) if getch # and/or getche are to be used, since these put the current tty # line in raw mode. # # Note that the buffer can be initialized by calling getse with a # string argument. Note also that, as getse now stands, it will # fail if the user backspaces over everything that has been input. # This change does not coincide with its behavior in previous ver- # sions. It can be changed by commenting out the line "if *s < 1 # then fail" below, and uncommenting the line "if *s < 1 then # next." local chr static BS initial { BS := getval("bc") | "\b" if not getval("bs") then { reset_tty() stop("Your terminal can't backspace!") } } /s := "" repeat { case chr := getch() | fail of { "\r"|"\n" : return s c_cc.vkill : { if *s < 1 then next every 1 to *s do writes(BS) s := "" } c_cc.verase : { # if *s < 1 then next writes(BS) & s := s[1:-1] if *s < 1 then fail } default: writes(chr) & s ||:= chr } } end procedure setup_tty() change_tty_mode("setup") return end procedure reset_tty() # Reset (global) mode switch to &null to show we're in cooked mode. current_mode := &null change_tty_mode("reset") return end procedure getch() local chr # If the global variable current_mode is null, then we have to # reset the terminal to raw mode. if /current_mode := 1 then setup_tty() chr := reads(&input) case chr of { c_cc.vintr : reset_tty() & stop() # shouldn't hard code this in c_cc.vquit : reset_tty() & stop() default : return chr } end procedure getche() local chr # If the global variable current_mode is null, then we have to # reset the terminal to raw mode. if /current_mode := 1 then setup_tty() chr := reads(&input) case chr of { c_cc.vintr : reset_tty() & stop() c_cc.vquit : reset_tty() & stop() default : writes(chr) & return chr } end procedure change_tty_mode(switch) # global c_cc (global record containing values for kill, etc. chars) local get_term_params, i static reset_string initial { getval("li") # check to be sure itlib is set up find("unix",map(&features)) | stop("change_tty_mode: These routines must run under UNIX.") get_term_params := open("/bin/stty -g 2>&1","pr") reset_string := !get_term_params close(get_term_params) reset_string ? { # tab upto the fifth field of the output of the stty -g cmd # fields of stty -g seem to be the same as those of the # termio struct, except that the c_line field is missing every 1 to 4 do tab(find(":")+1) c_cc := termio_struct("\x03","\x1C","\x08","\x15") every i := 1 to 3 do { c_cc[i] := char(integer("16r"||tab(find(":")))) move(1) } c_cc[i+1] := char(integer("16r"||tab(0))) } } if switch == "setup" then system("/bin/stty -echo raw") else system("/bin/stty "||reset_string) return end