I've read from the GNU getline documentation that it's capable for binding some callback functions to some keys. I know already how to bind an action to the TAB key using rl_bind_key function.
But how can I use it to bind some action to the following keys?:
CTRL + TAB, ESC, PAUSE/BREAK
#include <stdio.h>
#include <readline/readline.h>
int my_cool_readline_func (int count, int key) {
printf ("key pressed: %d\n", key);
rl_on_new_line ();
return 0;
}
int main(void) {
rl_command_func_t my_cool_readline_func;
rl_bind_key ('\t', my_cool_readline_func);
rl_bind_key (27, my_cool_readline_func); /* ascii code for ESC */
rl_bind_keyseq ("\\C-a", my_cool_readline_func);
while (1) {
char *line = readline ("rl> ");
}
}
If your are running a GNU system (or one of its variants) then run:
info readline "command line editing" "introduction" # notation convention
info readline "programming" "readline" "biding" # biding functions
Related
I'm trying to write a pty I/O transparent filter for a shell.
The following example mostly works. Most programs run as expected with the wrapper. This example does not do any filtering, it's purpose is just to provide a framework.
EDIT: With my answer below I got this example working. I've updated the example here to reflect that.
Here is the now working code:
/*
This example is public domain. Use as you see fit.
The purpose of this example is show how a process can run a shell transparently and be able to filter it's input and output.
This example does not show off any I/O filtering, it only provides the framework on which that could be added.
Tested only on GNU/Linux with recent kernels and recent g++ and clang++
This example is based on original found here:
https://www.scriptjunkie.us/wp-content/uploads/2011/04/stdioterminallogger.c
There were 2 problems with the code this example was based on.
1) Terminal (re)sizing was not being handled
2) Some applications display incorrectly or keys don't work
a) with 'joe', Enter, Ctrl-M, and Ctrl-J don't work
b) 'joe' has display isues
c) 'snake' (game) has display issues
Also, be aware of this:
#define LOGFILELOCATION "/tmp/.shlog"
in the original code, not this example.
This example does not write produce any files. (intermediate or otherwise)
The following programs do seem to work correctly:
vi, vim, nano, mcedit, htop, top
#1 has been solved with a resize handler (see handler and handleTerminalResize)
Use this in shell's profile/bashrc to indicate pty-filter is present
[ "${inptyfilter}" == "true" ] && PS1="(pty-filter) ${PS1}"
Compile with any of the following:
g++ -std=c++11 pty-filter.cpp -lutil -o pty-filter
g++ -std=c++1y pty-filter.cpp -lutil -o pty-filter
g++ -std=c++1z pty-filter.cpp -lutil -o pty-filter
clang++ -std=c++11 pty-filter.cpp -lutil -o pty-filter
clang++ -std=c++1y pty-filter.cpp -lutil -o pty-filter
clang++ -std=c++1z pty-filter.cpp -lutil -o pty-filter
# for stricter compilation:
clang++ -std=c++1z pty-filter.cpp -lutil -o pty-filter -Wall -Werror -Weverything -Wno-c++98-compat -Wno-missing-prototypes -Wno-disabled-macro-expansion -Wno-vla-extension -Wno-vla
*/
// standard C stuff
#include <cstdio>
#include <cstdlib>
#include <csignal>
#include <cerrno>
#include <cstdarg>
// C++ stuff
#include <string>
// Everything else
#include <pty.h>
#include <unistd.h>
#include <termios.h>
#include <sys/mman.h>
#include <sys/wait.h>
// shared globals
struct sharedBookT {
pid_t childPid;
pid_t parentPid;
pid_t shellPid;
int shellFd;
termios oldTerm, newTerm, shellTerm;
bool readyToQuit;
char fromTerminalBuffer [4096];
char toTerminalBuffer [4096];
char padding [3];
};
// avoid non C++ casts (when used with stricter compilation)
typedef const char* constCharPtrT;
typedef void* voidPtr;
typedef sharedBookT* sharedBookPtrT;
static sharedBookPtrT sharedBookPtr = 0;
// sprintf for std::string
std::string Sprintf (const char* fmt, ...) __attribute__ ((format (printf, 1, 2)));
std::string Sprintf (const char* fmt, ...) {
va_list ap;
va_start (ap, fmt);
const auto n = vsnprintf (0, 0, fmt, ap);
va_end (ap);
char result [n+2];
va_start (ap, fmt);
vsnprintf (result, size_t (n+1), fmt, ap);
va_end (ap);
return std::string (result);
}
// c_str and length shortcut operators for std::string
const char* operator* (const std::string& s) { return s.c_str (); }
size_t operator+ (const std::string& s) { return s.length (); }
// resize shell's pty and notifiy chell of change
void handleTerminalResize () {
sharedBookT& shared = *sharedBookPtr;
winsize ws;
ioctl(0, TIOCGWINSZ, &ws);
ioctl(shared.shellFd, TIOCSWINSZ, &ws);
sigqueue (shared.shellPid, SIGWINCH, {0});
}
// log signal, for convience just to stdout
void logsignal (int signal) {
// can't reliably use regular printf from a signal handler
const auto msg = Sprintf ("Got signal %d\n", signal);
write (1, *msg, +msg);
}
// common signal handler
void handler(int signal, siginfo_t * infoP, void *context __attribute__ ((unused))) {
const auto& si = *infoP;
const auto myPid = getpid ();
sharedBookT& shared = *sharedBookPtr;
// using SIGUSR to notify processes of termination
// (processes must check for it after blocking syscalls)
if (signal == SIGUSR2) { // Notification to quit
shared.readyToQuit = true;
return;
}
auto cc = char (-1);
if (myPid == shared.parentPid) {
// only parent process should handle these
// if child processes handle these as well, there are multiple insertions
switch (si.si_signo) {
case SIGINT: cc = 0x03; break; // "Ctrl-C"
case SIGTSTP: cc = 0x1A; break; // "Ctrl-Z"
case SIGQUIT: cc = 0x1C; break; // "Ctrl-\"
case SIGWINCH: handleTerminalResize (); break;
default: logsignal (signal); break;
}
}
// write control character (if any) to shell's pty
if (-1 < cc) write(shared.shellFd, &cc, 1);
}
// Add common signal handler for each signal
void setupsignal(int signal) {
struct sigaction act;
sigaction(signal, NULL, &act);
act.sa_sigaction = handler;
act.sa_flags |= SA_SIGINFO;
sigaction(signal, &act, NULL);
}
// launch shell with new pty
void launchShell () {
sharedBookT& shared = *sharedBookPtr;
tcgetattr(0, &shared.shellTerm);
const auto pid = forkpty(&shared.shellFd, NULL, &shared.shellTerm, NULL);
if (pid == -1 || pid == 0) {
if (pid == 0) {
shared.shellPid = getpid ();
// inform shell it's pty is being filtered
setenv ("inptyfilter", "true", 1);
exit(execlp("/bin/bash", "bash", NULL));
}
else {
perror ("forkpty failed");
exit (1);
}
}
}
int main () {
// create shared globals structure
sharedBookPtr = sharedBookPtrT (mmap (
NULL, sizeof (sharedBookT),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0
));
sharedBookT& shared = *sharedBookPtr;
launchShell ();
shared.parentPid = getpid ();
//Set up handler for signals
setupsignal(SIGINT);
setupsignal(SIGTSTP);
setupsignal(SIGUSR1);
setupsignal(SIGUSR2);
setupsignal(SIGQUIT);
setupsignal(SIGWINCH);
//setupsignal(SIGTTIN);
//setupsignal(SIGTTOU);
// fork to handle output to the terminal
if (0 == fork ()) {
shared.childPid = getpid ();
// loop while reading and echoing the pty's output
for (;;) {
// read from Shell's Pty
const auto charsRead = read (shared.shellFd, shared.toTerminalBuffer, sizeof (shared.toTerminalBuffer));
// if characters were read, echo them and continue
if (0 < charsRead) {
write (1, shared.toTerminalBuffer, size_t (charsRead));
continue;
}
// if error, check if we are done
if ((charsRead == -1) and (errno == EIO)) {
fprintf (stderr, "\nterminating I/O processes\r\n");
// signal parent to exit
sigqueue (shared.parentPid, SIGUSR2, {0});
break;
}
}
fprintf (stderr, "Exiting pty-filter (toTerminal)\r\n");
exit (0);
}
// wait for pids to be updated
while ((0 == shared.shellPid) or (0 == shared.childPid)) usleep (1);
fprintf (stderr, "parent: %d\n", shared.parentPid);
fprintf (stderr, "shell: %d\n", shared.shellPid);
fprintf (stderr, "child: %d\n", shared.childPid);
tcgetattr(0, &shared.oldTerm); // Disable buffered I/O and echo mode for pty
shared.newTerm = shared.oldTerm;
cfmakeraw (&shared.newTerm);
tcsetattr(0, TCSANOW, &shared.newTerm);
// shell needs intial sizing
handleTerminalResize ();
for (;;) {//loop while processing input from pty
const auto charsRead = read (0, shared.fromTerminalBuffer, sizeof (shared.fromTerminalBuffer));
// SIGUSR1 will drop process out of read so flag can be read
if (shared.readyToQuit) {
fprintf (stderr, "Exiting pty-filter (fromTerminal)\r\n");
break;
}
// in we got input from the terminal, pass it on to the shell's pty
if (0 < charsRead) {
write (shared.shellFd, shared.fromTerminalBuffer, size_t (charsRead));
continue;
}
// if error check if we are done
// However, this is never executed, child fork terminates first
if ((charsRead == -1) and (errno == EIO)) break;
}
tcsetattr(0, TCSANOW, &shared.oldTerm); //reset terminal
// wait for child forks to exit
for (;;) {
auto wpid = wait (0);
if (wpid == -1) break;
fprintf (stderr, "%d is done\n", wpid);
}
perror ("status");
return 0;
}
My question is, what am I missing? What would cause some programs (like joe and snake) to display erratically, while many other programs (like vi, vim, nano, mcedit, htop, top) seem to work just fine.
(On my system joe and snake work just fine without the "pty filter".)
EDIT: As stated above, it now works
Replacing this:
shared.newTerm.c_lflag &= tcflag_t (~ICANON);
shared.newTerm.c_lflag &= tcflag_t (~ECHO);
with this:
shared.newTerm.c_lflag &= tcflag_t (~(ICANON | ISIG | IEXTEN | ECHO));
shared.newTerm.c_iflag &= tcflag_t (~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK));
shared.newTerm.c_oflag &= tcflag_t (~OPOST);
shared.newTerm.c_cc[VMIN] = 1; // 1 char at a time input
shared.newTerm.c_cc[VTIME] = 0;
made it start working correctly. However, this seems like it should not have any effect, as this is being done on stdin:
shared.newTerm.c_oflag &= tcflag_t (~OPOST);
EDIT: The following post answers the question about stdin vs stdout for tcsetattr.
When setting terminal attributes via tcsetattr(fd.....), can fd be either stdout or stdin?
But anyways, it works now. I will update my original post to reflect this.
EDIT: This post was marked as related: Using the linux pseudo terminal API for multiple debug terminals
While the answer was not on that post, it pointed to a site that had the information I needed: http://www.man7.org/tlpi/code/online/dist/tty/tty_functions.c.html
EDIT: Replacing the above with the following also works. I will update my original post accordingly.:
cfmakeraw (&shared.newTerm);
I've working on this problem where I need to assign a character to a become the delete key in Linux with termios. I've looked up resources and it seems like everyone is doing it this way but for some reason I cannot get it to work.
So I am trying to bind the 'q' character to become the new delete key. This is what I have. I am assigning the 'q' character be the new backspace but when I compile and run 'q' doesn't delete anything.
#include <stdio.h>
#include <termios.h>
int main()
{
struct termios err;
err.c_cc[VERASE] = 'q';
return 0;
}
cough
...
struct termios err;
tcgetattr(0, &err);
err.c_cc[VERASE] = 'q';
tcsetattr(0, TCSANOW, &err);
...
I'm new to C++ programming. So, which libraries or functions should I use in retrieving this info from the registry? Just give me a brief idea about the steps involved in retrieving the java version number from registry. I'm using VC++.
If java paths are properly set, you could just run java -version from code:
Using the code described here How to execute a command and get output of command within C++ using POSIX? :
#include <string>
#include <iostream>
#include <stdio.h>
std::string exec(char* cmd) {
FILE* pipe = _popen(cmd, "r");
if (!pipe) return "ERROR";
char buffer[128];
std::string result = "";
while(!feof(pipe)) {
if(fgets(buffer, 128, pipe) != NULL)
result += buffer;
}
_pclose(pipe);
return result;
}
using like:
int main(void) {
std::cout << exec("java -version");
return 0;
}
I gone through the Documentation of NCURSES. I am not getting that if I use getch without initscr then why this program is not working. Is there any other approach to get arrow keys input without clearing screen (that initscr do).
#include <ncurses.h>
#include <unistd.h>
int main()
{
int ch;
//initscr();
//raw();
//keypad(stdscr, TRUE);
//noecho();
while(1)
{
ch = getch();
switch(ch)
{
case KEY_UP:
printw("\nUp Arrow");
break;
case KEY_DOWN:
printw("\nDown Arrow");
break;
case KEY_LEFT:
printw("\nLeft Arrow");
break;
case KEY_RIGHT:
printw("\nRight Arrow");
break;
}
if(ch == KEY_UP)
break;
}
//endwin();
}
Alternatively you may use change the terminal attribute through tcsetattr in termios. If you cycle between the canonical mode (requires new line for the process to begin) and non-canonocal mode (Keypress is more than enough).
The following program works as follows - THe process waits for user input. If up arrow key is pressed, it prints 'Arrow key pressed' and exits. If something else is pressed, it waits for the user to press Enter and then prints the user inpu. Exits after the inut is printed.
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
struct termios oldt, newt;
char ch, command[20];
int oldf;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
while(1)
{
ch = getchar();
if (ch == '\033')
{ printf("Arrow key\n"); ch=-1; break;}
else if(ch == -1) // by default the function returns -1, as it is non blocking
{
continue;
}
else
{
break;
}
}
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF)
{
ungetc(ch,stdin);ith
putchar(ch);
scanf("%s",command);
printf("\n%s\n",command);
return 1;
}
return 0;
}
getch is the same as wgetch(stdscr). It assumes that the screen has been initialized. ncurses (any curses implementation) has to do a couple of things to make wgetch usable:
read the terminal description
initialize the terminal I/O modes
allocate a screen to draw on.
The last is because wgetch does a wrefresh on the window for which it was called, before doing a read.
You could use newterm with filter to avoid clearing the screen, and doing line-oriented input. The filter program in ncurses-examples demonstrates how to do this.
I have a solution without ncurses
You can use simple-getch like this:
t_key keys[] = {
{"[A", K_UP},
{"[B", K_DOWN},
{"[D", K_LEFT},
{"[C", K_RIGHT},
{NULL, K_UNK},
};
int key;
ch_init();
while ((key = ch_get(keys)) != K_BACK)
{
printf("%d\n", key);
}
ch_end();
keys array is a list of escape sequences which will be used, (when you type an arrow key in a terminal, it will write an escape key followed by multiples characters to define the key.)
This sequences may/will change between terminals, you should use termcap to properly set this sequences.
I was looking at libblkid and was confused about the documentation. Could someone provide me with an example of how I could find the UUID of a root linux partition using this library?
It's pretty much as simple as the manual makes it look: you create a probe structure, initialize it, ask it for some information, and then free it. And you can combine the first two steps into one. This is a working program:
#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <blkid/blkid.h>
int main (int argc, char *argv[]) {
blkid_probe pr;
const char *uuid;
if (argc != 2) {
fprintf(stderr, "Usage: %s devname\n", argv[0]);
exit(1);
}
pr = blkid_new_probe_from_filename(argv[1]);
if (!pr) {
err(2, "Failed to open %s", argv[1]);
}
blkid_do_probe(pr);
blkid_probe_lookup_value(pr, "UUID", &uuid, NULL);
printf("UUID=%s\n", uuid);
blkid_free_probe(pr);
return 0;
}
blkid_probe_lookup_value sets uuid to point to a string that belongs to the pr structure, which is why the argument is of type const char *. If you needed to, you could copy it to a char * that you manage on your own, but for just passing to printf, that's not needed. The fourth argument to blkid_probe_lookup_value lets you get the length of the returned value in case you need that as well. There are some subtle differences between blkid_do_probe, blkid_do_safeprobe, and blkid_do_fullprobe, but in cases where the device has a known filesystem and you just want to pull the UUID out of it, taking the first result from blkid_do_probe should do.
First you need to find the device mounted as as root. See man getmntent (3). Once you know the device, use blkid_new_probe_from_filename as described by hobbs.
#include <stdio.h>
#include <mntent.h>
int main() {
FILE* fstab = setmntent("/etc/mtab", "r");
struct mntent *e;
const char *devname = NULL;
while ((e = getmntent(fstab))) {
if (strcmp("/", e->mnt_dir) == 0) {
devname = e->mnt_fsname;
break;
}
}
printf("root devname is %s\n", devname);
endmntent(fstab);
return 0;
}