''[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? :^/ In line-editing mode (at least on Unix-like systems), the operating system is doing things like buffering up the data (to make I/O more efficient, providing for functionality like backspacing (perhaps ^H/^?), interruption signaling (perhaps ^C/^?), word deletion, line deletion, line refresh, literal quoting (perhaps ^V), and more. The buffering, for example, means that an application cannot get any input until the user has pressed the Enter/Return type key. Normally, you want this type of pre-processing - these are the things that, at a command line, an application doesn't want to have to worry about. However, often in a text user interface, you want more control than that. (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], [MacOS X], [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 <@$channel } proc disableRaw {{channel stdin}} { exec /bin/stty -raw echo <@$channel } enableRaw set c [read stdin 1] puts -nonewline $c disableRaw ---- '''Raw Mode on Windows''' A different approach is needed on [Microsoft Windows] NT-based systems. 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[http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/getconsolemode.asp]. If you are using TWAPI 0.7 or above, you can use modify_console_input_mode[http://twapi.sf.net/console.html#modify_console_input_mode] to simplify the above code. (Apparently, you're stuck on 95/98/ME.) ---- [schlenk] 8/Sep/2005 The same API exists on Win9x according to the MSDN article referred to above, so TWAPI may work, but it is untested and unsupported on windows 9x. '''[PWQ]''' ''10 Sept 05'' Whats wrong with using fileevents: proc readsingle {} {puts "I read '[read stdin 1]' char} fileevent stdin read readsingle This should work on all platforms [DKF]: Once you're in raw mode, using [fileevent] is exactly the way to do it (assuming you want to keep the event loop active, of course). In cooked mode, the data only ever comes to Tcl in the first place by whole lines, so fileevent works, but isn't too useful for when you want to read less than a line (e.g. a single char). And if stdin were [fconfigure]d ''-buffering none''? ---- [JMN] 2005-09-28 So how would one go about getting the non-ascii keypresses such as arrow keys from a console app under windows? For Unix systems (I tried it on FreeBSD) the script on http://wiki.tcl.tk/12969 works ok. On windows however - even if I put the console in raw mode using either the cygwin stty utility or using [twapi] - various keys such as arrow keys never seem to result in any data on stdin. 2005-10-03 Answering my own question.. I've reached a satisfactory result using the dll package here: http://wiki.tcl.tk/12264 to call the kernel32 functions: CreateFileA & ReadConsoleInputW. Declared using: dll::load kernel32 -> k ::k::cmd "int ReadConsoleInputW(int, char *, int, int*)" ::k::cmd "int CreateFileA(char *, int, int, int *, int, int, int)" d using: The ReadConsoleInput function allows you to supply a buffer which gets filled with various events including keypresses (keyup & keydown) & window focus events. Of course - you have to work out what bytes in the buffer correspond to what parts of the Windows API structures - and you may end up having to map your keyboard scan-codes to ascii values or in the case of arrow-keys etc - to ansi escape sequences. It's a lot of work because you also have to do things such as track the state of modifier keys such as shift & ctrl to change the values you send. http://vt100.net can be helpful for working out some of the sequences and general ideas behind terminals. I then use the [Memchan] package to create a fifo2. One end of the fifo2 can then be transferred to another thread using thread::transfer ([Thread] package) This allows you to have the ReadConsoleInput handler running in its own thread, pumping keypress information to your main thread over a channel which you can read instead of stdin. i.e the net result is that you can get basically the same information (and more if needed) you'd get from a raw-mode stdin on a unix platform. Using CreateFileA on the special file name {conin$} you get a handle for console input (instead of 'GetStdHandle -10' to get stdin). Personally I like the idea of having console data available from a channel other than stdin - so stdin can be used for other data whilst allowing interaction on the console. Next question... How do I intercept console keypress events on unix platforms so I can keep stdin free? ---- I tried to use the script given as an example in the paragraph titled '''Raw Mode on Unix''', but I am getting exception like, /bin/stty: standard input: Invalid argument Am I missing something? Do I need to add anything to script other than the one given here? [LV] What version of unix are you using? ---- [[[Category Example]|[Category Device Control]]]