Version 0 of Reading a single character from the keyboard using Tcl

Updated 2005-09-08 12:34:23 by dkf

DKF, 8-Sep-2005

How to read a single character from the keyboard using just Tcl? (It's pretty easy using a Tk GUI, of course!) Why doesn't just doing [read stdin 1] work?

Well, in fact it does work, but only if the terminal isn't working in line-editing mode. In line-editing mode, the OS terminal engine only sends the text to the applications once the user presses the return key. Guess what mode the terminal is in by default? :^/

(A little terminology. We're trying to switch things to raw mode here so we can read the "raw" keystrokes, with the default line-editing mode often called cooked mode by contrast.)


Raw Mode on Unix

On Unix platforms (e.g. Linux, Solaris, MacOSX, AIX, etc.) you can use the stty program to turn raw mode on and off, like this:

  exec /bin/stty raw <@stdin
  set c [read stdin 1]
  exec /bin/stty -raw <@stdin

(We use the <@stdin because stty works out what terminal to work with using standard input on some platforms. On others it prefers /dev/tty instead, but putting in the redirection makes the code more portable.)

However, it is usually a good idea to turn off echoing of characters in raw mode. It means that you're responsible for everything, but that's often what you want anyway. Wrapping things up in some procedures, we get this:

  proc enableRaw {{channel stdin}} {
     exec /bin/stty raw -echo <@stdin
  }
  proc disableRaw {{channel stdin}} {
     exec /bin/stty -raw echo <@stdin
  }

  enableRaw
  set c [read stdin 1]
  puts -nonewline $c
  disableRaw

Raw Mode on Windows

A different approach is needed on WinNT. This requires the twapi extension.

  package require twapi
  proc enableRaw {{channel stdin}} {
     set console_handle [twapi::GetStdHandle -10]
     set oldmode [twapi::GetConsoleMode $console_handle]
     set newmode [expr {$oldmode & ~6}] ;# Turn off the echo and line-editing bits
     twapi::SetConsoleMode $console_handle $newmode
  }
  proc disableRaw {{channel stdin}} {
     set console_handle [twapi::GetStdHandle -10]
     set oldmode [twapi::GetConsoleMode $console_handle]
     set newmode [expr {$oldmode | 6}] ;# Turn on the echo and line-editing bits
     twapi::SetConsoleMode $console_handle $newmode
  }

  enableRaw
  set c [read stdin 1]
  puts -nonewline $c
  disableRaw

This code was adapted from the Echo-free password entry page with the help of the appropriate page on MSDN[L1 ].


[Category ?]