I'am writing a program which capturing videos from 4 cameras simultaneously, so I have 4 threads to control each camera. In each thread I want it to continue capturing until I hit a key and that key corresponds to 'q' or something.
For keypress handle I search the Internet and found method like this:
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
int kbhit(int key) {
int ch;
int old_file_status;
struct termios old_term_attr;
struct termios new_term_attr;
tcgetattr(STDIN_FILENO, &old_term_attr);
new_term_attr = old_term_attr;
new_term_attr.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &new_term_attr);
old_file_status = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, old_file_status | O_NONBLOCK);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &old_term_attr);
fcntl(STDIN_FILENO, F_SETFL, old_file_status);
if(ch == c)
return 1;
return 0;
}
And in my VideoCapture class I have code like this (not complete):
static void *capureVideo(void *para) {
// Some code...
while(!(kbhit('q') {
// Read frame...
}
}
void creatThread() {
if (pthread_create(&threadID, NULL, capureVideo, this) != 0) {
perror("thread create faild");
exit(EXIT_FAILURE);
}
}
When the program runs, once I hit the key 'q' 4 times the program did quits and control is given back to the shell. But in some certain circumstances (I don't exactly know, it doesn't happen every time) it lead to a problem, that is when I then go and type commands into the shell, the characters I type don't show up. When I press enter the commands are submitted.
I search this problem and found this: https://askubuntu.com/a/172747, which indicates that my terminal attributes were not reset properly. But in the keypress handle code I noticed that this two lines of code
tcsetattr(STDIN_FILENO, TCSANOW, &old_term_attr);
fcntl(STDIN_FILENO, F_SETFL, old_file_status);
did reset the terminal attributes. So I wonder if it's relevant to the multithread. I'm new to mutlithread programming and couldn't solve it myself, so could someone help me? Any suggestions are highly appreciated.
You have multiple threads trying to change parameters of the (static) terminal at the same time:
tcgetattr(STDIN_FILENO, &old_term_attr);
new_term_attr = old_term_attr;
new_term_attr.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &new_term_attr);
Without this being locked, your terminal attributes are completely random.
The fixes are to either protect this with a mutex, or if you spawn another thread to read the keyboard and set a flag for when 'q' has been pressed; which your other threads can read, you can do something like
(pardon the psudo code)
bool shouldRun = true
void captureThreadMain {
while (shouldRun) {
captureFrame();
}
}
void keyboardPressMain {
while (getKey('q'));
shouldRun = false;
}
This will mean that you only need to press 'q' once to stop all your frame collecting threads.
Related
considering this code:
#include<cstdio>
extern"C"{
#include<termios.h>
#include<unistd.h>
}
int main(){
termios termattr_bak;
termios termattr;
/* switch to raw mode*/
tcgetattr(STDIN_FILENO,&termattr_bak);
termattr.c_iflag |= IGNBRK;
termattr.c_iflag &= ~unsigned(INLCR|ICRNL|IXON|IXOFF);
termattr.c_lflag &= ~unsigned(ICANON|ECHO|ECHOK|ECHOE|ECHONL|ISIG|IEXTEN);
termattr.c_cc[VMIN] = 1;
termattr.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO,TCSAFLUSH,&termattr);
/* */
printf("test\r\n");
printf("test\r\n");
printf("test\r\n");
fflush(stdout);
/* restore terminal attributes*/
tcsetattr(STDIN_FILENO,TCSAFLUSH,&termattr_bak);
return 0;
}
the program compiles with g++ main.cc and works just as expected around 80% of the time with this trace:
test
test
test
but sometimes, randomly, the trace is like this:
test
test
test
or this:
test
test
test
or even those above with capital letters (!?) like
TEST
TEST
TEST
and so on.
I just run the same program again and again in the same terminal with randomly the different behaviors above.
It is the same under both gnome-terminal and xterm.
I tried to fflush and/or tcflush and/or tcdrain at different locations with no success.
What is happening here and how do I prevent this ?
EDIT:
It seems that gcc -Og main.cc produces a different code that works correctly. So it looks to me like something is beeing broken by the optimisations, which means I am doing something wrong with the code which only shows up when optimized (but What ?), or less likely there is a bug with GCC
EDIT2:
As SergeBallesta mentionned, I was not initializing the termios structure before use, which was leading the random problems above.
This one works :
#include<cstdio>
extern"C"{
#include<termios.h>
#include<unistd.h>
}
int main(){
termios termattr_bak;
termios termattr{};//<<<<<<<<<<<<<<< zero-init the struct
/* switch to raw mode*/
tcgetattr(STDIN_FILENO,&termattr_bak);
termattr.c_iflag |= IGNBRK;
termattr.c_iflag &= ~unsigned(INLCR|ICRNL|IXON|IXOFF);
termattr.c_lflag &= ~unsigned(ICANON|ECHO|ECHOK|ECHOE|ECHONL|ISIG|IEXTEN);
termattr.c_cc[VMIN] = 1;
termattr.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO,TCSAFLUSH,&termattr);
/* */
printf("test\r\n");
printf("test\r\n");
printf("test\r\n");
fflush(stdout);
/* restore terminal attributes*/
tcsetattr(STDIN_FILENO,TCSAFLUSH,&termattr_bak);
return 0;
}
You should not be zero-initializing the termios structure. Get the current settings, make a copy, then adjust it for raw mode using cfmakeraw().
#define _DEFAULT_SOURCE
#include <termios.h>
#include <unistd.h>
struct termios initial_settings;
struct termios raw_settings;
void finalize()
{
printf( "\033[?1049l" );
tcsetattr( STDIN_FILENO, &initial_settings );
}
bool initialize()
{
if (tcgetattr( STDIN_FILENO, &initial_settings ) < 0)
return false;
raw_settings = initial_settings;
cfmakeraw( &raw_settings );
// adjust raw_settings if needed here //
// for example, for non-blocking, polling I/O: //
// raw_settings.c_cc[VMIN] = 0;
// raw_settings.c_cc[VTIME] = 0;
if (tcsetattr( STDIN_FILENO, TCSANOW, &raw_settings ) < 0)
return false;
// more initializations here, like: //
printf( "\033[?1049h" ); // alternate screen buffer mode
...
// Make sure to undo all the changes at program exit
atexit( finalize );
}
how to detect a keyboard event in C without prompting the user in linux? That is the program running should terminate by pressing any key.
can anyone please help with this?
You have to modify terminal settings using termios. See Stevens & Rago 2nd Ed 'Advanced Programming in the UNIX Environment' it explains why tcsetattr() can return successfuly without having set all terminal characteristcs, and why you see what looks to be redundant calls to tcsetattr().
This is ANSI C in UNIX:
#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
int checktty(struct termios *p, int term_fd)
{
struct termios ck;
return (
tcgetattr(term_fd, &ck) == 0 &&
(p->c_lflag == ck.c_lflag) &&
(p->c_cc[VMIN] == ck.c_cc[VMIN]) &&
(p->c_cc[VTIME] == ck.c_cc[VMIN])
);
}
int
keypress(int term_fd)
{
unsigned char ch;
int retval=read(term_fd, &ch, sizeof ch);
return retval;
}
int /* TCSAFLUSH acts like fflush for stdin */
flush_term(int term_fd, struct termios *p)
{
struct termios newterm;
errno=0;
tcgetattr(term_fd, p); /* get current stty settings*/
newterm = *p;
newterm.c_lflag &= ~(ECHO | ICANON);
newterm.c_cc[VMIN] = 0;
newterm.c_cc[VTIME] = 0;
return(
tcgetattr(term_fd, p) == 0 &&
tcsetattr(term_fd, TCSAFLUSH, &newterm) == 0 &&
checktty(&newterm, term_fd) != 0
);
}
void
term_error(void)
{
fprintf(stderr, "unable to set terminal characteristics\n");
perror("");
exit(1);
}
void
wait_and_exit(void)
{
struct timespec tsp={0,500}; /* sleep 500 usec (or likely more ) */
struct termios attr;
struct termios *p=&attr;
int keepon=0;
int term_fd=fileno(stdin);
fprintf(stdout, "press any key to continue:");
fflush(stdout);
if(!flush_term(term_fd, p) )
term_error();
for(keepon=1; keepon;)
{
nanosleep(&tsp, NULL);
switch(keypress(term_fd) )
{
case 0:
default:
break;
case -1:
fprintf(stdout, "Read error %s", strerror(errno));
exit(1);
break;
case 1: /* time to quit */
keepon=0;
fprintf(stdout, "\n");
break;
}
}
if( tcsetattr(term_fd, TCSADRAIN, p) == -1 &&
tcsetattr(term_fd, TCSADRAIN, p) == -1 )
term_error();
exit(0);
}
int main()
{
wait_and_exit();
return 0; /* never reached */
}
The nanosleep call is there to prevent the code from gobbling up system resources. You could call nice() and not use nanosleep(). All this does is sit and wait for a keystroke, then exit.
If you want to do that in a graphical application, you should use some libraries to do this.
Such a simple task can be easily done with whatever library (even low level ones like Xlib).
Just choose one and look for a tutorial that shows how to handle keyboard events.
no way with ANSI C. Look at ncurses lib.
Here’s code from /usr/src/bin/stty/key.c:
f_cbreak(struct info *ip)
{
if (ip->off)
f_sane(ip);
else {
ip->t.c_iflag |= BRKINT|IXON|IMAXBEL;
ip->t.c_oflag |= OPOST;
ip->t.c_lflag |= ISIG|IEXTEN;
ip->t.c_lflag &= ~ICANON;
ip->set = 1;
}
}
At a minimum, you have to get out of ICANON mode before your select(2) syscall or your FIONREAD ioctl will work.
I have an ancient, 20-year-old perl4 program that clears CBREAK and ECHO mode this way. It is doing curses stuff without resorting to the curses library:
sub BSD_cbreak {
local($on) = shift;
local(#sb);
local($sgttyb);
# global $sbttyb_t
$sgttyb_t = &sgttyb'typedef() unless defined $sgttyb_t;
# native BSD stuff by author (tsc)
ioctl(TTY,&TIOCGETP,$sgttyb)
|| die "Can't ioctl TIOCGETP: $!";
#sb = unpack($sgttyb_t,$sgttyb);
if ($on) {
$sb[&sgttyb'sg_flags] |= &CBREAK;
$sb[&sgttyb'sg_flags] &= ~&ECHO;
} else {
$sb[&sgttyb'sg_flags] &= ~&CBREAK;
$sb[&sgttyb'sg_flags] |= &ECHO;
}
$sgttyb = pack($sgttyb_t,#sb);
ioctl(TTY,&TIOCSETN,$sgttyb)
|| die "Can't ioctl TIOCSETN: $!";
}
sub SYSV_cbreak {
# SysV code contributed by Jeff Okamoto <okamoto#hpcc25.corp.hp.com>
local($on) = shift;
local($termio,#termio);
# global termio_t ???
$termio_t = &termio'typedef() unless defined $termio_t;
ioctl(TTY,&TCGETA,$termio)
|| die "Can't ioctl TCGETA: $!";
#termio = unpack($termio_t, $termio);
if ($on) {
$termio[&termio'c_lflag] &= ~(&ECHO | &ICANON);
$termio[&termio'c_cc + &VMIN] = 1;
$termio[&termio'c_cc + &VTIME] = 1;
} else {
$termio[&termio'c_lflag] |= (&ECHO | &ICANON);
# In HP-UX, it appears that turning ECHO and ICANON back on is
# sufficient to re-enable cooked mode. Therefore I'm not bothering
# to reset VMIN and VTIME (VEOF and VEOL above). This might be a
# problem on other SysV variants.
}
$termio = pack($termio_t, #termio);
ioctl(TTY, &TCSETA, $termio)
|| die "Can't ioctl TCSETA: $!";
}
sub POSIX_cbreak {
local($on) = shift;
local(#termios, $termios, $bitmask);
# "file statics" for package cbreak:
# $savebits, $save_vtime, $save_vmin, $is_on
$termios_t = &termios'typedef() unless defined $termios_t;
$termios = pack($termios_t, ()); # for Sun SysVr4, which dies w/o this
ioctl(TTY,&$GETIOCTL,$termios)
|| die "Can't ioctl GETIOCTL ($GETIOCTL): $!";
#termios = unpack($termios_t,$termios);
$bitmask = &ICANON | &IEXTEN | &ECHO;
if ($on && $cbreak'ison == 0) {
$cbreak'ison = 1;
$cbreak'savebits = $termios[&termios'c_lflag] & $bitmask;
$termios[&termios'c_lflag] &= ~$bitmask;
$cbreak'save_vtime = $termios[&termios'c_cc + &VTIME];
$termios[&termios'c_cc + &VTIME] = 0;
$cbreak'save_vmin = $termios[&termios'c_cc + &VMIN];
$termios[&termios'c_cc + &VMIN] = 1;
} elsif ( !$on && $cbreak'ison == 1 ) {
$cbreak'ison = 0;
$termios[&termios'c_lflag] |= $cbreak'savebits;
$termios[&termios'c_cc + &VTIME] = $cbreak'save_vtime;
$termios[&termios'c_cc + &VMIN] = $cbreak'save_vmin;
} else {
return 1;
}
$termios = pack($termios_t,#termios);
ioctl(TTY,&$SETIOCTL,$termios)
|| die "Can't ioctl SETIOCTL ($SETIOCTL): $!";
}
sub DUMB_cbreak {
local($on) = shift;
if ($on) {
system("stty cbreak -echo");
} else {
system("stty -cbreak echo");
}
}
And it elsewhere says that for POSIX,
($GETIOCTL, $SETIOCTL) = (TIOCGETA, TIOCSETA);
RE-translation back into the original C is left as an exercise for the reader, because I can't remember where the 20-years-ago-me snagged it from originally. :(
Once you're out of ICANON mode on the tty, now your select(2) syscall works properly again. When select's read mask returns that that descriptor is ready, then you do a FIONREAD ioctl to discover exactly how many bytes are waiting for you on that file descriptor. Having got that, you can do a read(2) syscall for just that many bytes, preferably on an O_NONBLOCK descriptor, although by now that should no longer be necessary.
Hm, here’s a foreboding note in /usr/src/usr.bin/vi/cl/README.signal:
Run in cbreak mode. There are two problems in this area. First, the
current curses implementations (both System V and Berkeley) don't give
you clean cbreak modes. For example, the IEXTEN bit is left on, turning
on DISCARD and LNEXT. To clarify, what vi WANTS is 8-bit clean, with
the exception that flow control and signals are turned on, and curses
cbreak mode doesn't give you this.
We can either set raw mode and twiddle the tty, or cbreak mode and
twiddle the tty. I chose to use raw mode, on the grounds that raw
mode is better defined and I'm less likely to be surprised by a curses
implementation down the road. The twiddling consists of setting ISIG,
IXON/IXOFF, and disabling some of the interrupt characters (see the
comments in cl_init.c). This is all found in historic System V (SVID
3) and POSIX 1003.1-1992, so it should be fairly portable.
If you do a recursive grep for \b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b on the non-kernel portions of /usr/src/, you should find stuff you can use. For example:
% grep -Pr '\b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b' /usr/src/{{s,}bin,usr.{s,}bin,etc,gnu}
I would look at /usr/src/usr.bin/less/screen.c, down in the raw_mode() function. Riddled with ifdefs though it is in a quest for portability, that looks like the cleanest code for what you want to do. There’s also stuff lurking down in GNU.
OH MY, look in /usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl! That must be my old code that I posted above. Interesting that it’s trickled out to every src system in the world. Scary, too, since it is twenty years out of date. Gosh, it's weird to see echoes of a younger self. Really hard to remember such particulars from 20 years ago.
I also see in /usr/src/lib/libcurses/term.h this line:
#define tcgetattr(fd, arg) ioctl(fd, TCGETA, arg)
in a bunch of ifdefs that are trying to infer termio or termios availability.
This should be enough to get you started.
I currently have a C/C++ program which uses a barcode scanner as a keyboard, catches the input and does something with it. Here's the relevant parts of code:
int get_InStream() {
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return FD_ISSET(STDIN_FILENO, &fds);
}
void nonblock(int state) {
struct termios ttystate;
tcgetattr(STDIN_FILENO, &ttystate);
if (state == 1) {
// ~ICANON: turn off canonical mode
// ~ECHO: not display character
//~ ttystate.c_lflag &= ~ECHO; // (ICANON & ECHO);
tcgetattr( 0, &ttystate); /* read curr. setting */
original_mode = ttystate; /* remember these */
ttystate.c_lflag &= ~ICANON; /* no buffering */
//~ ttystate.c_lflag &= ~ECHO; /* no echo either */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
//minimum of number input read.
ttystate.c_cc[VMIN] = 1;
}
else if (state == 0) {
//~ // turn on canonical mode
//~ ttystate.c_lflag |= ECHO;
tcsetattr(0, TCSANOW, &original_mode); /* undo -icanon, -echo */
}
// set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
}
bool keyState(int key) { // Uses ASCII table
bool pressed = false;
int i = get_InStream(); // Allows to read from terminal
if (i != 0) {
char c = fgetc(stdin);
if (c == (char) key) {
pressed = true;
} else {
pressed = false;
char string_key = c;
pthread_mutex_lock(&id_mutex);
// Append character to content buffer
strcat(content, string_key);
pthread_mutex_unlock(&id_mutex);
}
}
return pressed;
}
void* get_inputContent(void* arg) {
pthread_detach(pthread_self());
nonblock(1);
while (1) {
// if this returns True, content contains data
if (keyState(0x0A)) { // 0x0A in ASCII table corresponds to New Line
pthread_mutex_lock(&id_mutex);
printf("Read this %d characters through barcode scanner: %s\n", strlen(content), content); //DEBUG
// doSomething()
strcpy(content, "\0"); // empty out content
pthread_mutex_unlock(&id_mutex);
}
}
nonblock(0);
pthread_exit(NULL);
}
Right now this works well as a separate thread from the main program, but if I open another terminal while the main program is running and I leave the focus on the new one, the barcode input is not caught by the thread.
So I'd like, either in C/C++ or in Bash, to let's say share the input accross various terminals, so that my thread can use it. Is there any way to do this?
I've searched for various options:
another descriptor to use in select()
using export in Bash
writing to a shared file
but I'm not so sure for any of those. Any suggestions?
EDIT: the program is being run on Raspberry Pi OS, previously Raspbian if I'm not mistaken
This is a XY problem situation right here. Your problem 'X' is
How can I access the keyboard device as which the barcode scanner presents itself to the system regardless of the current state of the system?
But you think, that by solving the problem 'Y'
How can I keygrab input directed to a different terminal?
Problem Y is hard, because it has security implications. Problem X is easy, but its solution depends on the operating system being used.
You mention bash and POSIX style file descriptor. So I'm guessing, that you're on some flavor of Linux. And with that, the problem is easy to solve! Each and every input device presents itself as a evdev device under /dev/input/event<X>. Your barcode scanner will show up there, as well.
You could either implement the evdev protocol yourself. Or just use libinput to do the heavy lifting.
And it being present as an independent input device allows you to do a few things:
Use udev to control which user accounts get access to it.
Use udev to actually detach it from the terminals, so that the barcode scanner can not be used to input (possibly harmful) commands.
I'm writing a VT100 terminal engine but input handling is somehow tricky.
First I initialize my tty with no echoing.
static void init_tty() {
if (tcgetattr(STDIN_FILENO, &ctty) != 0)
throw std::runtime_error("can not get tty attribute");
ctty.c_lflag &= ~(FLAG(ICANON) | FLAG(ECHO) | FLAG(ISIG));
if (tcsetattr(STDIN_FILENO, TCSANOW, &ctty) != 0)
throw std::runtime_error("can not set tty attribute");
}
Next I initialize a non-blocking keyboard file descriptor.
static void init_keyboard(const char *device) {
if ((kfd = open(device, FLAG(O_RDONLY) | FLAG(O_SYNC))) == -1)
throw std::runtime_error("can not open keyboard fd");
unsigned flags = fcntl(kfd, F_GETFL, 0);
flags |= FLAG(O_NONBLOCK);
if (fcntl(kfd, F_SETFL, flags) == -1)
throw std::runtime_error("can not set keyboard fd flags");
}
So far so good. The main loop is just polling events and will break if pressing ESC.
int main() {
while (true) {
read(kfd, evt, sizeof(input_event));
if (evt->type == EV_KEY && evt->value == 1 && evt->code == KEY_ESC)
break;
}
}
So I type some characters. Meanwhile my terminal is buffering all characters that I am typing in. After the program exits all characters got displayed.
So here my question: How can I disable terminal output stream buffering while my program is running?
Here is a GIF to illustrate the problem.
Steps:
sudo ./clac
engine is running but not echoing; typing some characters
pressing ESC
typed in characters got displayed; program exit
The program should not display typed keys thats why i turned of echoing but in the background the keys get buffered. and when the program exits those characters got printed. like i have to flush some buffers befor exit.
This can be accomplished by calling
tcflush(STDIN_FILENO, TCIFLUSH);
just before exiting; see tcflush(3) - Linux man page.
I've just solve my problem. (https://unix.stackexchange.com/questions/126974/where-do-i-find-ioctl-eviocgrab-documented)
So I am grabbing all events immediately after my program starts including ENTER
Example: sudo ./clac ENTER
The solution was to add 1000 milliseconds delay at the start and then GRAB the fd events with
std::this_thread::sleep_for(std::chrono::seconds(1));
if (ioctl(kfd, EVIOCGRAB, FD_GRAB) == -1)
throw std::runtime_error("can not set keyboard fd grab");
And when the program exits UNGRAB the fd with
if (ioctl(kfd, EVIOCGRAB, FD_UNGRAB) == -1)
throw std::runtime_error("can not set keyboard fd ungrab");
How do you do nonblocking console IO on Linux/OS X in C?
I want to add an example:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
char buf[20];
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
sleep(4);
int numRead = read(0, buf, 4);
if (numRead > 0) {
printf("You said: %s", buf);
}
}
When you run this program you have 4 seconds to provide input to standard in. If no input found, it will not block and will simply return.
2 sample executions:
Korays-MacBook-Pro:~ koraytugay$ ./a.out
fda
You said: fda
Korays-MacBook-Pro:~ koraytugay$ ./a.out
Korays-MacBook-Pro:~ koraytugay$
Like Pete Kirkham, I found cc.byexamples.com, and it worked for me. Go there for a good explanation of the problem, as well as the ncurses version.
My code needed to take an initial command from standard input or a file, then watch for a cancel command while the initial command was processed. My code is C++, but you should be able to use scanf() and the rest where I use the C++ input function getline().
The meat is a function that checks if there is any input available:
#include <unistd.h>
#include <stdio.h>
#include <sys/select.h>
// cc.byexamples.com calls this int kbhit(), to mirror the Windows console
// function of the same name. Otherwise, the code is the same.
bool inputAvailable()
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return (FD_ISSET(0, &fds));
}
This has to be called before any stdin input function When I used std::cin before using this function, it never returned true again. For example, main() has a loop that looks like this:
int main(int argc, char* argv[])
{
std::string initialCommand;
if (argc > 1) {
// Code to get the initial command from a file
} else {
while (!inputAvailable()) {
std::cout << "Waiting for input (Ctrl-C to cancel)..." << std::endl;
sleep(1);
}
std::getline(std::cin, initialCommand);
}
// Start a thread class instance 'jobThread' to run the command
// Start a thread class instance 'inputThread' to look for further commands
return 0;
}
In the input thread, new commands were added to a queue, which was periodically processed by the jobThread. The inputThread looked a little like this:
THREAD_RETURN inputThread()
{
while( !cancelled() ) {
if (inputAvailable()) {
std::string nextCommand;
getline(std::cin, nextCommand);
commandQueue.lock();
commandQueue.add(nextCommand);
commandQueue.unlock();
} else {
sleep(1);
}
}
return 0;
}
This function probably could have been in main(), but I'm working with an existing codebase, not against it.
For my system, there was no input available until a newline was sent, which was just what I wanted. If you want to read every character when typed, you need to turn off "canonical mode" on stdin. cc.byexamples.com has some suggestions which I haven't tried, but the rest worked, so it should work.
You don't, really. The TTY (console) is a pretty limited device, and you pretty much don't do non-blocking I/O. What you do when you see something that looks like non-blocking I/O, say in a curses/ncurses application, is called raw I/O. In raw I/O, there's no interpretation of the characters, no erase processing etc. Instead, you need to write your own code that checks for data while doing other things.
In modern C programs, you can simplify this another way, by putting the console I/O into a thread or lightweight process. Then the I/O can go on in the usual blocking fashion, but the data can be inserted into a queue to be processed on another thread.
Update
Here's a curses tutorial that covers it more.
I bookmarked "Non-blocking user input in loop without ncurses" earlier this month when I thought I might need non-blocking, non-buffered console input, but I didn't, so can't vouch for whether it works or not. For my use, I didn't care that it didn't get input until the user hit enter, so just used aio to read stdin.
Here's a related question using C++ -- Cross-platform (linux/Win32) nonblocking C++ IO on stdin/stdout/stderr
Another alternative to using ncurses or threads is to use GNU Readline, specifically the part of it that allows you to register callback functions. The pattern is then:
Use select() on STDIN (among any other descriptors)
When select() tells you that STDIN is ready to read from, call readline's rl_callback_read_char()
If the user has entered a complete line, rl_callback_read_char will call your callback. Otherwise it will return immediately and your other code can continue.
Let`s see how it done in one of Linux utilites. For example, perf/builtin-top.c sources (simplified):
static void *display_thread(void *arg)
{
struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
struct termios save;
set_term_quiet_input(&save);
while (!done) {
switch (poll(&stdin_poll, 1, delay_msecs)) {
...
}
}
tcsetattr(0, TCSAFLUSH, &save);
}
So, if you want to check if any data available, you can use poll() or select() like this:
#include <sys/poll.h>
...
struct pollfd pfd = { .fd = 0, .events = POLLIN };
while (...) {
if (poll(&pfd, 1, 0)>0) {
// data available, read it
}
...
}
In this case you will receive events not on each key, but on whole line, after [RETURN] key is pressed. It's because terminal operates in canonical mode (input stream is buffered, and buffer flushes when [RETURN] pressed):
In canonical input processing mode, terminal input is processed in
lines terminated by newline ('\n'), EOF, or EOL characters. No input
can be read until an entire line has been typed by the user, and the
read function (see Input and Output Primitives) returns at most a
single line of input, no matter how many bytes are requested.
If you want to read characters immediately, you can use noncanonical mode. Use tcsetattr() to switch:
#include <termios.h>
void set_term_quiet_input()
{
struct termios tc;
tcgetattr(0, &tc);
tc.c_lflag &= ~(ICANON | ECHO);
tc.c_cc[VMIN] = 0;
tc.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &tc);
}
Simple programm (link to playground):
#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>
#include <termios.h>
void set_term_quiet_input()
{
struct termios tc;
tcgetattr(0, &tc);
tc.c_lflag &= ~(ICANON | ECHO);
tc.c_cc[VMIN] = 0;
tc.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &tc);
}
int main() {
struct pollfd pfd = { .fd = 0, .events = POLLIN };
set_term_quiet_input();
while (1) {
if (poll(&pfd, 1, 0)>0) {
int c = getchar();
printf("Key pressed: %c \n", c);
if (c=='q') break;
}
usleep(1000); // Some work
}
}
Not entirely sure what you mean by 'console IO' -- are you reading from STDIN, or is this a console application that reads from some other source?
If you're reading from STDIN, you'll need to skip fread() and use read() and write(), with poll() or select() to keep the calls from blocking. You may be able to disable input buffering, which should cause fread to return an EOF, with setbuf(), but I've never tried it.