home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / OL.LZH / PROCS.LZH / GETCHLIB.ICN < prev    next >
Text File  |  1991-07-13  |  9KB  |  331 lines

  1. ############################################################################
  2. #
  3. #    Name:     getchlib.icn
  4. #
  5. #    Title:     Implementation of getch() for Unix (and more)
  6. #
  7. #    Author:     Richard L. Goerwitz
  8. #
  9. #    Date:     June 3, 1991
  10. #
  11. #    Version: 1.14
  12. #
  13. ############################################################################
  14. #
  15. #  Implementing getch() is a much, much more complex affair under Unix
  16. #  than it is under, say, MS-DOS.  This library represents one,
  17. #  solution to the problem - one which can be run as a library, and
  18. #  need not be compiled into the run-time system.  Note that it will
  19. #  not work on all systems.  In particular, certain Suns (with a
  20. #  screwy stty command) and the NeXT 1.0 OS (lacking the -g option for
  21. #  stty) do not run getchlib properly.  See the bugs section below for
  22. #  workarounds.
  23. #
  24. #  Four basic utilities are included here:
  25. #
  26. #    getch()        - waits until a keystroke is available &
  27. #        returns it without displaying it on the screen
  28. #    getche()    - same as getch() only with echo
  29. #    getse(s)    - like getche() only for strings.  The optional
  30. #        argument s gives getse() something to start with.  Use this
  31. #           if, say, you want to read single characters in cbreak mode,
  32. #           but get more input if the character read is the first part
  33. #           of a longer command.  If the user backspaces over everything
  34. #           that has been input, getse() fails.  Returns on \r or \n.
  35. #    reset_tty()    - absolutely vital routine for putting the cur-
  36. #           rent tty line back into cooked mode; call it before exiting
  37. #           or you will find yourself with a locked-up terminal; use it
  38. #           also if you must temporarily restore the terminal to cooked
  39. #           mode
  40. #
  41. #  Note that getse() *must* be used in place of read(&input) if you
  42. #  are planning on using getch() or getche(), since read(&input)
  43. #  assumes a tty with "sane" settings.
  44. #
  45. #  Warning:  The routines below do not do any sophisticated output
  46. #  processing.  As noted above, they also put your tty line in raw
  47. #  mode.  I know, I know:  "Raw is overkill - use cbreak."  But in
  48. #  a world that includes SysV, one must pick a lowest common denomi-
  49. #  nator.  And no, icanon != cbreak.
  50. #
  51. #  BUGS: These routines will not work on systems that do not imple-
  52. #  ment the -g option for the stty command.  The NeXT workstation is
  53. #  an example of such a system.  Tisk, tisk.  If you are on a BSD
  54. #  system where the network configuration makes stty | more impossible,
  55. #  then substitute /usr/5bin/stty (or whatever your system calls the
  56. #  System V stty command) for /bin/stty in this file.  If you have no
  57. #  SysV stty command online, then you can try replacing every instance
  58. #  of "stty -g 2>&1" below with "stty -g 2>&1 1> /dev/tty" or
  59. #  something similar.
  60. #
  61. ############################################################################
  62. #
  63. #  Example program:
  64. #
  65. #      The following program is a simple file viewer.  To run, it
  66. #  needs to be linked with itlib.icn, iscreen.icn, and this file
  67. #  (getchlib.icn).
  68. #
  69. #  procedure main(a)
  70. #
  71. #      # Simple pager/file searcher for Unix systems.  Must be linked
  72. #      # with itlib.icn and iscreen.icn.
  73. #  
  74. #      local intext, c, s
  75. #  
  76. #      # Open input file
  77. #      intext := open(a[1],"r") | {
  78. #      write(&errout,"Can't open input file.")
  79. #      exit(1)
  80. #      }
  81. #  
  82. #      # Initialize screen
  83. #      clear()
  84. #      print_screen(intext) | exit(0)
  85. #  
  86. #      # Prompt & read input
  87. #      repeat {
  88. #      iputs(igoto(getval("cm"), 1, getval("li")))
  89. #      emphasize()
  90. #      writes("More? (y/n or /search):")
  91. #      write_ce(" ")
  92. #      case c := getche() of {
  93. #          "y" : print_screen(intext) | break
  94. #          " " : print_screen(intext) | break
  95. #          "n" : break
  96. #          "q" : break
  97. #          "/" : {
  98. #          iputs(igoto(getval("cm"), 1, getval("li")))
  99. #          emphasize()
  100. #          writes("Enter search string:")
  101. #          write_ce(" ")
  102. #          pattern := GetMoreInput()
  103. #          /pattern | "" == pattern & next
  104. #          # For more complex patterns, use findre() (IPL findre.icn)
  105. #          if not find(pattern, s := !intext) then {
  106. #              iputs(igoto(getval("cm"), 1, getval("li")))
  107. #              emphasize()
  108. #              write_ce("String not found.")
  109. #              break
  110. #          }
  111. #          else print_screen(intext, s) | break
  112. #          }
  113. #      }
  114. #      }
  115. #  
  116. #      reset_tty()
  117. #      write()
  118. #      exit(0)
  119. #
  120. #  end
  121. #  
  122. #  procedure GetMoreInput(c)
  123. #  
  124. #      local input_string
  125. #      static BS
  126. #      initial BS := getval("bc") | "\b"
  127. #  
  128. #      /c := ""
  129. #      if any('\n\r', chr := getch())
  130. #      then return c
  131. #      else {
  132. #      chr == BS & fail
  133. #      writes(chr)
  134. #      input_string := getse(c || chr) | fail
  135. #      if any('\n\r', input_string)
  136. #      then fail else (return input_string)
  137. #      }
  138. #  
  139. #  end
  140. #  
  141. #  procedure print_screen(f,s)
  142. #  
  143. #      if /s then
  144. #      begin := 1
  145. #      # Print top line, if one is supplied
  146. #      else {
  147. #      iputs(igoto(getval("cm"), 1, 1))
  148. #      write_ce(s ? tab(getval("co") | 0))
  149. #      begin := 2
  150. #      }
  151. #  
  152. #      # Fill the screen with lines from f; clear and fail on EOF.
  153. #      every i := begin to getval("li") - 1 do {
  154. #      iputs(igoto(getval("cm"), 1, i))
  155. #      if not write_ce(read(f) ? tab(getval("co") | 0)) then {
  156. #          # Clear remaining lines on the screen.
  157. #          every j := i to getval("li") do {
  158. #          iputs(igoto(getval("cm"), 1, j))
  159. #          iputs(getval("ce"))
  160. #          }
  161. #          iputs(igoto(getval("cm"), 1, i))
  162. #          fail
  163. #      }
  164. #      }
  165. #      return
  166. #  
  167. #  end
  168. #  
  169. #  procedure write_ce(s)
  170. #  
  171. #      normal()
  172. #      iputs(getval("ce")) |
  173. #      writes(repl(" ",getval("co") - *s))
  174. #      writes(s)
  175. #      return
  176. #
  177. #  end
  178. #
  179. ############################################################################
  180. #
  181. #  Requires: UNIX
  182. #
  183. #  Links: itlib.icn (or a related file; if so, change below)
  184. #
  185. ############################################################################
  186.  
  187. link itlib
  188.  
  189. global c_cc, current_mode        # what mode are we in, raw or cooked?
  190. record termio_struct(vintr,vquit,verase,vkill)
  191.  
  192. procedure getse(s)
  193.  
  194.     # getse() - like getche, only for strings instead of single chars
  195.     #
  196.     # This procedure *must* be used instead of read(&input) if getch
  197.     # and/or getche are to be used, since these put the current tty
  198.     # line in raw mode.
  199.     #
  200.     # Note that the buffer can be initialized by calling getse with a
  201.     # string argument.  Note also that, as getse now stands, it will
  202.     # fail if the user backspaces over everything that has been input.
  203.     # This change does not coincide with its behavior in previous ver-
  204.     # sions.  It can be changed by commenting out the line "if *s < 1
  205.     # then fail" below, and uncommenting the line "if *s < 1 then
  206.     # next."
  207.  
  208.     local chr
  209.     static BS
  210.     initial {
  211.     BS := getval("bc") | "\b"
  212.     if not getval("bs") then {
  213.         reset_tty()
  214.         stop("Your terminal can't backspace!")
  215.     }
  216.     }
  217.  
  218.     /s := ""
  219.     repeat {
  220.     case chr := getch() | fail of {
  221.         "\r"|"\n"    : return s
  222.         c_cc.vkill   : {
  223.         if *s < 1 then next
  224.         every 1 to *s do writes(BS)
  225.         s := ""
  226.         }
  227.         c_cc.verase   : {
  228.         # if *s < 1 then next
  229.         writes(BS) & s := s[1:-1]
  230.         if *s < 1 then fail
  231.         }
  232.         default: writes(chr) & s ||:= chr
  233.     }
  234.     }
  235.  
  236. end
  237.  
  238.  
  239.  
  240. procedure setup_tty()
  241.     change_tty_mode("setup")
  242.     return
  243. end
  244.  
  245.  
  246.  
  247. procedure reset_tty()
  248.  
  249.     # Reset (global) mode switch to &null to show we're in cooked mode.
  250.     current_mode := &null
  251.     change_tty_mode("reset")
  252.     return
  253.  
  254. end
  255.  
  256.  
  257.  
  258. procedure getch()
  259.  
  260.     local chr
  261.  
  262.     # If the global variable current_mode is null, then we have to
  263.     # reset the terminal to raw mode.
  264.     if /current_mode := 1 then
  265.     setup_tty()
  266.  
  267.     chr := reads(&input)
  268.     case chr of {
  269.     c_cc.vintr : reset_tty() & stop()  # shouldn't hard code this in
  270.     c_cc.vquit  : reset_tty() & stop()
  271.     default : return chr
  272.     }
  273.  
  274. end
  275.  
  276.  
  277.  
  278. procedure getche()
  279.  
  280.     local chr
  281.  
  282.     # If the global variable current_mode is null, then we have to
  283.     # reset the terminal to raw mode.
  284.     if /current_mode := 1 then
  285.     setup_tty()
  286.  
  287.     chr := reads(&input)
  288.     case chr of {
  289.     c_cc.vintr  : reset_tty() & stop()
  290.     c_cc.vquit  : reset_tty() & stop()
  291.     default : writes(chr) & return chr
  292.     }
  293.  
  294. end
  295.  
  296.  
  297.  
  298. procedure change_tty_mode(switch)
  299.  
  300.     # global c_cc   (global record containing values for kill, etc. chars)
  301.     local get_term_params, i
  302.     static reset_string
  303.     initial {
  304.     getval("li")    # check to be sure itlib is set up
  305.     find("unix",map(&features)) |
  306.         stop("change_tty_mode:  These routines must run under Unix.")
  307.     get_term_params := open("/bin/stty -g 2>&1","pr")
  308.     reset_string := !get_term_params
  309.     close(get_term_params)
  310.     reset_string ? {
  311.         # tab upto the fifth field of the output of the stty -g cmd
  312.         # fields of stty -g seem to be the same as those of the
  313.         # termio struct, except that the c_line field is missing
  314.         every 1 to 4 do tab(find(":")+1)
  315.         c_cc := termio_struct("\x03","\x1C","\x08","\x15")
  316.         every i := 1 to 3 do {
  317.         c_cc[i] := char(integer("16r"||tab(find(":"))))
  318.         move(1)
  319.         }
  320.         c_cc[i+1] := char(integer("16r"||tab(0)))
  321.     }
  322.     }
  323.  
  324.     if switch == "setup"
  325.     then system("/bin/stty -echo raw")
  326.     else system("/bin/stty "||reset_string)
  327.  
  328.     return
  329.  
  330. end
  331.