|
发表于 2004-5-18 08:55:49
|
显示全部楼层
正巧在国外论坛也看到了这个话题,供大家参考
unbuffered stdin
Hi, 29 April 2004
I've looked at an awful lot of posted question/answers, FAQs, and web sites, and have tried the methods they suggest, and I'm still stuck.
I'm programming in C++ on Red Hat Linux version 9.
I need to intercept individual characters from stdin as they are typed so that I can do custom line editing within a program. (In other words, I can not wait for the user to enter a newline before the program processes the keystrokes.) I have done this in the past using graphical programs (for instance glut and OpenGL), but I can't find a method that works for the normal shell window.
All the resources have said it is buffering in the terminal program that must be turned off, and that the method is system dependent. The various system calls seem to work without returning an error code, but do not yield a behavior change in the terminal.
I am amazed that this is so hard to find, because it isn't all that unusual of a need. For instance, "more", "top", and other programs display into the shell's teminal window and yet process individual keystrokes as commands. For that matter, so does any shell program. (I'd check the source of one of these programs, but the only way I seem able to get the source is to install the full Linux kernel source, which seems excessive.)
Does anyone have ideas or working code?
The following simple test program demonstrates the capability that I need.
When I run this program now, as I press keyboard keys, nothing is displayed until a newline is entered, and then all the output is written.
What I want:
If any keyboard key is pressed, then the character is immediately written twice.
int main() {
while (true) {
int val = getc(stdin);
cerr << char(val) << char(val);
}
return 0;
}
Thanks,
Mark
P.S. - I may not be able to respond quickly to any questions as I'm traveling for two weeks and unsure of Internet access.
--------------------------------------------------------------------------------
Posted by infamous41md on 04-30-2004 04:55 AM:
you need to mess with termios settings. check out the source code for chapter 11:
http://www.kohala.com/start/apue.html
u need to set it so that terminal driver only waits for a single character before returning. it's all in the source, i reaaaaaly don't feel like looking at it tho
__________________
HOW TO USE CODE TAGS
my site && wishlist
Three bugs every C* programmer should understand:
buffer overflows format strings signed/unsigned still hungry?
--------------------------------------------------------------------------------
Posted by ToniT on 04-30-2004 05:05 AM:
See the ncurses programming howto on how to code unbuffered keyhandler.
The sources of more, top can be obtained without installling the full linux source code.
If you are doing commandline editing, I suggest using readline library.
Not just to avoid reinventing the wheel, but that way your program is having a consistent
look&feel with other terminal programs (bash, tcsh, ncftp, lftp, octave, ahcd, parted, smbclient, mysql/postgres-client, R, devtodo, cle and abook just to mention few I use that use readline).
--------------------------------------------------------------------------------
Posted by mvt on Today 04:00 AM:
Thank you both for the help. 17 May 2004
infamous41md's suggestion led me to searching about
termios. A web page (quoted below) helped with
the needed syntax. Result: a solution.
In the interest of closure and to assist other readers,
attached below is my working C++ test program (it
consists mostly of comments).
Thanks,
Mark
/*
The following 3 commands compile this on my system.
/bin/rm -f stdin_test.o stdin_test
g++ -c stdin_test.cc
g++ stdin_test.o -lcurses -o stdin_test
*/
// program accepts charaters one at a time from stdin
// without waiting for a newline (unbuffered)
// based on: http://www.erlenstar.demon.co.uk/unix/faq_4.html
//
// Modify the values of vtime and vmin (at appoximately line 62, below)
// to try different modes. The current values are: vtime = 1 and vmin = 0
// which allows the main program to poll for characters. Using vtime=1
// intentionally slows the speed of the main loop, changing vtime to zero
// will greatly increase the speed of the main loop.
//
// On my system, the some keys are still intercepted:
// "F1" calls up the "Gnome Terminal manual"
// "F10" opens a menu
// " rint Screen" runs a screen capture program
// "Scroll Lock" is intercepted, unknown action.
// " ause" is intercepted, unknown action.
// "Num Lock" toggles status light (but not received values)
// keypad "home", "End", " gUp", and " gDn" scroll the terminal
#include <iostream>
#include <termios.h>
using namespace std;
// ---------------------------------------------------------------------------
void set_keypress(termios& stored_settings) {
// change the terminal settings to return each character as it is typed
// (disables line-oriented buffering)
// returns the original settings in the argument structure
// obtain the current settings flags
tcgetattr(0, &stored_settings);
// copy existing setting flags
termios new_settings = stored_settings;
// modify flags
// first, disable canonical mode
// (canonical mode is the typical line-oriented input method)
new_settings.c_lflag &= (~ICANON);
new_settings.c_lflag &= (~ECHO); // don't echo the character
new_settings.c_lflag &= (~ISIG); // don't automatically handle control-C
// vtime and vmin setting interactions are complex
// both > 0
// Blocks until it has first new character, then tries to get a total
// of vmin characters, but never waits more than vtime between characters.
// Returns when have vmin characters or the wait for next character is
// too long.
// vtime = 0, vmin > 0
// Blocks until vmin characters received (or a signal is received)
// vtime > 0, vmin = 0
// If a character is ready within vtime, it is returned immediately.
// If no character is avalable within vtime, zero is returned.
// both = 0
// Documentation somewhat unclear, but apparently returns immediately
// with all available characters up to the limit of the number
// requested by a read(). Returns -1 if no characters are available.
new_settings.c_cc[VTIME] = 1; // timeout (tenths of a second)
new_settings.c_cc[VMIN] = 0; // minimum number of characters
// apply the new settings
tcsetattr(0, TCSANOW, &new_settings);
// note:
// The return value from tcsetattr() is not tested because its value
// reflects "success" if any PART of the attributes is changed, not
// when all the values are changed as requested (stupidity!).
// Since the content of the termios structure may differ with
// implementation, as may the various constants such as ICANON, I see
// no elegant way to check if the desired actions were completed
// successfully. Comparing byte-by-byte shows the current state is
// NOT EQUAL to the requested state, and yet it runs, so the changes
// were apparently made. Can not check for success/failure.
}
// ---------------------------------------------------------------------------
void reset_keypress(const termios& stored_settings) {
// applies the terminal settings supplied as the argument
tcsetattr(0, TCSANOW, &stored_settings);
}
// ---------------------------------------------------------------------------
int main() {
// save the previous settings while modifying the current settings
termios stored_settings;
set_keypress(stored_settings);
cout << " ress keys... exit with a control-C." << endl;
// loop to poll for characters
while (true) {
int val = getc(stdin);
cerr << " " << val << endl;
if (val == 3) {
cout << "Saw control-c, exiting." << endl;
// return to original settings
reset_keypress(stored_settings);
exit(0);
}
}
return 0;
}
// ---------------------------------------------------------------------------
--------------------------------------------------------------------------------
Posted by ToniT on Today 05:53 AM:
Same with ncurses:
code:#include <ncurses.h>
int main(void) {
int ch;
initscr();
cbreak();
for(;;) {
ch=getch();
printw("%c",ch);
}
endwin();
}
Compile the program with flags: -lncurses
eg. gcc -Wall -lncurses wkeyc.c -o wkey
--------------------------------------------------------------------------------
Posted by Hko on Today 05:55 AM:
This thread may be of some help too:
http://www.linuxquestions.org/quest...;threadid=34027
--------------------------------------------------------------------------------
Posted by mvt on Today 08:23 AM:
In reply to ToniT's post (#5).
Thanks for the response.
I had tried ncurses when you first suggested it, and tried your code again just now. It works reasonably well except that initscr() creates a new display surface which is used for program output and which disappears upon program completion. I need to be able to see the output from previous comands so I can cut and paste text into my program... and after the program exits I need to be able to cut and paste text output by the program into other programs.
I could not figure out how to prevent getting a new display surface, although I suspect it is possible.
Other issues, perhaps also possible. (I've forgotten what I read in the ncurses docs, because I've looked at so many programs recently.)
I prefer to handle control-Z and control-C myself.
I prefer no automatic echo of characters.
The display no longer scrolls when text fills it.
Mark
|
|