2 ;; Copyright (c) Neil Van Dyke. See file "info.rkt".
4 (require (for-syntax racket/base
9 (doc (section "Introduction")
13 " package provides a Racket interface for character-cell video
14 display terminals on Unix-like systems -- such as for "
15 (as-index "GNU Screen")
17 (as-index (code "tmux"))
19 (index '("cloud server" "server") "cloud servers")
22 " windows on a workstation desktop, and some older hardware
23 terminals (even the venerable "
24 (as-index "DEC VT100")
25 "). Currently, it implements a subset of features available on most
28 (para "This package could be used to implement a status/management console
29 for a Racket-based server process (perhaps run in GNU Screen or "
31 " on a server machine, to be detached and reattached from SSH
32 sessions), a lightweight user interface for a systems tool, a command-line
33 REPL, a text editor, creative retro uses of old equipment, and, perhaps most
35 ;; (hyperlink "http://en.wikipedia.org/wiki/Rogue_%28computer_game%29"
42 " package does not include any native code (such as from "
43 (as-index (code "terminfo"))
45 (as-index (code "termcap"))
47 (as-index (code "curses"))
49 (as-index (code "ncurses"))
50 ") in the Racket process,
51 such as through the Racket FFI or C extensions, so there is less potential for
52 a problem involving native code to threaten the reliability or security of a
55 " is implemented in pure Racket code except for executing "
57 " for some purposes. Specifically, "
59 " at startup time and shutdown time, to set modes, and (for terminal
60 types that don't seem to support a screen size report control sequence) when
61 getting screen size. Besides security and stability, lower dependence on
62 native code might also simplify porting to host platforms that don't have those
63 native code facilities."))
65 (doc (subsection "Demo")
67 (para "For a demonstration, the following command, run from a terminal, should install the "
69 " package (if not already installed), and run the demo:")
71 (commandline "racket -pm neil/charterm/demo")
73 (para "This demo reports what keys you pressed, while letting you edit a
74 text field, and while displaying a clock. The clock is updated roughly once
75 per second, and is not updated during heavy keyboard input, such as when typing
76 fast. The demo responds to changing terminal sizes, such as when an XTerm is
77 window is resized. It also displays the determined terminal size, and some
81 (racket charterm-display)
82 ". Exit the demo by pressing the "
86 (para "Note: Although this demo includes an editable text field, as proof
87 of concept, the current version of "
89 " does not provide editable text fields as reusable functionality."))
91 (doc (subsection "Simple Example")
93 (para "Here's your first "
98 (UNSYNTAX (code "#lang racket/base"))
100 (require (planet neil/charterm))
103 (charterm-clear-screen)
104 (charterm-cursor 10 5)
105 (charterm-display "Hello, ")
107 (charterm-display "you")
109 (charterm-display ".")
110 (charterm-cursor 1 1)
111 (charterm-display "Press a key...")
112 (let ((key (charterm-read-key)))
113 (charterm-cursor 1 1)
114 (charterm-clear-line)
115 (printf "You pressed: ~S\r\n" key))))
117 (para "Now you're living the dream of the '70s."))
119 (doc (section "Terminal Diversity")
121 (para "Like people, few terminals are exactly the same.")
123 (para "Some key (ha) terms (ha) used by "
127 (itemlist (item (tech "termvar")
128 " --- a string value like from the Unix-like "
130 " environment variable, used to determine a default "
136 (item (tech "protocol")
137 " --- how to control the display, query for information, etc.")
139 (item (tech "keydec")
140 " --- how to decode key encodings of a particular
141 terminal. A keydec is constructed from one or more keysets, can produce "
147 (item (tech "keyset")
148 " --- a specification of encoding some of the keys in a
149 particular terminal, including "
155 (item (tech "keylabel")
156 " --- a string for how a key is likely labeled on a
157 keyboard, such as the DEC VT100 "
159 " key would have a keylabel "
167 (item (tech "keycode")
168 " --- a value produced by a decoded key,
169 such as a character for normal printable keys, like "
173 ", a symbol for some recognized unprintable keys, like "
177 ", or possibly a number for unrecognized keys.")
179 (item (tech "keyinfo")
180 " --- an object that is used like a "
183 bundles together a keycode and a "
185 ", as well as alternatate keycodes and
186 information about how the key was decoded (e.g., from which "
190 (para "These terms are discussed in the following subsections.")
193 " is developed with help of original documentation such as that
194 curated by Paul Williams at "
195 (hyperlink "http://vt100.net/" "vt100.net")
196 ", various commentary found on the Web, observed behavior with
197 modern software terminals like XTerm, various emulators for hardware terminals,
198 and sometimes original hardware terminals. Thanks to Mark Pearrow for
199 contributing a TeleVideo 950, and Paul McCabe for a Wyse S50 WinTerm.")
201 (para "At time of this writing, the author is looking to acquire a DEC
202 VT525, circa 1994, for ongoing testing.")
204 (para "The author welcomes feedback on useful improvements to "
206 "'s support for terminal diversity (no pun). If you have a terminal
207 that is sending an escape sequence not recognized by the demo, you can run the
212 ") argument to see the exact byte sequence:")
214 (commandline "racket -pm- neil/charterm/demo -n")
218 " is used, this will be indicated by the bottom-most scrolling line,
219 rather than saying ``"
220 (tt "To quit, press " (bold "Esc") ".")
221 "'' instead will say ``"
222 (tt "There is no escape from this demo.")
223 "'' You will have to kill the process through some other means."))
225 (doc (subsection "Protocol")
227 (para "The first concept "
229 " has for distinguishing how to communicate with a terminal is what
230 is what is called here "
232 ", which concerns everything except how keyboard keys are decoded.
233 The following protocols are currently implemented:")
237 (item (deftech (code "ansi") " protocol")
238 " --- Terminals approximating ["
240 "], which is most terminals in use today, including software ones
241 like XTerm. This protocol is the emphasis of this package; the other protocols
242 are for unusual situations.")
244 ;; (item (code "dec-vt100")
245 ;; " --- The DEC VT100 and compatibles that could be considered "
247 ;; " except don't have insert-line and delete-line.")
249 (item (deftech (code "wyse-wy50") " protocol")
250 " --- Terminals compatible with the Wyse WY-50. This support is
259 "]. Note that video attributes are not supported, due to the WY-50's
260 model of having video attribute changes occupy character cells; you may wish
261 to run the Wyse terminal in an ANSI or VT100 mode.")
263 (item (deftech (code "televideo-925") " protocol")
264 " --- Terminals compatible with the TeleVideo 925. This support is based on ["
266 "] and behavior of ["
268 "]. Note that video attributes are not supported, due to the 925's
269 model of having video attribute changes occupy character cells; you may wish to
270 run your TeleVideo terminal in ANSI or VT100 mode, if it has one.")
272 (item (deftech (code "ascii") " protocol")
273 " --- Terminals that support ASCII but not much else that we know about.")))
275 (define-syntax (%charterm:protocol-case stx)
276 (syntax-case stx (else)
277 ((_ ERROR-NAME ACTUAL-PROTO (PART0 PART1 PARTn ...) ...)
278 (let loop-clauses ((clause-stxes (syntax->list #'((PART0 PART1 PARTn ...) ...)))
279 (reverse-out-clause-stxes '())
281 (need-protos-hash (make-immutable-hasheq (map (lambda (proto)
286 (if (null? clause-stxes)
287 (let ((missing-protos (hash-keys need-protos-hash)))
288 (if (or else-stx (null? missing-protos))
290 (let ((actual-proto ACTUAL-PROTO))
292 #,@(reverse reverse-out-clause-stxes)
295 (else (error ERROR-NAME
296 "unimplemented for protocol: ~S"
298 (raise-syntax-error '%charterm:protocol-case
299 (format "missing protocols ~S" missing-protos)
301 (let* ((clause-stx (car clause-stxes))
302 (clause-parts (syntax->list clause-stx))
303 (part0-stx (car clause-parts))
304 (part0-e (syntax-e part0-stx)))
305 (if (eq? 'else part0-e)
307 (raise-syntax-error '%charterm:protocol-case
308 "else clause multiply defined"
312 (loop-clauses (cdr clause-stxes)
313 reverse-out-clause-stxes
316 (let loop-protos ((proto-stxes (syntax->list (car (syntax->list clause-stx))))
317 (need-protos-hash need-protos-hash))
318 (if (null? proto-stxes)
319 (loop-clauses (cdr clause-stxes)
320 (cons clause-stx reverse-out-clause-stxes)
323 (let* ((proto-stx (car proto-stxes))
324 (proto-e (syntax-e proto-stx)))
325 (if (symbol? proto-e)
326 (if (hash-has-key? need-protos-hash proto-e)
327 (loop-protos (cdr proto-stxes)
328 (hash-remove need-protos-hash proto-e))
329 (raise-syntax-error '%charterm:protocol-case
330 "protocol unrecognized or multiply defined"
332 (raise-syntax-error '%charterm:protocol-case
333 "invalid protocol symbol"
334 proto-stx))))))))))))
336 (define-syntax (%charterm:unimplemented stx)
341 "unimplemented feature for protocol ~S"
342 (charterm-protocol CT))))))
344 (doc (subsection "Key Encoding")
346 (para "While most video display control, they seem to vary more by key
351 " author was motivated to increase the sophistication of its
352 keyboard handling after a series of revelations on the Sunday of the long
355 " was initially written. The first was discovering that four of the
356 function keys that had been working fine in "
358 " did not work in XTerm. Dave Gilbert somewhat demystified this by
359 pointing out that the original VT100 had only four function keys, which set
360 into motion an unfortunate series of bad decisions by various developers of
361 terminal software to be needlessly incompatible with each other. After
362 Googling, a horrifying 2005 Web post by Phil Gregory ["
364 "], which showed that key encoding among XTerm variants was even
365 worse than one could ever fear. Even if one already knew how much subtleties
366 of old terminals varied (e.g., auto-newline behavior, whether an attribute
367 change consumed a space, etc.), this incompatibility in newer software was
368 surprising. Then, on a hunch, I tried the Linux Console on a Debian Squeeze
369 machine, which surely is ANSI, and found, however, that it generated "
370 (italic "yet different")
371 " byte sequences, for the first "
373 " (not four) function keys. Then I compared all to the ["
375 "] standard, which turns out to be nigh-inscrutable, so which might
376 help explain why everyone became so anti-social.")
379 " now provides the abstractions of "
383 " to deal with this diversity in a maintainable way."))
385 (doc (subsubsection "Keylabel")
389 " is a Racket string for how a key is likely labeled on a particular terminal's keyboard. Different keyboards may have different keylabels for the same "
391 ". For example, a VT100 has a "
397 "), while many other keyboards would label the key "
403 "). The keylabel currently is most useful for documenting and debugging, although it could later be used when giving instructions to the user, such as knowing whether to tell the user the "
413 (doc (subsubsection "Keycode")
417 " is a value representing a key read from a terminal, which can be a Racket character, symbol, or number. Keys corresponding to printable characters have keycodes as Racket characters. Some keys corresponding to special non-printable characters can have keycodes of Racket symbols, such as "
425 ;; TODO: Document here all the symbol keycodes we define.
427 (doc (defproc (charterm-keycode? (x any/c))
429 "Predicate for whether or not "
431 " is a valid keycode."))
432 (provide charterm-keycode?)
433 (define (charterm-keycode? x)
436 (exact-nonnegative-integer? x))
440 (doc (subsubsection "Keyinfo")
448 ", and how it is encoded as bytes. It is represented in Racket as
450 (racket charterm-keyinfo)
453 (define-struct charterm-keyinfo
462 (doc (defproc (charterm-keyinfo? (x any/c))
464 "Predicate for whether or not "
467 (racket charterm-keyinfo)
469 (provide charterm-keyinfo?)
472 (((charterm-keyinfo-keyset-id (ki charterm-keyinfo?)) symbol?)
473 ((charterm-keyinfo-bytelang (ki charterm-keyinfo?)) string?)
474 ((charterm-keyinfo-bytelist (ki charterm-keyinfo?)) (listof byte?))
475 ((charterm-keyinfo-keylabel (ki charterm-keyinfo?)) string?)
476 ((charterm-keyinfo-keycode (ki charterm-keyinfo?)) charterm-keycode?)
477 ((charterm-keyinfo-all-keycodes (ki charterm-keyinfo?)) (listof charterm-keycode?)))
478 (para "Get information from a "
479 (racket charterm-keyinfo)
481 (provide charterm-keyinfo-keyset-id
482 charterm-keyinfo-bytelang
483 charterm-keyinfo-bytelist
484 charterm-keyinfo-keylabel
485 charterm-keyinfo-keycode
486 charterm-keyinfo-all-keycodes)
488 (define %charterm:bytestr-to-byte-hash
501 ,@(for/list ((n (in-range 1 26)))
502 (cons (string #\^ (integer->char (+ 96 n)))
504 ,@(for/list ((n (in-range 1 26)))
505 (cons (string-append "ctrl-"
506 (string (integer->char (+ 96 n))))
508 ,@(for/list ((n (in-range 32 127)))
509 (cons (string (integer->char n))
511 ,@(for/list ((n (in-range 0 255)))
512 (cons (string-append "("
517 (define (%charterm:bytestr->byte bytestr)
518 (hash-ref %charterm:bytestr-to-byte-hash bytestr))
520 (define (%charterm:bytelang->bytelist bytelang secondary?)
521 (let ((bytelist (map %charterm:bytestr->byte
522 (regexp-split #rx" +" bytelang))))
523 (if (and secondary? (not (= 1 (length bytelist))))
524 (error '%charterm:bytelang->bytelist
525 "bytelist for secondary keyset: ~S"
529 (define (%charterm:keycode->keylabel keycode)
530 (cond ((not keycode) #f)
531 ((symbol? keycode) (string-titlecase (symbol->string keycode)))
532 ((char? keycode) (string keycode))
533 ((number? keycode) (number->string keycode))
534 (else (error '%charterm:keycode->keylabel
535 "invalid keycode: ~S"
538 (define (%charterm:keylang->keyinfo keyset-id keylang secondary?)
539 (apply (lambda (bytelang . args)
540 (let-values (((bytelist)
541 (%charterm:bytelang->bytelist bytelang secondary?))
542 ((keylabel keycode all-keycodes)
543 (let ((keylabel (car args)))
544 (if (or (string? keylabel)
549 (let ((keycode (car args)))
550 (values (%charterm:keycode->keylabel keycode)
553 (make-charterm-keyinfo keyset-id
561 (doc (subsubsection "Keyset")
565 " is a specification of keys on a particular keyboard, including their "
567 ", encoding as bytes, and primary and alternate "
568 (tech #:key "keycode" "keycodes")
571 ;; TODO: Expose ability to construct keysets, once it's finalized.
572 (para "The means of constructing a keyset is currently internal to this package."))
574 (define-struct charterm-keyset
575 (id primary-keyinfos secondary-keyinfos)
578 (doc (defproc (charterm-keyset? (x any/c))
580 (para "Predicate for whether or not "
583 (provide charterm-keyset?)
585 (doc (defproc (charterm-keyset-id (ks charterm-keyset?))
587 (para "Get a symbol identifying the keyset."))
588 (provide charterm-keyset-id)
590 ;; (define (%charterm:keyinfos? x)
591 ;; (for/and ((x (in-list x)))
592 ;; (charterm-keyinfo? x)))
594 ;; (define (%charterm:assert-keyinfos keyinfos)
595 ;; (or (%charterm:keyinfos? keyinfos)
596 ;; (error '%charterm:assert-keyinfos
597 ;; "assertion failed: ~S"
600 (define (make-charterm-keyset-from-keylangs keyset-id
602 (secondary-keylangs '()))
603 (let ((primary-keyinfos (map (lambda (keylang)
604 (%charterm:keylang->keyinfo keyset-id keylang #f))
606 (secondary-keyinfos (map (lambda (keylang)
607 (%charterm:keylang->keyinfo keyset-id keylang #t))
608 secondary-keylangs)))
609 ;; (%charterm:assert-keyinfos primary-keyinfos)
610 ;; (%charterm:assert-keyinfos secondary-keyinfos)
611 (charterm-keyset keyset-id
613 secondary-keyinfos)))
615 (doc (defthing charterm-ascii-keyset charterm-keyset?
616 (para "From the old ["
618 "] standard. When defining a "
620 ", this is good to have as a final keyset, after the others.")))
621 (define charterm-ascii-keyset
623 `(("(0)" "NUL" nul null)
624 ("(1)" "Ctrl-A" ctrl-a start-of-heading soh)
625 ("(2)" "Ctrl-B" ctrl-b start-of-text stx)
626 ("(3)" "Ctrl-C" ctrl-c end-of-text etx)
627 ("(4)" "Ctrl-D" ctrl-d end-of-transmission eot)
628 ("(5)" "Ctrl-E" ctrl-e enquiry enq)
629 ("(6)" "Ctrl-F" ctrl-f acknowledge ack)
630 ("(7)" "Ctrl-G" ctrl-g bell bel)
631 ("(8)" "Backspace" backspace ctrl-h bs)
632 ("(9)" "Tab" tab ctrl-i horizontal-tab ht)
633 ("(10)" "Linefeed" linefeed ctrl-j line-feed lf)
634 ("(11)" "Ctrl-K" ctrl-k vertical-tab vt)
635 ("(12)" "Ctrl-L" ctrl-l formfeed form-feed ff)
636 ("(13)" "Return" return ctrl-m carriage-return cr)
637 ("(14)" "Ctrl-N" ctrl-n shift-out so)
638 ("(15)" "Ctrl-O" ctrl-o shift-in si)
639 ("(16)" "Ctrl-P" ctrl-p data-link-escape dle)
640 ("(17)" "Ctrl-Q" ctrl-q device-control-1 dc1)
641 ("(18)" "Ctrl-R" ctrl-r device-control-2 dc2)
642 ("(19)" "Ctrl-S" ctrl-s device-control-3 dc3)
643 ("(20)" "Ctrl-T" ctrl-t device-control-4 dc4)
644 ("(21)" "Ctrl-U" ctrl-u negative-acknowledgement nak)
645 ("(22)" "Ctrl-V" ctrl-v synchronous-idle syn)
646 ("(23)" "Ctrl-W" ctrl-w end-of-transmission-block etb)
647 ("(24)" "Ctrl-X" ctrl-x cancel can)
648 ("(25)" "Ctrl-Y" ctrl-y end-of-medium em)
649 ("(26)" "Ctrl-Z" ctrl-z substitute sub)
650 ("(27)" "Esc" escape esc)
651 ("(28)" "FS" file-separator fs)
652 ("(29)" "GS" group-separator gs)
653 ("(30)" "RS" record-separtor rs)
654 ("(31)" "US" unit-separator us)
655 ("(32)" "Space" #\space space sp)
656 ("(127)" "Delete" delete del)
657 ,@(for/list ((n (in-range 32 127)))
658 (let ((c (integer->char n)))
659 (list (string-append "(" (number->string n) ")")
662 (make-charterm-keyset-from-keylangs
667 (doc (defthing charterm-dec-vt100-keyset charterm-keyset?
668 (para "From the DEC VT100. This currently defines the four function
669 keys (labeled on the keyboard, "
677 ", and the arrow keys. ["
681 "] were used as references.")))
682 (provide charterm-dec-vt100-keyset)
683 (define charterm-dec-vt100-keyset
684 (make-charterm-keyset-from-keylangs
686 '(("esc O P" "PF1" f1)
696 ;; Note: PowerTerm does not map PC key F1 like VT100, etc. It maps all
697 ;; the PC F keys to other sequences that are like the VT220.
700 (doc (defthing charterm-dec-vt220-keyset charterm-keyset?
701 (para "From the DEC VT220. This currently defines function keys "
706 (provide charterm-dec-vt220-keyset)
707 (define charterm-dec-vt220-keyset
708 (make-charterm-keyset-from-keylangs
732 ;; TODO: Make the keylang expand to both "esc [" and "(155)" CSI or
758 (doc (defthing charterm-screen-keyset charterm-keyset?
760 (hyperlink "http://en.wikipedia.org/wiki/GNU_Screen"
762 " terminal multiplexer, according to ["
765 (hyperlink "http://en.wikipedia.org/wiki/Tmux"
768 (provide charterm-screen-keyset)
769 (define charterm-screen-keyset
770 (make-charterm-keyset-from-keylangs
785 ("esc [ 3 ~" "Delete" delete del)
786 ("esc [ 7 ~" "Home" home)
787 ("esc [ 8 ~" "End" end)
789 ("(127)" "Backspace" backspace)
792 (doc (defthing charterm-linux-keyset charterm-keyset?
793 (para "From the Linux console. Currently defines function keys "
797 " only, since the rest will be inherited from other keysets.")))
798 (provide charterm-linux-keyset)
799 (define charterm-linux-keyset
800 (make-charterm-keyset-from-keylangs
808 (doc (defthing charterm-xterm-x11r6-keyset charterm-keyset?
809 (para "From the XTerm in X11R6, according to ["
812 (provide charterm-xterm-x11r6-keyset)
813 (define charterm-xterm-x11r6-keyset
814 (make-charterm-keyset-from-keylangs
828 ("esc [ 1 1 ; 2 ~" f13)
829 ("esc [ 1 2 ; 2 ~" f14)
830 ("esc [ 1 3 ; 2 ~" f15)
831 ("esc [ 1 4 ; 2 ~" f16)
832 ("esc [ 1 5 ; 2 ~" f17)
833 ("esc [ 1 7 ; 2 ~" f18)
834 ("esc [ 1 8 ; 2 ~" f19)
835 ("esc [ 1 9 ; 2 ~" f20)
836 ("esc [ 2 0 ; 2 ~" f21)
837 ("esc [ 2 1 ; 2 ~" f22)
838 ("esc [ 2 3 ; 2 ~" f23)
839 ("esc [ 2 4 ; 2 ~" f24)
840 ("esc [ 1 1 ; 5 ~" f25)
841 ("esc [ 1 2 ; 5 ~" f26)
842 ("esc [ 1 3 ; 5 ~" f27)
843 ("esc [ 1 4 ; 5 ~" f28)
844 ("esc [ 1 5 ; 5 ~" f29)
845 ("esc [ 1 7 ; 5 ~" f30)
846 ("esc [ 1 8 ; 5 ~" f31)
847 ("esc [ 1 9 ; 5 ~" f32)
848 ("esc [ 2 0 ; 5 ~" f33)
849 ("esc [ 2 1 ; 5 ~" f34)
850 ("esc [ 2 3 ; 5 ~" f35)
851 ("esc [ 2 4 ; 5 ~" f36)
852 ("esc [ 1 1 ; 6 ~" f37)
853 ("esc [ 1 2 ; 6 ~" f38)
854 ("esc [ 1 3 ; 6 ~" f39)
855 ("esc [ 1 4 ; 6 ~" f40)
856 ("esc [ 1 5 ; 6 ~" f41)
857 ("esc [ 1 7 ; 6 ~" f42)
858 ("esc [ 1 8 ; 6 ~" f43)
859 ("esc [ 1 9 ; 6 ~" f44)
860 ("esc [ 2 0 ; 6 ~" f45)
861 ("esc [ 2 1 ; 6 ~" f46)
862 ("esc [ 2 3 ; 6 ~" f47)
863 ("esc [ 2 4 ; 6 ~" f48))))
865 (doc (defthing charterm-xterm-xfree86-keyset charterm-keyset?
866 (para "From the XFree86 XTerm, according to ["
869 (provide charterm-xterm-xfree86-keyset)
870 (define charterm-xterm-xfree86-keyset
871 (make-charterm-keyset-from-keylangs
889 ("esc [ 1 5 ; 2 ~" f17)
890 ("esc [ 1 7 ; 2 ~" f18)
891 ("esc [ 1 8 ; 2 ~" f19)
892 ("esc [ 1 9 ; 2 ~" f20)
893 ("esc [ 2 0 ; 2 ~" f21)
894 ("esc [ 2 1 ; 2 ~" f22)
895 ("esc [ 2 3 ; 2 ~" f23)
896 ("esc [ 2 4 ; 2 ~" f24)
901 ("esc [ 1 5 ; 5 ~" f29)
902 ("esc [ 1 7 ; 5 ~" f30)
903 ("esc [ 1 8 ; 5 ~" f31)
904 ("esc [ 1 9 ; 5 ~" f32)
905 ("esc [ 2 0 ; 5 ~" f33)
906 ("esc [ 2 1 ; 5 ~" f34)
907 ("esc [ 2 3 ; 5 ~" f35)
908 ("esc [ 2 4 ; 5 ~" f36)
913 ("esc [ 1 5 ; 6 ~" f41)
914 ("esc [ 1 7 ; 6 ~" f42)
915 ("esc [ 1 8 ; 6 ~" f43)
916 ("esc [ 1 9 ; 6 ~" f44)
917 ("esc [ 2 0 ; 6 ~" f45)
918 ("esc [ 2 1 ; 6 ~" f46)
919 ("esc [ 2 3 ; 6 ~" f47)
920 ("esc [ 2 4 ; 6 ~" f48))))
922 (doc (defthing charterm-xterm-new-keyset charterm-keyset?
923 (para "From the current "
925 ", often called simply "
927 ", as developed by Thomas E. Dickey, and documented in ["
928 (tech "XTerm-ctlseqs")
929 "]. Several also came from decompiling a "
931 " entry. Thanks to Dickey for his emailed help.")))
932 (provide charterm-xterm-new-keyset)
933 (define charterm-xterm-new-keyset
934 (make-charterm-keyset-from-keylangs
948 ;; The following came from decompiling an xterm terminfo
969 ("esc O I" tab kp-tab)
970 ("esc O M" "Enter" return enter kp-return kp-enter)
971 ("esc O P" "PF1" f1 kp-f1)
972 ("esc O Q" "PF2" f2 kp-f2)
973 ("esc O R" "PF3" f3 kp-f3)
974 ("esc O S" "PF4" f4 kp-f4)
975 ("esc [ 3 ~" "Delete" delete del kp-delete)
976 ("esc [ 2 ~" "Insert" insert ins kp-insert)
977 ("esc O F" "End" end kp-end)
978 ("esc [ B" "Down" down kp-down)
979 ("esc [ 6 ~" "PgDn" pgdn kp-pgdn)
980 ("esc [ D" "Left" left kp-left)
981 ("esc [ E" "Begin" begin kp-begin)
982 ("esc [ C" "Right" right kp-right)
983 ("esc O H" "Home" home kp-home)
984 ("esc [ A" "Up" up kp-up)
985 ("esc [ 5 ~" "PgUp" pgup kp-pgup)
987 ("esc [ 1 1 ~" "F1" f1)
988 ("esc [ 1 2 ~" "F2" f2)
989 ("esc [ 1 3 ~" "F3" f3)
990 ("esc [ 1 4 ~" "F4" f4)
992 ;; TODO: continue working on this from dickey's xterm control sequences doc
996 (doc (defthing charterm-rxvt-keyset charterm-keyset?
998 (hyperlink "http://en.wikipedia.org/wiki/Rxvt"
1000 " terminal emulator. These come from ["
1003 currently define function keys "
1008 (define charterm-rxvt-keyset
1009 (make-charterm-keyset-from-keylangs
1011 '(("esc [ 1 1 ~" f1)
1021 ("esc [ 2 3 ~" shift-f1 f11) ;; TODO: These shift- and ctrl- are actually from termvar xterm in an rxvt
1022 ("esc [ 2 4 ~" shift-f2 f12)
1023 ("esc [ 2 5 ~" shift-f3 f13)
1024 ("esc [ 2 6 ~" shift-f4 f14)
1025 ("esc [ 2 8 ~" shift-f5 f15)
1026 ("esc [ 2 9 ~" shift-f6 f16)
1027 ("esc [ 3 1 ~" shift-f7 f17)
1028 ("esc [ 3 2 ~" shift-f8 f18)
1029 ("esc [ 3 3 ~" shift-f9 f19)
1030 ("esc [ 3 4 ~" shift-f10 f20)
1031 ("esc [ 2 3 $" shift-f11 f21)
1032 ("esc [ 2 4 $" shift-f12 f22)
1033 ("esc [ 1 1 ^" ctrl-f1 f23)
1034 ("esc [ 1 2 ^" ctrl-f2 f24)
1035 ("esc [ 1 3 ^" ctrl-f3 f25)
1036 ("esc [ 1 4 ^" ctrl-f4 f26)
1037 ("esc [ 1 5 ^" ctrl-f5 f27)
1038 ("esc [ 1 7 ^" ctrl-f6 f28)
1039 ("esc [ 1 8 ^" ctrl-f7 f29)
1040 ("esc [ 1 9 ^" ctrl-f8 f30)
1041 ("esc [ 2 0 ^" ctrl-f9 f31)
1042 ("esc [ 2 1 ^" ctrl-f10 f32)
1043 ("esc [ 2 3 ^" ctrl-f11 f33)
1044 ("esc [ 2 4 ^" ctrl-f12 f34)
1055 ("(127)" "Backspace" backspace) ; Override one from "ascii" keyset.
1056 ;; TODO: actually, these arrow keys were observed in rxvt with termvar xterm. which keyset should they be in?
1058 ("esc [ B" "Down" down)
1059 ("esc [ C" "Right" right)
1060 ("esc [ D" "Left" left)
1061 ("esc [ 5 ~" "PgUp" pgup page-up)
1062 ("esc [ 6 ~" "PgDn" pgdn page-down)
1063 ("esc [ 7 ~" "Home" home)
1064 ("esc [ 8 ~" "End" end)
1065 ("esc [ 3 ~" "Delete" delete del)
1066 ("esc [ 2 ~" "Insert" insert ins)
1069 (doc (defthing charterm-wyse-wy50-keyset charterm-keyset?
1070 (para "From the Wyse WY-50, based on ["
1072 "] and looking at photos of WY-50 keyboard, and tested in ["
1076 "]. The shifted function keys are provided as both "
1085 (provide charterm-wyse-wy50-keyset)
1086 (define charterm-wyse-wy50-keyset
1087 (make-charterm-keyset-from-keylangs
1105 ("^a ` cr" "Shift-F1" shift-f1 f17)
1106 ("^a a cr" "Shift-F2" shift-f2 f18)
1107 ("^a b cr" "Shift-F3" shift-f3 f19)
1108 ("^a c cr" "Shift-F4" shift-f4 f20)
1109 ("^a d cr" "Shift-F5" shift-f5 f21)
1110 ("^a e cr" "Shift-F6" shift-f6 f22)
1111 ("^a f cr" "Shift-F7" shift-f7 f23)
1112 ("^a g cr" "Shift-F8" shift-f8 f24)
1113 ("^a h cr" "Shift-F9" shift-f9 f25)
1114 ("^a i cr" "Shift-F10" shift-f10 f26)
1115 ("^a j cr" "Shift-F11" shift-f11 f27)
1116 ("^a k cr" "Shift-F12" shift-f12 f28)
1117 ("^a l cr" "Shift-F13" shift-f13 f29)
1118 ("^a m cr" "Shift-F14" shift-f14 f30)
1119 ("^a n cr" "Shift-F15" shift-f15 f31)
1120 ("^a o cr" "Shift-F16" shift-f16 f32)
1121 ("ctrl-h" "Left" left)
1122 ("linefeed" "Down" down)
1124 ("(12)" "Right" right)
1125 ("esc W" "DEL Char" delete)
1126 ("esc Q" "INS Char" insert-char)
1127 ("esc q" "Ins" insert ins)
1128 ("esc T" "CLR Line" clear-line)
1129 ("esc r" "Repl" repl)
1130 ("esc R" "DEL Line" delete-line)
1131 ("esc J" "PAGE Prev" pgup page-up)
1132 ("esc K" "PAGE Next" pgdn page-down)
1133 ("esc P" "Print" print)
1134 ("esc Y" "CLR Screen" clear-screen)
1135 ("(30)" "Home" home record-separator rs)
1136 ("(13)" "Return" return)
1137 ("(127)" "Shift-Backspace" backspace shift-backspace)
1140 (doc (defthing charterm-televideo-925-keyset charterm-keyset?
1141 (para "From the TeleVideo 925, based on ["
1142 (tech "TVI-925-IUG")
1145 "], and from looking at a TeleVideo 950 keyboard.")))
1146 (provide charterm-televideo-925-keyset charterm-keyset?)
1147 (define charterm-televideo-925-keyset
1148 (make-charterm-keyset-from-keylangs
1150 '(("ctrl-a @ cr" f1)
1162 ("ctrl-a \\ cr" "SHIFT-F1" shift-f1)
1163 ("ctrl-a a cr" "SHIFT-F2" shift-f2)
1164 ("ctrl-a b cr" "SHIFT-F3" shift-f3)
1165 ("ctrl-a c cr" "SHIFT-F4" shift-f4)
1166 ("ctrl-a d cr" "SHIFT-F5" shift-f5)
1167 ("ctrl-a e cr" "SHIFT-F6" shift-f6)
1168 ("ctrl-a f cr" "SHIFT-F7" shift-f7)
1169 ("ctrl-a g cr" "SHIFT-F8" shift-f8)
1170 ("ctrl-a h cr" "SHIFT-F9" shift-f9)
1171 ("ctrl-a i cr" "SHIFT-F10" shift-f10)
1172 ("ctrl-a j cr" "SHIFT-F11" shift-f11)
1174 ("ctrl-k" "Up" up ctrl-k)
1175 ("ctrl-v" "Down" down ctrl-v)
1176 ("ctrl-h" "Left" left ctrl-h)
1177 ("ctrl-l" "Right" right ctrl-l)
1179 ("esc W" "CHAR DELETE" delete del char-delete)
1181 ("esc Q" "CHAR INSERT" insert ins char-insert)
1183 ("esc j" "Reverse Linefeed" reverse-linefeed reverse-lf reverse-line-feed)
1185 ("esc i" "BACK TAB" backtab back-tab)
1186 ("ctrl-m" "RETURN" return ctrl-m)
1187 ("ctrl-j" "LINEFEED" linefeed lf ctrl-j)
1188 ("(127)" "DEL" delete del)
1189 ;; ("esc Q" "CHAR INSERT" char-insert char-ins)
1193 (doc (subsubsection "Keydec")
1197 " object is a key decoder for a specific variety of terminal, such
1200 ". A keydec is used to turn received key encodings from a terminal into "
1204 " values. A keydec is constructed from a prioritized list of "
1206 " objects, with earlier-listed keysets taking priority of
1207 later-listed keysets when there is conflict between them as to how to decode a
1208 particular byte sequence."))
1210 (define (%charterm:make-keytree (alist '()))
1211 (make-immutable-hasheqv alist))
1213 (define (%charterm:keytree-add-keyinfo-if-can keytree keyinfo)
1214 (let ((bytelist (charterm-keyinfo-bytelist keyinfo)))
1215 (let loop-bytelist ((this-byte (car bytelist))
1216 (rest-bytes (cdr bytelist))
1219 (cond ((hash-ref node this-byte #f)
1220 => (lambda (existing-sub-node)
1221 ;; Node has a match for this byte, so do we have another
1222 ;; byte and can follow it?
1223 (if (null? rest-bytes)
1224 ;; Node has a match for this byte, but we have no
1225 ;; more bytes, so can't add.
1227 ;; Node has a match for this byte, and we have more
1228 ;; bytes, so follow it.
1231 (loop-bytelist (car rest-bytes)
1233 existing-sub-node)))))
1235 ;; Node has no match for this byte, so add new path.
1238 (let loop ((rest-bytes rest-bytes))
1239 (if (null? rest-bytes)
1241 (%charterm:make-keytree
1242 (cons (cons (car rest-bytes)
1243 (loop (cdr rest-bytes)))
1246 ((charterm-keyinfo? node)
1247 ;; Node is already a keyinfo, so can't add.
1250 '%charterm:keytree-add-keyinfo-if-can
1251 "invalid node ~S with this-byte ~S, rest-bytes ~S, keyinfo ~S"
1257 (define (%charterm:keytree-add-any-keyinfos-can keytree keyinfos)
1258 (let loop ((keyinfos keyinfos)
1260 (if (null? keyinfos)
1262 (loop (cdr keyinfos)
1263 (%charterm:keytree-add-keyinfo-if-can keytree
1266 (define (%charterm:make-keytree-from-keyinfoses keyinfoses)
1267 (let loop ((keyinfoses keyinfoses)
1268 (keytree (%charterm:make-keytree)))
1269 (if (null? keyinfoses)
1271 (let ((keyinfos (car keyinfoses)))
1272 ;; (and (not (null? keyinfos))
1273 ;; (not (charterm-keyinfo? (car keyinfos)))
1274 ;; (error '%charterm:make-keytree-from-keyinfoses
1275 ;; "bad keyinfos: ~S"
1277 (loop (cdr keyinfoses)
1278 (%charterm:keytree-add-any-keyinfos-can keytree
1281 (doc (defproc (charterm-keydec-id (kd charterm-keydec?))
1283 (para "Gets the ID symbol of the "
1286 (provide charterm-keydec-id)
1288 (struct charterm-keydec
1294 (define (charterm-make-keydec keydec-id . keysets)
1295 (charterm-keydec keydec-id
1296 (%charterm:make-keytree-from-keyinfoses
1297 (map charterm-keyset-primary-keyinfos keysets))
1298 (%charterm:make-keytree-from-keyinfoses
1299 (map charterm-keyset-secondary-keyinfos keysets))))
1301 (doc (subsubsub*section "ANSI Keydecs"))
1303 (doc (defthing charterm-vt100-keydec charterm-keydec?
1304 (para (tech "Keydec")
1310 (provide charterm-vt100-keydec)
1311 (define charterm-vt100-keydec
1312 (charterm-make-keydec 'vt100
1313 charterm-dec-vt100-keyset
1314 charterm-dec-vt220-keyset
1315 charterm-xterm-new-keyset
1316 charterm-linux-keyset
1317 charterm-rxvt-keyset
1318 charterm-xterm-xfree86-keyset
1319 charterm-xterm-x11r6-keyset
1320 charterm-ascii-keyset))
1322 (doc (defthing charterm-vt220-keydec charterm-keydec?
1323 (para (tech "Keydec")
1329 (provide charterm-vt220-keydec)
1330 (define charterm-vt220-keydec
1331 (charterm-make-keydec 'vt220
1332 charterm-dec-vt220-keyset
1333 charterm-dec-vt100-keyset
1334 charterm-ascii-keyset))
1336 (doc (defthing charterm-screen-keydec charterm-keydec?
1337 (para (tech "Keydec")
1343 (provide charterm-screen-keydec)
1344 (define charterm-screen-keydec
1345 (charterm-make-keydec 'screen
1346 charterm-screen-keyset
1347 charterm-linux-keyset
1348 charterm-dec-vt220-keyset
1349 charterm-dec-vt100-keyset
1350 charterm-xterm-new-keyset
1351 charterm-xterm-xfree86-keyset
1352 charterm-xterm-x11r6-keyset
1353 charterm-ascii-keyset))
1355 (doc (defthing charterm-linux-keydec charterm-keydec?
1356 (para (tech "Keydec")
1362 (provide charterm-linux-keydec)
1363 (define charterm-linux-keydec
1364 (charterm-make-keydec 'linux
1365 charterm-linux-keyset
1366 charterm-dec-vt220-keyset
1367 charterm-dec-vt100-keyset
1368 charterm-xterm-new-keyset
1369 charterm-xterm-xfree86-keyset
1370 charterm-xterm-x11r6-keyset
1371 charterm-screen-keyset
1372 charterm-ascii-keyset))
1374 (doc (defthing charterm-xterm-new-keydec charterm-keydec?
1375 (para (tech "Keydec")
1379 (racket "xterm-new")
1381 (provide charterm-xterm-new-keydec)
1382 (define charterm-xterm-new-keydec
1383 (charterm-make-keydec 'xterm-new
1384 charterm-xterm-new-keyset
1385 charterm-xterm-xfree86-keyset
1386 charterm-xterm-x11r6-keyset
1387 charterm-rxvt-keyset
1388 charterm-dec-vt220-keyset
1389 charterm-dec-vt100-keyset
1390 charterm-linux-keyset
1391 charterm-ascii-keyset))
1393 (doc (defthing charterm-xterm-keydec charterm-keydec?
1394 (para (tech "Keydec")
1399 ". Currently same as the keydec for "
1401 ", except for a different ID.")))
1402 (provide charterm-xterm-keydec)
1403 (define charterm-xterm-keydec
1404 (charterm-make-keydec 'xterm
1405 charterm-xterm-new-keyset
1406 charterm-xterm-xfree86-keyset
1407 charterm-xterm-x11r6-keyset
1408 charterm-rxvt-keyset
1409 charterm-dec-vt220-keyset
1410 charterm-dec-vt100-keyset
1411 charterm-linux-keyset
1412 charterm-ascii-keyset))
1414 (doc (defthing charterm-rxvt-keydec charterm-keydec?
1415 (para (tech "Keydec")
1421 (provide charterm-rxvt-keydec)
1422 (define charterm-rxvt-keydec
1423 (charterm-make-keydec 'rxvt
1424 charterm-rxvt-keyset
1425 charterm-xterm-new-keyset
1426 charterm-xterm-xfree86-keyset
1427 charterm-xterm-x11r6-keyset
1428 charterm-dec-vt220-keyset
1429 charterm-dec-vt100-keyset
1430 charterm-linux-keyset
1431 charterm-ascii-keyset))
1433 (doc (subsubsub*section "Wyse Keydecs"))
1435 (doc (defthing charterm-wy50-keydec charterm-keydec?
1436 (para (tech "Keydec")
1442 (provide charterm-wy50-keydec)
1443 (define charterm-wy50-keydec
1444 (charterm-make-keydec 'wy50
1445 charterm-wyse-wy50-keyset
1446 charterm-ascii-keyset))
1448 (doc (subsubsub*section "TeleVideo Keydecs"))
1450 (doc (defthing charterm-tvi925-keydec charterm-keydec?
1451 (para (tech "Keydec")
1457 (provide charterm-tvi925-keydec)
1458 (define charterm-tvi925-keydec
1459 (charterm-make-keydec 'tvi925
1460 charterm-televideo-925-keyset
1461 charterm-ascii-keyset))
1463 (doc (subsubsub*section "ASCII Keydecs"))
1465 (doc (defthing charterm-ascii-keydec charterm-keydec?
1466 (para (tech "Keydec")
1472 (provide charterm-ascii-keydec)
1473 (define charterm-ascii-keydec
1474 (charterm-make-keydec 'ascii
1475 charterm-ascii-keyset))
1477 (doc (subsubsub*section "Default Keydecs"))
1479 (doc (defthing charterm-ansi-keydec charterm-keydec?
1480 (para (tech "Keydec")
1481 " for any presumed ANSI-ish terminal, combining many ANSI-ish "
1484 (define charterm-ansi-keydec
1485 (charterm-make-keydec 'ansi
1486 charterm-dec-vt220-keyset
1487 charterm-dec-vt100-keyset
1488 charterm-xterm-new-keyset
1489 charterm-linux-keyset
1490 charterm-rxvt-keyset
1491 charterm-xterm-xfree86-keyset
1492 charterm-xterm-x11r6-keyset
1493 charterm-ascii-keyset))
1495 (doc (defthing charterm-insane-keydec charterm-keydec?
1496 (para (tech "Keydec")
1497 " for the uniquely desperate situation of wanting to possibly have
1498 extensive key decoding for a terminal that might not even be ansi, but be
1499 Wyse, TeleVideo, or some other ASCII.")))
1500 (provide charterm-insane-keydec)
1501 (define charterm-insane-keydec
1502 (charterm-make-keydec 'insane
1503 charterm-xterm-new-keyset
1504 charterm-linux-keyset
1505 charterm-dec-vt220-keyset
1506 charterm-dec-vt100-keyset
1507 charterm-linux-keyset
1508 charterm-xterm-xfree86-keyset
1509 charterm-xterm-x11r6-keyset
1510 charterm-wyse-wy50-keyset
1511 charterm-televideo-925-keyset
1512 charterm-ascii-keyset))
1514 (doc (subsection "Termvar")
1520 " package calls the value of the Unix-like "
1522 " environment variable. Each "
1528 ". Note, however, that "
1530 " is not always a precise indicator of the best protocol and keydec,
1531 but by default we work with what we have."))
1533 ;; TODO: Document the termvars here? Move this subsection?
1535 (doc (section (code "charterm") " Object")
1539 " object captures the state of a session with a particular terminal.")
1543 " object is also a synchronizable event, so it can be used with
1544 procedures such as "
1546 ". As an event, it becomes ready when there is at least one byte
1547 available for reading from the terminal, and its synchronization result is
1550 (doc (defproc (charterm? (x any/c))
1552 (para "Predicate for whether or not "
1559 (doc (defproc (charterm-termvar (ct charterm?))
1564 (provide charterm-termvar)
1566 (doc (defproc (charterm-protocol (ct charterm?))
1571 (provide charterm-protocol)
1573 (doc (defproc (charterm-keydec (ct charterm?))
1578 (provide (rename-out (charterm-keydec* charterm-keydec)))
1580 (define-struct charterm
1587 (buf-start #:mutable)
1592 (screensize #:mutable))
1593 #:property prop:evt (struct-field-index evt))
1595 (define (%charterm:protocol-unimplemented error-name ct)
1597 "protocol unimplemented: ~S"
1598 (charterm-protocol ct)))
1600 (define (%charterm:protocol-unreachable error-name ct)
1602 "internal error: protocol unreachable: ~S"
1603 (charterm-protocol ct)))
1605 (define %charterm:stty-minus-f-arg-string
1606 (case (system-type 'os)
1610 (doc (defparam current-charterm ct (or/c #f charterm?)
1611 (para "This parameter provides the default "
1613 " for most of the other procedures. It is usually set automatically by "
1614 (racket call-with-charterm)
1616 (racket with-charterm)
1618 (racket open-charterm)
1620 (racket close-charterm)
1622 (provide current-charterm)
1623 (define current-charterm (make-parameter #f))
1625 (doc (defproc (open-charterm
1626 (#:tty tty (or/c #f path-string?) #f)
1627 (#:current? current? boolean? #t))
1629 (para "Returns an open "
1631 " object, by opening I/O ports on the terminal device at "
1636 (filepath "/dev/tty")
1637 "), and setting raw mode and disabling echo (via "
1638 (filepath "/bin/stty")
1642 (racket current-charterm)
1643 " parameter is also set to this object.")))
1644 (provide open-charterm)
1645 (define (open-charterm #:tty (tty #f)
1646 #:current? (current? #t))
1647 (let* ((tty (cleanse-path (or tty "/dev/tty")))
1648 (tty-str (path->string tty)))
1649 (or (system* "/bin/stty"
1650 %charterm:stty-minus-f-arg-string
1654 (error 'open-charterm
1657 (with-handlers ((exn:fail? (lambda (e)
1658 (with-handlers ((exn:fail? void))
1659 (system* "/bin/stty"
1660 %charterm:stty-minus-f-arg-string
1664 (let*-values (((in out) (open-input-output-file tty
1667 ;; TODO: Do we actually need to turn off buffering?
1668 (file-stream-buffer-mode in 'none)
1669 (file-stream-buffer-mode out 'none)
1671 (((termvar) (getenv "TERM"))
1672 ((termvar) (cond ((not termvar) #f)
1673 ((equal? "" termvar) #f)
1674 (else (string-downcase termvar))))
1676 ;; TODO: Once the patterns have been fleshed out, make the exact
1677 ;; matches a hash, and optimize the regexps.
1678 (cond ((not termvar) (values #f #f))
1680 ((equal? "ascii" termvar) (values 'ascii charterm-ascii-keydec))
1681 ((equal? "dumb" termvar) (values 'ascii charterm-ascii-keydec))
1682 ((equal? "linux" termvar) (values 'ansi charterm-linux-keydec))
1683 ((equal? "rxvt" termvar) (values 'ansi charterm-rxvt-keydec))
1684 ((equal? "screen" termvar) (values 'ansi charterm-screen-keydec))
1685 ((equal? "tvi925" termvar) (values 'televideo-925 charterm-tvi925-keydec))
1686 ((equal? "tvi950" termvar) (values 'televideo-925 charterm-tvi925-keydec))
1687 ((equal? "vt100" termvar) (values 'ansi charterm-vt100-keydec))
1688 ((equal? "vt102" termvar) (values 'ansi charterm-vt100-keydec))
1689 ((equal? "vt220" termvar) (values 'ansi charterm-vt220-keydec))
1690 ((equal? "wy50" termvar) (values 'wyse-wy50 charterm-wy50-keydec))
1691 ((equal? "wy60" termvar) (values 'wyse-wy50 charterm-wy50-keydec))
1692 ((equal? "wy75" termvar) (values 'wyse-wy50 charterm-wy50-keydec))
1693 ((equal? "wyse50" termvar) (values 'wyse-wy50 charterm-wy50-keydec))
1694 ((equal? "wyse60" termvar) (values 'wyse-wy50 charterm-wy50-keydec))
1695 ((equal? "wyse75" termvar) (values 'wyse-wy50 charterm-wy50-keydec))
1696 ((equal? "xterm" termvar) (values 'ansi charterm-xterm-new-keydec))
1697 ((equal? "xterm-new" termvar) (values 'ansi charterm-xterm-new-keydec))
1698 ;; ANSI-ish Guesses:
1699 ((regexp-match #rx"ansi$" termvar) (values 'ansi charterm-ansi-keydec))
1700 ((regexp-match #rx"^ansi" termvar) (values 'ansi charterm-ansi-keydec))
1701 ((regexp-match #rx"^xterm" termvar) (values 'ansi charterm-xterm-new-keydec))
1702 ((regexp-match #rx"^rxvt" termvar) (values 'ansi charterm-rxvt-keydec))
1703 ((regexp-match #rx"^vt" termvar) (values 'ansi charterm-rxvt-keydec))
1704 ;; Non-ANSI Guesses:
1705 ((regexp-match #rx"^wy" termvar) (values 'wyse-wy50 charterm-wy50-keydec))
1706 ((regexp-match #rx"^tvi" termvar) (values 'televideo-925 charterm-tvi925-keydec))
1708 (else (values #f #f))))
1710 (values (or protocol 'ansi)
1711 (or keydec charterm-ansi-keydec))))
1712 (letrec ((wrapping-evt (wrap-evt in
1714 (ct (make-charterm tty-str ; tty
1719 (make-bytes buf-size) ; buf
1726 (if (and (eq? protocol 'ansi)
1727 (not (member termvar '("screen"))))
1731 (current-charterm ct))
1734 (doc (defproc (close-charterm (#:charterm ct charterm? (current-charterm)))
1738 " by closing the I/O ports, and undoing "
1739 (racket open-charterm)
1741 (filepath "/bin/stty")
1743 (racket current-charterm)
1746 ", then that parameter will be changed to "
1748 " for good measure. You might wish to use "
1749 (racket with-charterm)
1750 " instead of worrying about calling "
1751 (racket close-charterm)
1753 (para "Note: If you exit your Racket process without properly closing the "
1755 ", your terminal may be left in a crazy state. You can fix it with
1757 (commandline "stty sane")))
1758 (provide close-charterm)
1759 (define (close-charterm #:charterm (ct (current-charterm)))
1760 (with-handlers ((exn:fail? void)) (close-input-port (charterm-in ct)))
1761 (with-handlers ((exn:fail? void)) (close-output-port (charterm-out ct)))
1762 ;; TODO: Set the port fields of the struct to #f?
1763 (if (with-handlers ((exn:fail? (lambda (e) #f)))
1764 (system* "/bin/stty"
1765 %charterm:stty-minus-f-arg-string
1769 (if (eq? ct (current-charterm))
1770 (current-charterm #f)
1772 (error 'close-charterm
1775 ;; (define (call-with-charterm proc #:tty (tty #f))
1776 ;; (let* ((tty (cleanse-path tty))
1777 ;; (ct (open-charterm #:tty tty #:current? #f)))
1783 ;; (close-charterm #:charterm ct)))))
1785 (doc (defform (with-charterm expr? ...))
1788 " and evaluates the body expressions in sequence with "
1789 (racket current-charterm)
1790 " set appropriately. When control jumps out of the body, in a
1791 manner of speaking, the "
1794 (provide with-charterm)
1795 (define-syntax (with-charterm stx)
1797 ((_ BODY0 BODYn ...)
1801 (set! ct (open-charterm #:current? #t)))
1805 (close-charterm #:charterm ct)
1808 (doc (section "Terminal Information"))
1810 (doc (defproc (charterm-screen-size (#:charterm ct charterm? (current-charterm)))
1811 (values (or/c #f exact-nonnegative-integer?)
1812 (or/c #f exact-nonnegative-integer?))
1813 (para "Attempts to get the screen size, in character columns and rows.
1814 It may do this through a control sequence or through "
1816 ". If unable to get a value, then default of (80,24) is used.")
1817 (para "The current behavior in this version of "
1819 " is to adaptively try different methods of getting screen size,
1820 and to remember what worked for the next time this procedure is called for "
1822 ". For terminals that are identified as "
1826 " environment variable (e.g., terminal emulators like GNU Screen
1829 "), the current behavior is to not try the control sequence (which
1830 causes a 1-second delay waiting for a terminal response that never arrives),
1833 ". For all other terminals, the control sequence is tried first, before trying "
1835 ". If neither the control sequence nor "
1837 " work, then neither method is tried again for "
1839 ", and instead the procedure always returns ("
1843 "). This behavior very well might change in future versions of "
1845 ", and the author welcomes feedback on which methods work with
1846 which terminals.")))
1847 (provide charterm-screen-size)
1848 (define (charterm-screen-size #:charterm (ct (current-charterm)))
1849 ;; TODO: Make it store screen side in slots of charterm object too. Then
1850 ;; create a "with-resizeable-charterm" form that has a resize handler (or
1851 ;; maybe make the resize handler an argument to "with-charterm".
1853 (case (charterm-screensize ct)
1854 ((control) (%charterm:screen-size-via-control ct))
1855 ((stty) (%charterm:screen-size-via-stty ct))
1856 ;; TODO: Instead of (80,24), maybe be sensitive to termvar.
1857 ((none) (values 80 24))
1858 ((control/stty/none)
1859 (let-values (((cols rows) (%charterm:screen-size-via-control ct)))
1862 (begin (set-charterm-screensize! ct 'stty/none)
1865 (let-values (((cols rows) (%charterm:screen-size-via-stty ct)))
1868 (begin (set-charterm-screensize! ct 'none)
1870 (else (error 'charterm-screen-size
1871 "invalid screensize ~S"
1872 (charterm-screensize ct))))))
1874 (define (%charterm:screen-size-via-control ct)
1875 (%charterm:protocol-case
1876 '%charterm:screen-size-via-control
1877 (charterm-protocol ct)
1879 (%charterm:write-bytes ct #"\e[18t")
1880 (cond ((%charterm:read-regexp-response ct #rx#"\e\\[8;([0-9]+);([0-9]+)t")
1882 (values (%charterm:bytes-ascii->nonnegative-integer (list-ref m 1))
1883 (%charterm:bytes-ascii->nonnegative-integer (list-ref m 0)))))
1884 ;; TODO: We could do "ioctl" "TIOCGWINSZ", but that means FFI.
1886 ;; TODO: We could execute "stty -a" (or perhaps "stty -g") to get
1887 ;; around doing an FFI call.
1888 (else (values #f #f))))
1889 ((wyse-wy50 televideo-925)
1890 (%charterm:protocol-unreachable '%charterm:screen-size-via-control ct))))
1892 (define (%charterm:screen-size-via-stty ct)
1893 (let* ((stdout (open-output-bytes))
1894 (stderr (open-output-bytes))
1895 (proc (list-ref (process*/ports stdout
1896 (open-input-bytes #"")
1899 %charterm:stty-minus-f-arg-string
1903 (bstr (begin (proc 'wait)
1904 (get-output-bytes stdout))))
1905 (if (eq? 'done-ok (proc 'status))
1906 (let-values (((width height)
1907 (cond ((regexp-match-positions
1908 #rx#"rows +([0-9]+);.*columns +([0-9]+)"
1911 (values (%charterm:bytes-ascii->nonnegative-integer
1912 (subbytes bstr (caaddr m) (cdaddr m)))
1913 (%charterm:bytes-ascii->nonnegative-integer
1914 (subbytes bstr (caadr m) (cdadr m))))))
1915 ((regexp-match-positions
1916 #rx#"columns +([0-9]+);.*rows +([0-9]+)"
1919 (values (%charterm:bytes-ascii->nonnegative-integer
1920 (subbytes bstr (caadr m) (cdadr m)))
1921 (%charterm:bytes-ascii->nonnegative-integer
1922 (subbytes bstr (caaddr m) (cdaddr m))))))
1924 ;; Note: These checks for 0 are for if "stty" returns 0, such as
1925 ;; seems to happen in the emulator on the Wyse S50 when in SSH rather than Telnet.
1926 (values (if (zero? width) #f width)
1927 (if (zero? height) #f height)))
1930 (doc (section "Display Control"))
1932 (define (%charterm:shift-buf ct)
1933 (let ((buf-start (charterm-buf-start ct))
1934 (buf-end (charterm-buf-end ct)))
1935 (if (= buf-start buf-end)
1936 ;; Buffer is empty, so are buf-start and buf-end at 0?
1939 (begin (set-charterm-buf-start! ct 0)
1940 (set-charterm-buf-end! ct 0)))
1941 ;; Buffer is not empty, so is buf-start at 0?
1943 ;; TODO: Maybe make this shift only if we need to to free N additional
1944 ;; bytes at the end?
1945 (if (zero? buf-start)
1947 (let ((buf (charterm-buf ct)))
1948 (bytes-copy! buf 0 buf buf-start buf-end)
1949 (set-charterm-buf-start! ct 0)
1950 (set-charterm-buf-end! ct (- buf-end buf-start)))))))
1952 (define (%charterm:read-into-buf/timeout ct timeout)
1953 (let ((in (charterm-in ct)))
1955 (let ((sync-result (sync/timeout/enable-break timeout in)))
1956 (cond ((not sync-result) #f)
1957 ((eq? sync-result in)
1958 ;; TODO: if buf is empty, then read into start 0!
1959 (let ((read-result (read-bytes-avail! (charterm-buf ct)
1961 (charterm-buf-end ct)
1962 (charterm-buf-size ct))))
1963 (if (zero? read-result)
1964 ;; TODO: If there's a timeout, subtract from it?
1966 (begin (set-charterm-buf-end! ct (+ (charterm-buf-end ct) read-result))
1968 (else (error '%charterm:read-into-buf/timeout
1969 "*DEBUG* sync returned ~S"
1972 (define (%charterm:read-regexp-response ct rx #:timeout-seconds (timeout-seconds 1.0))
1973 (let ((in (charterm-in ct)))
1974 (%charterm:shift-buf ct)
1975 ;; TODO: Implement timeout better, by checking clock and doing
1976 ;; sync/timeout, or by setting timer.
1977 (let loop ((timeout-seconds timeout-seconds))
1978 (if (= (charterm-buf-end ct) (charterm-buf-size ct))
1980 ;; TODO: Make this an exception instead of #f?
1982 (begin (or (let ((buf (charterm-buf ct))
1983 (buf-start (charterm-buf-start ct))
1984 (buf-end (charterm-buf-end ct)))
1985 (cond ((regexp-match-positions rx
1990 ;; TODO: Audit and test some of this buffer
1991 ;; code here and elsewhere.
1992 (let ((match-start (caar m))
1993 (match-end (cdar m)))
1994 (if (= match-start buf-start)
1995 (set-charterm-buf-start! ct match-end)
1996 (if (= match-end buf-end)
1997 (set-charterm-buf-end! ct match-start)
1998 (begin (bytes-copy! buf
2003 (set-charterm-buf-end! ct
2009 (subbytes buf (car pos) (cdr pos)))
2012 (if (%charterm:read-into-buf/timeout ct timeout-seconds)
2013 (loop timeout-seconds)
2017 (define (%charterm:bytes-ascii->nonnegative-integer bstr)
2018 (let ((bstr-len (bytes-length bstr)))
2023 (let* ((b (bytes-ref bstr i))
2027 (+ (* 10 result) b-num))
2028 (error '%charterm:bytes-ascii->nonnegative-integer
2032 (doc (subsection "Cursor"))
2034 (doc (defproc (charterm-cursor (x exact-positive-integer?)
2035 (y exact-positive-integer?)
2036 (#:charterm ct charterm? (current-charterm)))
2038 (para "Positions the cursor at column "
2042 ", with the upper-left character cell being (1, 1).")))
2043 (provide charterm-cursor)
2044 (define (charterm-cursor x y #:charterm (ct (current-charterm)))
2045 (%charterm:position ct x y))
2047 (doc (defproc (charterm-newline (#:charterm ct charterm? (current-charterm)))
2049 (para "Sends a newline to the terminal. This is typically a CR-LF
2051 (provide charterm-newline)
2052 (define (charterm-newline #:charterm (ct (current-charterm)))
2053 (%charterm:write-bytes ct #"\r\n"))
2055 (doc (subsection "Displaying"))
2057 (define %charterm:err-byte 63)
2059 (doc (defproc (charterm-display
2060 (#:charterm ct charterm? (current-charterm))
2061 (#:width width (or/c #f exact-positive-integer?) #f)
2062 (#:pad pad (or/c 'width boolean?) 'width)
2063 (#:truncate truncate (or/c 'width boolean?) 'width)
2066 (para "Displays each "
2068 " on the terminal, as if formatted by "
2070 ", with the exception that unprintable or non-ASCII characters
2071 might not be displayed. (The exact behavior of what is permitted is expected
2072 to change in a later version of "
2074 ", so avoid trying to send your own control sequences or using
2075 newlines, making assumptions about non-ASCII characters, etc.)")
2078 " is a number, then "
2082 " specify whether or not to pad with spaces or truncate the output, respectively, to "
2084 " characters. When "
2090 ", that is a convenience meaning ``true if, and only if, "
2095 (provide charterm-display)
2096 (define (charterm-display #:charterm (ct (current-charterm))
2099 #:truncate (truncate 'width)
2101 ;; TODO: make it replace unprintable and non-ascii characters with "?". Even newlines, tabs, etc?
2103 ;; TODO: Do we want buffering?
2104 (let ((out (charterm-out ct))
2105 (pad (if (eq? 'width pad) (if width #t #f) pad))
2106 (truncate (if (eq? 'width truncate) (if width #t #f) truncate)))
2107 (and pad (not width) (error 'charterm-display "#:pad cannot be true if #:width is not"))
2108 (and truncate (not width) (error 'charterm-display "#:truncate cannot be true if #:width is not"))
2109 (let loop ((args args)
2110 (remaining-width (or width 0)))
2112 (if (and pad (> remaining-width 0))
2113 ;; TODO: Get rid of this allocation.
2114 (begin (%charterm:write-bytes ct (make-bytes remaining-width 32))
2117 (let* ((arg (car args))
2118 (bytes (cond ((bytes? arg)
2121 (string->bytes/latin-1 arg
2125 (min (string-length arg)
2127 (string-length arg))))
2129 (string->bytes/latin-1 (number->string arg)
2130 %charterm:err-byte))
2131 (else (let ((arg (format "~A" arg)))
2132 (string->bytes/latin-1 arg
2136 (min (string-length arg)
2138 (string-length arg)))))))
2139 (remaining-width (- remaining-width (bytes-length bytes))))
2140 (cond ((or (not truncate) (> remaining-width 0))
2141 (%charterm:write-bytes ct bytes)
2144 ((zero? remaining-width)
2145 (%charterm:write-bytes ct bytes)
2147 (else (%charterm:write-subbytes ct bytes 0 (+ (bytes-length bytes)
2151 (define (%charterm:send-code ct . args)
2152 ;; TODO: Do we want buffering?
2153 (let ((out (charterm-out ct)))
2154 (let loop ((args args))
2157 (let ((arg (car args)))
2159 (write-bytes arg out))
2161 (write-string arg out))
2163 (display (inexact->exact arg) out))
2167 (else (error '%charterm:send-code
2168 "don't know how to send ~S"
2170 (loop (cdr args)))))))
2172 ;; (define %charterm:2-digit-bytes-vector
2173 ;; (vector #"00" #"01" #"02" #"03" #"04" #"05" #"06" #"07"
2174 ;; #"08" #"09" #"10" #"11" #"12" #"13" #"14" #"15"
2175 ;; #"16" #"17" #"18" #"19" #"20" #"21" #"22" #"23"
2176 ;; #"24" #"25" #"26" #"27" #"28" #"29" #"30" #"31"
2177 ;; #"32" #"33" #"34" #"35" #"36" #"37" #"38" #"39"
2178 ;; #"40" #"41" #"42" #"43" #"44" #"45" #"46" #"47"
2179 ;; #"48" #"49" #"50" #"51" #"52" #"53" #"54" #"55"
2180 ;; #"56" #"57" #"58" #"59" #"60" #"61" #"62" #"63"
2181 ;; #"64" #"65" #"66" #"67" #"68" #"68" #"69" #"70"
2182 ;; #"72" #"73" #"74" #"75" #"76" #"77" #"78" #"79"
2183 ;; #"80" #"81" #"82" #"83" #"84" #"85" #"86" #"87"))
2185 (define %charterm:televideo-925-cursor-position-to-byte-vector
2186 (list->vector (cons #f
2187 (for/list ((n (in-range 1 96)))
2190 ;; (provide/contract with error-checks on args
2191 (define (%charterm:position ct x y)
2192 (%charterm:protocol-case
2194 (charterm-protocol ct)
2196 (if (and (= 1 x) (= 1 y))
2197 (%charterm:write-bytes ct #"\e[;H")
2198 (%charterm:send-code ct #"\e[" y #";" x #"H")))
2200 ;; Note: We are using the WY-50 long codes because we don't know
2201 ;; confidently that we are an 80-column screen.
2202 (if (and (= 1 x) (= 1 y))
2203 (%charterm:write-bytes ct #"\ea1R1C")
2204 (%charterm:send-code ct #"\ea" y #"R" x #"C")))
2206 (if (and (= 1 x) (= 1 y))
2207 (%charterm:write-bytes ct #"\e= ")
2208 (begin (%charterm:write-bytes ct #"\e=")
2209 (%charterm:write-byte ct (vector-ref %charterm:televideo-925-cursor-position-to-byte-vector y))
2210 (%charterm:write-byte ct (vector-ref %charterm:televideo-925-cursor-position-to-byte-vector x)))))))
2212 (doc (subsection "Video Attributes"))
2214 ;; TODO: !!! document link to protocol section
2216 ;; TODO: !!! define "charterm-has-video-attributes?"
2219 (((charterm-normal (#:charterm ct charterm? (current-charterm))) void?)
2220 ((charterm-inverse (#:charterm ct charterm? (current-charterm))) void?)
2221 ((charterm-underline (#:charterm ct charterm? (current-charterm))) void?)
2222 ((charterm-blink (#:charterm ct charterm? (current-charterm))) void?)
2223 ((charterm-bold (#:charterm ct charterm? (current-charterm))) void?))
2225 (deftech "video attributes")
2226 " for subsequent writes to the terminal. In this version of "
2228 ", each is mutually-exclusive, so, for example, setting "
2232 ". Note that that video attributes are currently supported only for protocol "
2234 ", due to limitations of the TeleVideo and Wyse models for
2235 video attributes.")))
2237 (provide charterm-normal)
2238 (define (charterm-normal #:charterm (ct (current-charterm)))
2239 (%charterm:protocol-case
2241 (charterm-protocol ct)
2242 ((ansi) (%charterm:write-bytes ct #"\e[m"))
2243 ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA00"))
2244 ((televideo-925) (void))))
2246 (provide charterm-inverse)
2247 (define (charterm-inverse #:charterm (ct (current-charterm)))
2248 (%charterm:protocol-case
2250 (charterm-protocol ct)
2251 ((ansi) (%charterm:write-bytes ct #"\e[;7m"))
2252 ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA04"))
2253 ((televideo-925) (void))))
2255 (provide charterm-underline)
2256 (define (charterm-underline #:charterm (ct (current-charterm)))
2257 (%charterm:protocol-case
2259 (charterm-protocol ct)
2260 ((ansi) (%charterm:write-bytes ct #"\e[4m"))
2261 ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA08"))
2262 ((televideo-925) (void))))
2264 (provide charterm-blink)
2265 (define (charterm-blink #:charterm (ct (current-charterm)))
2266 (%charterm:protocol-case
2268 (charterm-protocol ct)
2269 ((ansi) (%charterm:write-bytes ct #"\e[5m"))
2270 ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA02"))
2271 ((televideo-925) (void))))
2273 (provide charterm-bold)
2274 (define (charterm-bold #:charterm (ct (current-charterm)))
2275 (%charterm:protocol-case
2277 (charterm-protocol ct)
2278 ((ansi) (%charterm:write-bytes ct #"\e[1m"))
2279 ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA0<"))
2280 ((televideo-925) (void))))
2282 (doc (subsection "Clearing"))
2284 (doc (defproc (charterm-clear-screen (#:charterm ct charterm? (current-charterm)))
2286 (para "Clears the screen, including first setting the video attributes to
2287 normal, and positioning the cursor at (1, 1).")))
2288 (provide charterm-clear-screen)
2289 (define (charterm-clear-screen #:charterm (ct (current-charterm)))
2290 ;; TODO: Have a #:style argument? Or #:background argument?
2291 (%charterm:protocol-case
2292 'charterm-clear-screen
2293 (charterm-protocol ct)
2294 ((ansi) (%charterm:write-bytes ct #"\e[m\e[2J\e[;H"))
2295 ((wyse-wy50) (%charterm:write-bytes ct #"\e+\e*\ea1R1C"))
2296 ((televideo-925) (%charterm:write-bytes ct #"\e+\e= "))))
2299 (((charterm-clear-line (#:charterm ct charterm? (current-charterm))) void?)
2300 ((charterm-clear-line-left (#:charterm ct charterm? (current-charterm))) void?)
2301 ((charterm-clear-line-right (#:charterm ct charterm? (current-charterm))) void?))
2302 (para "Clears text from the line with the cursor, or part of the line with the cursor.")))
2304 (provide charterm-clear-line)
2305 (define (charterm-clear-line #:charterm (ct (current-charterm)))
2306 (%charterm:protocol-case
2307 'charterm:clear-line
2308 (charterm-protocol ct)
2309 ((ansi) (%charterm:write-bytes ct #"\e[2K"))
2310 ((televideo-925) (%charterm:write-bytes ct #"\r\eT"))
2311 ;; TODO: wyse-wy50 is clearing to nulls, not spaces.
2312 ((wyse-wy50) (%charterm:write-bytes ct #"\r\et"))))
2314 (provide charterm-clear-line-left)
2315 (define (charterm-clear-line-left #:charterm (ct (current-charterm)))
2316 (%charterm:protocol-case
2317 'charterm-clear-line-left
2318 (charterm-protocol ct)
2319 ((ansi) (%charterm:write-bytes ct #"\e[1K"))
2320 ((televideo-925 wyse-wy50)
2321 ;; TODO: Do this by getting cursor position, then reposition and write spaces?
2322 (%charterm:unimplemented ct 'clearterm-clear-line-left))))
2324 (provide charterm-clear-line-right)
2325 (define (charterm-clear-line-right #:charterm (ct (current-charterm)))
2326 (%charterm:protocol-case
2327 'charterm-clear-line-right
2328 (charterm-protocol ct)
2329 ((ansi) (%charterm:write-bytes ct #"\e[K"))
2330 ((televideo-925) (%charterm:write-bytes ct #"\eT"))
2331 ;; TODO: wyse-wy50 is clearing to nulls, not spaces.
2332 ((wyse-wy50) (%charterm:write-bytes ct #"\et"))))
2334 (doc (subsection "Line Insert and Delete"))
2336 (doc (defproc (charterm-insert-line (count exact-positive-integer? 1)
2337 (#:charterm ct charterm? (current-charterm)))
2341 " blank lines at cursor. Note that not all terminals support
2343 (provide charterm-insert-line)
2344 (define (charterm-insert-line (count 1) #:charterm (ct (current-charterm)))
2345 (if (integer? count)
2346 (cond ((= count 0) (void))
2348 (%charterm:protocol-case
2349 'charterm-insert-line
2350 (charterm-protocol ct)
2351 ((ansi) (%charterm:send-code ct #"\e[" count "L"))
2352 ((wyse-wy50 televideo-925) (%charterm:write-bytes ct #"\eE"))))
2353 (else (error 'charterm-insert-line
2356 (error 'charterm-insert-line
2360 (doc (defproc (charterm-delete-line (count exact-positive-integer? 1)
2361 (#:charterm ct charterm? (current-charterm)))
2365 " blank lines at cursor. Note that not all terminals support
2367 (provide charterm-delete-line)
2368 (define (charterm-delete-line (count 1) #:charterm (ct (current-charterm)))
2369 (if (integer? count)
2370 (cond ((= count 0) (void))
2372 (%charterm:protocol-case
2373 'charterm-delete-line
2374 (charterm-protocol ct)
2376 (%charterm:send-code ct #"\e[" count "M"))
2377 ((wyse-wy50 televideo-925)
2379 (%charterm:write-bytes ct #"\eR")
2380 (let ((bstr (make-bytes (* 2 count) 82)))
2381 (let loop ((n (* 2 (- count 1))))
2382 (bytes-set! bstr n 27)
2384 (%charterm:write-bytes ct bstr)
2385 (loop (- n 2)))))))))
2386 (else (error 'charterm-delete-line
2389 (error 'charterm-delete-line
2393 (doc (subsubsection "Misc. Output"))
2395 (doc (defproc (charterm-bell (#:charterm ct charterm? (current-charterm)))
2397 (para "Rings the terminal bell. This bell ringing might manifest as a
2398 beep, a flash of the screen, or nothing.")))
2399 (provide charterm-bell)
2400 (define (charterm-bell #:charterm (ct (current-charterm)))
2401 (%charterm:write-bytes ct #"\007"))
2403 (doc (section "Keyboard Input")
2405 ;; TODO: !!! document link to terminal diversity section
2407 (para "Normally you will get keyboard input using the "
2408 (racket charterm-read-key)
2411 (doc (defproc (charterm-byte-ready? (#:charterm ct charterm? (current-charterm)))
2413 (para "Returns true/false for whether at least one byte is ready for
2414 reading (either in a buffer or on the port) from "
2416 ". Note that, since some keys are encoded as multiple bytes, just
2417 because this procedure returns true doesn't mean that "
2418 (racket charterm-read-key)
2419 " won't block temporarily because it sees part of a potential
2420 multiple-byte key encoding.")))
2421 (provide charterm-byte-ready?)
2422 (define (charterm-byte-ready? #:charterm (ct (current-charterm)))
2423 (or (> (charterm-buf-end ct) (charterm-buf-start ct))
2424 (byte-ready? (charterm-in ct))))
2426 (doc (defproc (charterm-read-key
2427 (#:charterm ct charterm? (current-charterm))
2428 (#:timeout timeout (or/c #f positive?) #f))
2429 (or #f char? symbol?)
2430 (para "Reads a key from "
2432 ", blocking indefinitely or until sometime after "
2434 " seconds has been reached, if "
2438 ". If timeout is reached, "
2441 (para "Many keys are returned as characters, especially ones that
2442 correspond to printable characters. For example, the unshifted "
2444 " key is returned as character "
2446 ". Some other keys are returned as symbols, such as "
2456 ", and many others.")
2457 (para "Since some keys are sent as ambiguous sequences, "
2458 (racket charterm-read-key)
2459 " employs separate timeouts internally, such as to disambuate
2462 " key (byte sequence 27) from what on some terminals would be
2465 " key (bytes sequence 27, 91, 50, 49, 126).")))
2466 (provide charterm-read-key)
2467 (define (charterm-read-key #:charterm (ct (current-charterm))
2468 #:timeout (timeout #f))
2469 (%charterm:read-keyinfo-or-key 'charterm-read-key ct timeout #f))
2471 (doc (defproc (charterm-read-keyinfo
2472 (#:charterm ct charterm? (current-charterm))
2473 (#:timeout timeout (or/c #f positive?) #f))
2476 (racket charterm-read-keyinfo)
2477 " except instead of returning a "
2482 (provide charterm-read-keyinfo)
2483 (define (charterm-read-keyinfo #:charterm (ct (current-charterm))
2484 #:timeout (timeout #f))
2485 (%charterm:read-keyinfo-or-key 'charterm-read-keyinfo ct timeout #t))
2487 (define (%charterm:read-keyinfo-or-key error-name ct timeout keyinfo?)
2488 ;; TODO: Maybe make this shift decision smarter -- compile the key tree ahead
2489 ;; of time so we know the max depth, and then we know exactly the max space
2490 ;; we will need for this call.
2491 (and (< (- (charterm-buf-size ct)
2492 (charterm-buf-start ct))
2494 (%charterm:shift-buf ct))
2495 (let ((buf (charterm-buf ct))
2496 (buf-start (charterm-buf-start ct))
2497 (buf-end (charterm-buf-end ct))
2498 (buf-size (charterm-buf-size ct))
2499 (keydec (charterm-keydec* ct))
2500 (b1 (%charterm:read-byte/timeout ct timeout)))
2502 (or (let loop ((tree (charterm-keydec-primary-keytree keydec))
2503 (probe-start (+ 1 buf-start))
2505 (cond ((hash-ref tree b #f)
2506 => (lambda (code-or-subtree)
2507 (cond ((hash? code-or-subtree)
2508 ;; We have more subtree to search.
2509 (if (or (< probe-start buf-end)
2510 (and (< buf-end buf-size)
2511 (%charterm:read-into-buf/timeout ct 0.5)))
2512 ;; We have at least one more byte, so recurse.
2513 (loop code-or-subtree
2515 (bytes-ref buf probe-start))
2516 ;; We have hit timeout or end of buffer, so
2517 ;; just accept the original byte.
2519 ((charterm-keyinfo? code-or-subtree)
2520 ;; We found our keyinfo, so consume the input and return the value.
2521 (begin (set-charterm-buf-start! ct probe-start)
2524 (charterm-keyinfo-keycode code-or-subtree))
2526 (else (error error-name
2527 "invalid object in keytree keyinfo position: ~S"
2528 code-or-subtree)))))
2530 ;; We didn't find a key code, so try secondary keytree with initial byte.
2531 (cond ((hash-ref (charterm-keydec-secondary-keytree keydec) b1 #f)
2532 => (lambda (keyinfo)
2535 (charterm-keyinfo-keycode keyinfo))))
2537 ;; TODO: Cache these keyinfos for unrecognized keys
2538 ;; in the charterm object, or make a fallback
2539 ;; keyset for them (although the fallback keyset,
2540 ;; while it works for 8-bit characters, becomes
2541 ;; less practical if we implement multibyte).
2542 (make-charterm-keyinfo #f
2548 (integer->char b1)))))
2549 ;; Got a timeout, so return #f.
2552 (define (%charterm:write-byte ct byt)
2553 (write-byte byt (charterm-out ct)))
2555 (define (%charterm:write-bytes ct bstr . rest-bstrs)
2556 (write-bytes bstr (charterm-out ct))
2557 (or (null? rest-bstrs)
2558 (for-each (lambda (bstr)
2559 (write-bytes bstr (charterm-out ct)))
2562 (define (%charterm:write-subbytes ct bstr start end)
2563 (write-bytes bstr (charterm-out ct) start end))
2565 (define (%charterm:read-byte/timeout ct timeout)
2566 (let ((buf-start (charterm-buf-start ct)))
2567 (if (or (< buf-start (charterm-buf-end ct))
2568 (%charterm:read-into-buf/timeout ct timeout))
2569 (begin0 (bytes-ref (charterm-buf ct) buf-start)
2570 (set-charterm-buf-start! ct (+ 1 buf-start)))
2573 (define (%charterm:read-byte ct)
2574 (%charterm:read-byte/timeout ct #f))
2576 (doc (section "References")
2578 (para "[" (deftech "ANSI X3.64") "] "
2579 (url "http://en.wikipedia.org/wiki/ANSI_escape_code"))
2581 (para "[" (deftech "ASCII") "] "
2582 (url "http://en.wikipedia.org/wiki/Ascii"))
2584 (para "[" (deftech "ECMA-43") "] "
2585 (hyperlink "http://www.ecma-international.org/publications/standards/Ecma-043.htm"
2586 (italic "Standard ECMA-43: 8-bit Coded Character Set Structure and Rules"))
2587 ", 3rd Ed., 1991-12")
2589 (para "[" (deftech "ECMA-48") "] "
2590 (hyperlink "http://www.ecma-international.org/publications/standards/Ecma-048.htm"
2591 (italic "Standard ECMA-48: Control Functions for Coded Character Sets"))
2592 ", 5th Ed., 1991-06")
2594 (para "[" (deftech "Gregory") "] "
2596 (hyperlink "http://aperiodic.net/phil/archives/Geekery/term-function-keys.html"
2597 "Terminal Function Key Escape Codes")
2598 ",'' 2005-12-13 Web post, as viewed on 2012-06")
2600 (para "[" (deftech "PowerTerm") "] "
2601 "Ericom PowerTerm InterConnect 8.2.0.1000 terminal emulator, as run on Wyse S50 WinTerm")
2603 (para "[" (deftech "TVI-925-IUG") "] "
2604 (hyperlink "http://vt100.net/televideo/tvi925_ig.pdf"
2605 (italic "TeleVideo Model 925 CRT Terminal Installation and User's Guide")))
2607 (para "[" (deftech "TVI-950-OM") "] "
2608 (hyperlink "http://www.mirrorservice.org/sites/www.bitsavers.org/pdf/televideo/Operators_Manual_Model_950_1981.pdf"
2609 (italic "TeleVideo Operator's Manual Model 950"))
2612 (para "[" (deftech "VT100-TM") "] "
2613 "Digital Equipment Corp., "
2614 (hyperlink "http://vt100.net/docs/vt100-tm/"
2615 (italic "VT100 Series Technical Manual"))
2616 ", 2nd Ed., 1980-09")
2618 (para "[" (deftech "VT100-UG") "] "
2619 "Digital Equipment Corp., "
2620 (hyperlink "http://vt100.net/docs/vt100-ug/"
2621 (italic "VT100 User Guide"))
2622 ", 3rd Ed., 1981-06")
2624 (para "[" (deftech "VT100-WP") "] "
2626 (hyperlink "http://en.wikipedia.org/wiki/VT100"
2629 (para "[" (deftech "WY-50-QRG") "] "
2630 (hyperlink "http://vt100.net/wyse/wy-50-qrg/wy-50-qrg.pdf"
2631 (italic "Wyse WY-50 Display Terminal Quick-Reference Guide")))
2633 (para "[" (deftech "WY-60-UG") "] "
2634 (hyperlink "http://vt100.net/wyse/wy-60-ug/wy-60-ug.pdf"
2635 (italic "Wyse WY-60 User's Guide")))
2637 (para "[" (deftech "wy60") "] "
2638 (hyperlink "http://code.google.com/p/wy60/"
2640 " terminal emulator"))
2642 (para "[" (deftech "XTerm-ctlseqs") "] "
2643 "Edward Moy, Stephen Gildea, Thomas Dickey, ``"
2644 (hyperlink "http://invisible-island.net/xterm/ctlseqs/ctlseqs.html"
2645 "Xterm Control Sequences")
2648 (para "[" (deftech "XTerm-Dickey") "] "
2649 (url "http://invisible-island.net/xterm/"))
2651 (para "[" (deftech "XTerm-FAQ") "] "
2652 "Thomas E. Dickey, ``"
2653 (hyperlink "http://invisible-island.net/xterm/xterm.faq.html"
2657 (para "[" (deftech "XTerm-WP") "] "
2659 (hyperlink "http://en.wikipedia.org/wiki/Xterm"
2664 (doc (section "Known Issues")
2668 (item "Need to support ANSI alternate CSI for 8-bit terminals, even
2669 before supporting 8-bit characters and multibyte.")
2671 (item "Only supports ASCII characters. Adding UTF-8 support, for terminal emulators
2672 that support it, would be nice.")
2674 (item "Expose the character-decoding mini-language as a configurable
2675 option. Perhaps wait until we implement timeout-based disambiguation at
2676 arbitrary points in the the DFA rather than just at the top. Also, might be
2677 better to resolve multi-byte characters first, in case that affects the
2680 (item "More controls for terminal features can be added.")
2682 (item "Currently only implemented to work on Unix-like systems like
2685 (item "Implement text input controls, either as part of this library or
2687 (racket charterm-demo)
2688 " as a starting point.")))
2690 ;; Note: Different ways to test demo:
2692 ;; racket -t demo.rkt -m
2693 ;; screen racket -t demo.rkt -m
2694 ;; tmux -c "racket -t demo.rkt -m"
2695 ;; xterm -e racket -t demo.rkt -m
2696 ;; rxvt -e racket -t demo.rkt -m
2697 ;; wy60 -c racket -t demo.rkt -m
2699 ;; racket -t demo.rkt -m- -n
2701 ;; TODO: Source for TeleVideo manuals:
2702 ;; http://www.mirrorservice.org/sites/www.bitsavers.org/pdf/televideo/
2704 ;; TODO: Add shifted function keys from T60 keyboard (not USB one).
2708 (#:planet 3:1 #:date "2013-05-13"
2710 (item "Now uses lowercase "
2712 " argument on MacOS X. (Thanks to Jens Axel S\u00F8gaard for reporting.)")
2713 (item "Documentation tweaks.")))
2715 (#:planet 3:0 #:date "2012-07-13"
2719 "'' in identifiers to ``"
2721 "'', hence the PLaneT major version number change.")
2722 (item "Documentation tweaks.")
2723 (item "Renamed package from ``"
2725 "'' to ``CharTerm''.")))
2727 (#:planet 2:5 #:date "2012-06-28"
2731 " object is now a synchronizable event.")
2732 (item "Documentation tweaks.")))
2734 (#:planet 2:4 #:date "2012-06-25"
2736 (item "Documentation fix for return type of "
2737 (racket charterm-read-keyinfo)
2740 (#:planet 2:3 #:date "2012-06-25"
2742 (item "Fixed problem determining screen size on some
2743 XTerms. (Thanks to Eli Barzilay for reporting.)")))
2745 (#:planet 2:2 #:date "2012-06-25"
2747 (item "Added another variation of encoding for XTerm arrow,
2748 Home, and End keys. (Thanks to Eli Barzilay.)")))
2750 (#:planet 2:1 #:date "2012-06-24"
2752 (item "Corrected PLaneT version number in "
2754 " in an example.")))
2756 (#:planet 2:0 #:date "2012-06-24"
2758 (item "Greatly increased the sophistication of handling of terminal diversity.")
2762 (code "televideo-950")
2764 (code "televideo-925")
2765 "] protocols, for supporting the native modes of Wyse and
2766 TeleVideo terminals, respectively, and compatibles.")
2767 (item "More support for different key encodings and termvars.")
2768 (item "Demo is now in a separate file, mainly for convenience
2769 in giving command lines that run it. This breaks a command line example
2770 previously documented, so changed PLaneT major version, although the
2771 previously-published example will need to have "
2773 " added to it anyway.")
2774 (item (racket charterm-screen-size)
2775 " now defaults to (80,24) when all else fails.")
2776 (item "Documentation changes.")))
2778 (#:planet 1:1 #:date "2012-06-17"
2784 ", now gets screen size via "
2786 ". This resolves the sluggishness reported with "
2788 ". [Correction: In version 1:1, this behavior is
2789 adaptive for all terminals, with the shortcut for "
2793 " that it doesn't bother trying the control sequence.]")
2794 (item "Documentation tweaks.")))
2796 (#:planet 1:0 #:date "2012-06-16"
2798 (item "Initial version."))))