I'm writing a program in C++ for Linux. I plan on using popen() to run another program. While I'm waiting for that program to finish, I want to be able to detect if the user presses keys on the keyboard so I know if they want to continue processing their files or if they want to pause after pclose(). What is the best way to accomplish this?
You can use a thread waiting for a key before calling popen, call popen, cancel the thread and check if any key was pressed before calling pclose.
An example using C (it is trivial to adapt it to C++), I'm ommiting error checks for brevity:
#define _XOPEN_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *handler(void *arg)
{
int *key = arg;
char c;
while (read(STDIN_FILENO, &c, 1))
{
if (*key == 0)
{
*key = c;
}
}
return NULL;
}
int main(void)
{
pthread_t thread;
int key = 0;
pthread_create(&thread, NULL, handler, &key);
// Simulate a long read using sleep 5
// to give you enough time to press a key + enter
FILE *cmd = popen("sleep 5; echo 'command finished'", "r");
char str[1024];
while (fgets(str, sizeof str, cmd))
{
printf("%s", str);
}
pthread_cancel(thread);
pthread_join(thread, NULL);
if (key != 0)
{
printf("%c was pressed\n", key);
}
pclose(cmd);
return 0;
}
EDIT:
Someone suggested in comments to use select instead of pthreads, I don't see much advantage in using select but here we go:
select() allows to wait until a file descriptor (in this case stdin) become ready, it can be connected to a timeout that specifies the interval that select() should block waiting for the file descriptor to become ready, in this case, since we want to read what is pending on stdin as soon as popen finishes, we set the timeout to 0, at the end we need to clean stdin, otherwise we end up poluting the terminal with garbage if the user starts typing but doesn't hit enter, or types more than one digit pressing enter.
IMHO threads are a cleaner alternative in this case. Same approach using select:
#define _XOPEN_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <termios.h>
void clear_stdin(void)
{
int stdin_copy = dup(STDIN_FILENO);
tcdrain(stdin_copy);
tcflush(stdin_copy, TCIFLUSH);
close(stdin_copy);
}
int keypress(int state)
{
int key = 0;
// state 0 = timeout (nothing to get)
if (state == 1)
{
char c;
if (read(STDIN_FILENO, &c, 1))
{
key = c;
}
}
clear_stdin();
return key;
}
int main(void)
{
fd_set set;
FD_ZERO(&set);
FD_SET(STDIN_FILENO, &set);
FILE *cmd = popen("sleep 5; echo 'command finished'", "r");
char str[1024];
while (fgets(str, sizeof str, cmd))
{
printf("%s", str);
}
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
int state = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
int key = keypress(state);
if (key != 0)
{
printf("%c was pressed\n", key);
}
pclose(cmd);
return 0;
}