LinuxQuestions.org
Visit Jeremy's Blog.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Reply
 
LinkBack Search this Thread
Old 04-29-2004, 03:04 PM   #1
mvt
LQ Newbie
 
Registered: Apr 2004
Location: Lake City, Florida, USA
Distribution: Red Hat and SuSE
Posts: 9

Rep: Reputation: 0
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.
 
Old 04-29-2004, 03:55 PM   #2
infamous41md
Member
 
Registered: Mar 2003
Posts: 804

Rep: Reputation: 30
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
 
Old 04-29-2004, 04:05 PM   #3
ToniT
Senior Member
 
Registered: Oct 2003
Location: Zurich, Switzerland
Distribution: Debian/unstable
Posts: 1,357

Rep: Reputation: 47
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).
 
Old 05-17-2004, 03:00 PM   #4
mvt
LQ Newbie
 
Registered: Apr 2004
Location: Lake City, Florida, USA
Distribution: Red Hat and SuSE
Posts: 9

Original Poster
Rep: Reputation: 0
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
// "Print Screen" runs a screen capture program
// "Scroll Lock" is intercepted, unknown action.
// "Pause" is intercepted, unknown action.
// "Num Lock" toggles status light (but not received values)
// keypad "home", "End", "PgUp", and "PgDn" 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 << "Press 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;
}
// ---------------------------------------------------------------------------
 
Old 05-17-2004, 04:53 PM   #5
ToniT
Senior Member
 
Registered: Oct 2003
Location: Zurich, Switzerland
Distribution: Debian/unstable
Posts: 1,357

Rep: Reputation: 47
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
 
Old 05-17-2004, 04:55 PM   #6
Hko
Senior Member
 
Registered: Aug 2002
Location: Groningen, The Netherlands
Distribution: ubuntu
Posts: 2,524

Rep: Reputation: 93
This thread may be of some help too:

http://www.linuxquestions.org/questi...threadid=34027

Last edited by Hko; 05-17-2004 at 04:58 PM.
 
Old 05-17-2004, 07:23 PM   #7
mvt
LQ Newbie
 
Registered: Apr 2004
Location: Lake City, Florida, USA
Distribution: Red Hat and SuSE
Posts: 9

Original Poster
Rep: Reputation: 0
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
 
Old 05-17-2004, 09:40 PM   #8
ToniT
Senior Member
 
Registered: Oct 2003
Location: Zurich, Switzerland
Distribution: Debian/unstable
Posts: 1,357

Rep: Reputation: 47
-No echo of characters: noecho();
-Want it to follow it to next line: nl(); or dont want? nonl();
-The scrolling: scrollok(stdscr,TRUE);
-Own ctrl-c handling: raw();


I'm not sure if there is an easy way to prevent ncurses from using alternate screen.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off
Trackbacks are Off
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
unbuffered stdin Mike Davies Linux - Software 1 03-03-2006 12:13 AM
Unbuffered Block Device rtg Linux - Hardware 2 09-02-2005 06:50 AM
stdout stdin Furlinastis Linux - Newbie 3 08-11-2005 11:00 PM
Statistics from stdin? edeca Linux - Software 0 05-23-2005 10:03 AM
stdin and NUL. How ? Mike Davies Linux - Software 5 10-28-2004 07:06 AM


All times are GMT -5. The time now is 05:39 AM.

Main Menu
 
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: @linuxquestions
Open Source Consulting | Domain Registration