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