Creating custom keyboard shortcuts in a linux c application - c

I am trying to handle keyboard shortcuts, I already know how to do it with signals but the problem is that the signal list doesn't offer a lot of choices.
So I was wondering if it was possible to handle shortcuts like CTRL+'key'
and key can be any keyboard key like A Z E R T Y.

Here is an example of using GNU readline. You can capture key sequences Ctrl+P, Ctrl+G, etc.
int keyPressed(int count, int key) {
printf("key pressed: %d\n",key);
rl_on_new_line();
return 0;
}
int main() {
rl_catch_signals = 0;
rl_bind_keyseq("\\C-g",keyPressed);
rl_bind_keyseq("\\C-p",keyPressed);
rl_bind_keyseq("\\C-z",keyPressed);
while(1) {
char *line = readline("rl> ");
}
For special characters such as signal characters, Ctrl+C,Ctrl+Z you will need rl_catch_signals=0. This way, you can define your own signal handlers.
One thing I found is that rl_bind_keyseq("\\C-z",keyPressed) will not be called, even if you put the terminal in raw mode before calling readline. Instead the terminal will still interpret Ctrl+Z as SIGTSTP.
Looking through the source code, it's apparent that every time readline() is called, the terminal settings are reset.
//rltty.c
#if defined (HANDLE_SIGNALS)
tiop->c_lflag &= ~ISIG;
#else
tiop->c_lflag |= ISIG;
#endif
Unless you want to modify readline, I suggest defining signal handlers for special characters.

Related

Why does getchar() only return (char)13 after another key has been pressed? (windows)

I was playing around with the Windows Console API and found some interesting behaviour. Using this function:
void defConsole() {
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode = 0;
GetConsoleMode(hStdIn, &mode);
SetConsoleMode(hStdIn, mode & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT));
}
I was able to make it so that the terminal behaves like non-canonical *nix terminals. (Getchar does not wait for enter and doesn't display what you write, like conio.h's getch or unistd.h's getpass). I then tried printing each character written along with its ascii(?) value.
int main() {
char c;
defConsole();
while (1) {
c = getchar();
printf("%c %d\n", c, (int)c);
}
}
However, something interesting happened: When I pressed enter, nothing happened, but the next key I pressed would be overriden by it (ascii code 13), and the next key would print both the last one and itself.
Why does this happen? What can I do to stop this? Should I change it to use winapi's keyboard input functions instead? That seems like an overkill to me.
To conform with C's line-ending standards, \r\n must be replaced with \n. But you can't know if the sequence is \r\n until you get the character after the \r, so it must be buffered until the next character comes in. Try typing Ctrl-J (\n) after the Enter to see what happens.

How portable is the use of getch outside curses mode?

I'm working on a terminal program to recognize individual key presses, including keypad keys, but I'd rather not do it in curses/program mode if possible. Rather than reinvent the wheel using terminfo and some sort of mapping or tree structure for fast keypad key matching, I figured I might just leverage curses and use tcgetattr() and tcsetattr() to do what I want outside curses mode while still using curses I/O functions to do the translation of keypad keys for me. Much to my surprise, this works (Linux, ncurses 6.1.20180127):
/**
* Most error checking elided for brevity.
*/
#include <stdio.h> // printf
#include <string.h> // memcpy
#include <curses.h>
#include <termios.h> // tcgetattr, tcsetattr
int main(void)
{
struct termios curr, new_shell_mode;
int c, fd;
SCREEN *sp;
FILE *ttyf;
/*
* Initialize desired abilities in curses.
* This unfortunately clears the screen, so
* a refresh() is required, followed by
* endwin().
*/
ttyf = fopen("/dev/tty", "r+b");
fd = fileno(ttyf);
sp = newterm(NULL, ttyf, ttyf);
raw();
noecho();
nonl();
keypad(stdscr, TRUE);
refresh();
// Save the current curses mode TTY attributes for later use.
tcgetattr(fd, &curr);
endwin();
/*
* Set the shell/non-curses mode TTY attributes to
* match those of program/curses mode (3 attempts).
*/
memcpy(&new_shell_mode, &curr, sizeof curr);
for (c = 0; c < 3; c++) {
tcsetattr(fd, TCSADRAIN, &new_shell_mode);
tcgetattr(fd, &curr);
if (0 == memcmp(&new_shell_mode, &curr, sizeof curr))
break;
}
// If new shell mode could fully be set, get a key press.
if (c != 3)
c = getch();
reset_shell_mode();
delscreen(sp);
fclose(ttyf);
printf("%02X\n", c);
return 0;
}
However, given that I've exited curses mode, is it actually safe/portable to still use getch() in the manner shown?
Or do I need to take the more difficult path of using setupterm() to load the terminfo DB and loop through the strnames array, calling tigetstr() for each, plus set my own termios flags manually and deal with reading the keypress myself?
Nothing in the XSI Curses spec seems to forbid this, provided stdscr remains valid, which seems to be either until the program exits or delwin() is called, I can continue using it, and since stdscr is connected to my ttyf file, which is the terminal, I can use it to get a keypress without resorting to handling everything myself.
You initialized curses using newterm, and it doesn't matter that you called endwin: curses will resume full-screen mode if it does a refresh as a side-effect of calling getch.
That's not just ncurses, but any curses implementation (except for long-obsolete BSD versions from the 1980s). X/Open Curses notes
If the current or specified window is not a pad, and it has been moved or modified since the last refresh operation, then it will be refreshed before another character is read.
In your example, nothing was "moved or modified". But getch checks. (There's probably nothing to be gained by the endwin/termios stuff, since newterm doesn't do a clear-screen until the first refresh).

Loop until user inputs an Integer, but skip the read in if no integer exists on the terminal

Im trying to create an application that will count pulses from an Oscilloscope, my problem is this:
I need a loop to run constantly, until a user input is entered. However I dont want getch() to be called unless there is an input on the terminal ready to be read. How would I go about checking for a character or integer existing on the terminal?
If you're coding for UNIX, you'll need to use either poll(2) or select(2).
Since you mention the non-standard getch you might also have kbhit which tests if there is input waiting. But don't slug your oscilloscope with that every loop: you can ease the flow by checking occasionally.
#include <conio.h>
#include <stdio.h>
#define POLLMASK 0xFFFF
int main(void){
int poll = 0;
int ch = 0;
while(ch != 27) {
// ... oscilloscope details
if ((poll++ & POLLMASK) == 0 && _kbhit()) {
ch = _getch();
// ... do something with input
}
}
return 0;
}
Use scanf() . It's standard, it already waits for a character in the buffer, and it's pretty much universal.
EDIT:
In the case of an unnamed OS, this is simply not to be done. Standard C has no way of reading raw data from a terminal and guaranteeing verbatim, unformatted results. It's entirely up to your terminal handler.
However, if your microcontroller has some sort of serial library, I would suggest doing something like this:
if characters in buffer is greater than 0, and the character just read is not a terminating character, report it. Otherwise, keep waiting for a character in the buffer.
until a user input is entered. this sentence indicates the user will use the keyboard eventually, therefore, you can track keyboard callback event to track the user's input. There are several libraries that provide you with such keyboard events (e.g. GLUT, SDL)

determing if a user pressed tab [duplicate]

This question already has answers here:
How to detect that arrow key is pressed using C under Linux or Solaris?
(3 answers)
Closed 8 years ago.
So I am writing a program, and of the the tasks is to determine if a user has pressed tab. So when he presses tab, I should print something to the console(or do tab completition etc). My problem is how do I do it without the user pressing enter. I tried looking into ncurses but I couldn't find a simple example that would teach me how to do it with tab.
Edit:
Using Linux
You can act on input using ncurses and the getch() function. It's going to return you an int value for the key pressed, you can check for a tab via looking to see if the return was 9. This code will loop displaying what was pressed until it was a tab then it exits.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ncurses.h>
int main() {
int c;
initscr(); /* Start curses mode */
cbreak();
noecho();
while(9 != (c = getch())) {
printw("%c\n", c);
if(halfdelay(5) != ERR) { /* getch function waits 5 tenths of a second */
while(getch() == c)
if(halfdelay(1) == ERR) /* getch function waits 1 tenth of a second */
break;
}
printw("Got a %d\n", c);
cbreak();
}
endwin();
return 0;
}
Technically this is not a C language question but a matter of operating system or runtime environment. On POSIX systems you must set, at least, your terminal in non-canonical mode.
The canonical mode buffers keyboard inputs to process them further if needed (for example, this lets you erase chars before your application see them).
There is many ways to switch to non-canonical mode. Of course you can use many different libraries ncurses, etc. But the trick behind is a set of system calls called termios. What you have to do is to read current attributes of the POSIX terminal and modify them accordingly to your needs. For example :
struct termios old, new;
/* read terminal attributes */
tcgetattr(STDIN_FILENO,&old);
/* get old settings */
new=old;
/* modify the current settings (common: switch CANON and ECHO) */
new.c_lflag &=(~ICANON & ~ECHO);
/* push the settings on the terminal */
tcsetattr(STDIN_FILENO,TCSANOW,&new);
do_what_you_want_and_read_every_pressed_char();
/* ok, restore initial behavior */
tcsetattr(STDIN_FILENO,TCSANOW,&old);

Keyboard input: how to separate keycodes received from user

I am writing an application involving user input from the keyboard. For doing it I use this way of reading the input:
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
int mygetch( ) {
struct termios oldt,
newt;
int ch;
tcgetattr( STDIN_FILENO, &oldt );
newt = oldt;
newt.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN_FILENO, TCSANOW, &newt );
ch = getchar();
tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
return ch;
}
int main(void)
{
int c;
do{
c = mygetch();
printf("%d\n",c);
}while(c!='q');
return 0;
}
Everyting works fine for letters digits,tabs but when hiting DEL, LEFT, CTRL+LEFT, F8 (and others) I receive not one but 3,4,5 or even 6 keycodes.
The question is: Is is possible to make a separation of these keycodes (to actually know that I only hit one key or key combination).
What I would like is to have a function to return a single integer value for any type of input (letter, digit, F1-F12, DEl, PGUP, PGDOWN, CTRL+A, CTRL+ALT+A, ALT+LEFT, etc). Is this possible?
I'm interested in an idea to to this, the language doesn't matter much, though I'd prefer perl or c.
Thanks,
Iulian
You'll want to look into the curses or ncurses libraries (or possibly slang).
Terminals, terminal windows, and the console are all modeled after actual terminals (a screen and a keyboard connected to the computer via a serial cable), and terminals are modeled after a teletype device (tty -- which is a keyboard and a simple printer).
Different terminals (real terminals, psudoterminals, and console) can have different representations for special characters, such as function and arrow keys. They do this because there are potentially more than 256 things that someone might send from between a terminal and a computer.
One of the many things curses does is takes the input that different terminal gives you and using the terminal type produces an integer which represents that key. It also provides macro constants for these values so KEY_DOWN can be compared to the result from curses' getch to determine if the down arrow was pressed.
Check out or , or you can google for curses and get lots of information.
Sounds like you're getting ANSI escape codes. You can break them up pretty easily using the information at this link. Your particular machine might have some special cases, but you should be able to identify and sort them out without too much trouble.

Resources