Detect automatically the escape sequence for a key combination - shell

Is it possible to automatically detect the escape sequence for a key combination?
, i.e., instead of running cat followed by pressing ctrl+right or ctrl+left manually to detect the escape sequence as ^[[5C or ^[[5D respectively, can it be automated? In that case, we may be able to send fn(ctrl+left) or fn(ctrl+right) as input to cat via pipe and get the output(for the first time, when ^[[5C or ^[[5D are unknown) directly.
fn(char a[]='ctrl+right') {..outputs the actual keypress signal for the input string a.. ;} | cat; fn=?
This question is the exact reverse of my other question https://stackoverflow.com/questions/34547008/reverse-map-the-keys-binding-value-to-the-key-combination.
I am asking this to automate the process of setting up key-bindings in the terminals, for devs, so that they need not run manually for all possible combinations, as we had to do now (as seen here)

Is it possible to automatically detect the escape sequence for a key
combination?
Since the shell (as any user program) has no insight into the terminal driver, it cannot predict what the driver will do with a key combination. Consider also that with certain terminals we can change the keyboard's behavior (e. g. cf. xmodmap). Automatic detection is not universally possible.

Related

Is there another way to handle arrow key input in Rust besides raw mode?

I am writing a small shell in Rust on Linux as an exercise and I wanted to implement command history as well as cursor moving (i.e. moving back the cursor to edit a typo in the command).
I did not find a way in the Rust standard library to handle arrow key events, but I found the Termion crate which handles key events.
However, handling key events with Termion means entering "raw mode" for stdout, which overrides "legacy" functionalities, as described in this article about the crate:
Without raw mode, you cannot write a proper interactive TTY application. Raw mode gives you complete control over the TTY:
It disables the line buffering: As you might notice, your command-line application tends to behave like the command-line. The programs will first get the input when the user types \n. Raw mode makes the program get the input after every key stroke.
It disables displaying the input: Without raw mode, the things you type appear on the screen, making it insufficient for most interactive TTY applications, where keys can represent controls and not textual input.
It disables canonicalization of the output: For example, \n represents “go one cell down” not “break the line”, for line breaks \n\r is needed.
It disables scrolling.
I find this solution a bit overkill, as I want to retain most of the "legacy" I/O functionalities. Is there another way to handle arrow key input, or will I need to use the raw mode?
There are several crates that provide line editing features for interactive programs. Here are a few that I found by searching crates.io for "readline" (the name of a C library):
rustyline seems to be the most popular on crates.io.
liner
linefeed
linenoise-rust is a set of Rust bindings to the linenoise library written in C.
I haven't used any of them, so this list is not a recommendation. Take a look at a few of them and choose one that suits your needs.

Read screen character from terminal

As far I researched, terminfo/termcap and ioctl don't allow to query character at (x,y) position on terminal's screen. This is rather unexpected, because:
one can read e.g. screen dimensions from terminal (e.g. tput co, tput li),
one can read input from terminal
– the two mean that communication with terminal is bidirectional. Is there really no way to query a character at given position, like with *inch*, *in_wch* curses functions (which work only for curses windows)?
That's expected (not "unexpected"):
tput gives values from a combination of the terminal database (fixed) and the operating system (changeable), but does not ask the terminal.
reading from the terminal is by an input stream from the device (what terminals are designed to do).
Some (not all) terminals support an escape sequence which asks the terminal where the cursor is. The resize program uses that to obtain the screensize. But keep in mind that it is known to be terminal-dependent, as mentioned in the manual page:
-s [rows columns]
This option indicates that Sun console escape sequences will be
used instead of the VT100-style xterm escape codes.
Very few terminals support an escape sequence which will simply read characters from the screen. Not everyone likes the notion of a program that can read their screen — perhaps without their knowledge.

Bash: how to duplicate input/output from interactive scripts only in complete lines?

How can I capture the input/ output from a script in realtime (such as with tee), but line-by-line instead of character-by-character? My goal is to capture the input typed into the interactive prompts of a script only after backspaces and auto-completion have finished processing (after the RETURN key is hit).
Specifically, I am trying to create a wrapper script for ssh that creates a timestamped log of commands used on remote servers. The script, which uses tee to redirect the output for filtering, works well, but the redirected output gets jumbled with unsubmitted characters whenever I use the backspace key or the up/down keys to scroll through my remote history. For example: service test stopexitservice test stopart or cd ..logs[1Pls -al.
Perhaps there is a way to capture the terminal's scrollback and redirect that like with tee?
Update: I have found a character-based cleanup solution that does what I want most of the time. However, I am still hoping for an answer to this question (which may well be msw's answer that it is very difficult to do).
In the Unix world there are two primary modes of handling keyboard input. These are known as 'raw' in which characters are passed from the terminal to the reading program one at a time. This is the mode that editors (and such) will use because the editor needs to respond immediately when you press a key.
The other terminal discipline is called 'cooked' which is the line by line behavior that you think of as the bash line by line input where you get to backspace and the command is not executed until you press return. Ssh has to take your input in raw, character-by-character mode because it has no idea what is running on the other side. For example, if you are running an editor on the far side, it can't wait for a return before sending the key-press. So, as some have suggested, grabbing shell history on the far side is the only reasonable way to get a command-by-command record of the bash commands you typed.
I oversimplified for clarity; actually most installations of bash take input in raw mode because they allow editor like command modification. For example, Ctrl-P scrolls up the command history or Ctrl-A goes to the beginning of the line. And bash needs to be able to get those keys the moment they are typed not waiting for a return.
This is another reason that capturing on the local side is obnoxiously difficult: if you capture on the local side, the stream will be filled with Backspaces and all of bash's editing commands. To get a true transcript of what the remote shell actually executed you have to parse the character stream as if you were the remote shell. There also a problem if you run something like
vi /some_file/which_is_on_the_remote/machine
the input stream to the local ssh will be filled with movement commands snippets of text including backspaces and so on and it would be bloody difficult to figure out what is part of a bash command and what is you talking to the editor.
Few things involving computers are impossible; getting clean input from the local side of an ssh invocation is really, really hard.
I question the actual utility of recording the commands that you execute on a local or remote machine. The reason is that there is so much state which is not visible from a command log. As a simple example here's a log of two commands:
17:00$ cp important_file important_file.bak
17:15$ rm important_file
and two days later you are trying to figure out whether important_file.bak should have the contents you intended or not. Given that log you can't answer that simple question. Even if you had the sequence
16:58$ cat important_file
17:00$ cp important_file important_file.bak
17:15$ rm important_file
If you aren't capturing the output, the cat in the log will not tell you anything. Give me almost any command sequence and I can envision a scenario in which it will not give you the information you need to make sense of what was done.
For a very similar purpose I use GNU screen which offer the option to record everything you do in a shell session (INPUT/OUTPUT). The log it creates also comes with undesirable characters but I clean them with perl:
perl -ne 's/\x1b[[()=][;?0-9]*[0-9A-Za-z]?//g;s/\r//g;s/\007//g;print' < screenlog.0
I hope this helps.
Some features of screen:
http://speaking-my-language.blogspot.com/2010/09/top-5-underused-gnu-screen-features.html
Site I found the perl-oneliner:
https://superuser.com/questions/99128/removing-the-escape-characters-from-gnu-screens-screenlog-n

Bash shell turns to symbols when using VIM Ack Plugin

Every now and then when using the ack-vim plugin the font in my window will change to all symbols. I've yet to see any pattern to when this happens. Any suggestions on the cause and possible remedy would be appreciated.
I've seen that happen when binary content got printed to the terminal. Do your Ack queries potentially include binary files?
A fix might be
:!echo -e '\ec\e(K\e[J'
These ANSI Escape sequences attempt to reset the terminal:
# "ESC c" - sends reset to the terminal.
# "ESC ( K" - reloads the screen output mapping table.
# "ESC [ J" - erases display.
This looks like the typical character set translation enabled by the Shift Out control character; you usually just need to send the Shift In control character to counteract it.
Basically, something is outputting a C-n character (Control-N, U+000E, named Shift Out) which tells your terminal to switch to a different display character set. You should be able to get your terminal to switch back to the normal display character set by sending a C-o (Control-O, U+000F, named Shift In) to it.
If you are in Vim, then you can probably send the C-o with a command like this:
:!printf \\017
You will have to type (or paste) this command “blindly” since (due to the alternate character set) you will probably not be able to read what you are typing. If you are typing it (not pasting), then you can also type C-v C-o (to insert a single, literal C-o) instead of the backslashed octal, if that is easier to remember.
If you find that this problem occurs only sporadically when you use the vim-ack plugin, then perhaps some bit of the text results contains the problematic Shift Out character. You might try searching for the file with a command like this:
grep -FRl $(printf \\016) .
Once you know the names of the files, then you should be able to use Vim to search for the character (start a search and type C-v C-n to insert a literal C-n). Maybe it is just some garbage that you can clean out, or maybe you can configure your ack-based searches to exclude the problematic files.
You also tagged the question with tmux. I can not tell for sure, but it looks like the top line might be a tmux status line. Since this line is also corrupted it indicates that it your external terminal emulator that has switched character sets, not just one of your tmux panes.
If you send Shift Out or Shift In directly to a tmux pane it will only affect that pane (each pane is emulated independently), so your status like could not have been munged just by a stray Shift Out hitting a single pane.
If you are running inside tmux, then the easiest way to reset the outside terminal is to suspend and resume your tmux client (or detach from and reattach to your session). tmux pretty much resets the outside terminal when it gives up control.
Depending on the situation, you may also have to reset the character set of the tmux pane by sending it a C-o, too (i.e. printf \\017 at a shell, or a :! prompt in Vim).
It is easy to see how a stray Shift Out could reconfigure a single tmux pane, but it is harder to see how it could have “leaked” out to reconfigure the external terminal (tmux is pretty good at isolating things like this). However, there is a control sequence that tmux recognizes that instructs it to pass data directly to the external terminal (thus “leaking out”), but it is much less likely that you would randomly encounter this sequence since it is much longer:
printf '\ePtmux;%s\e\\' 'stuff bound for the external terminal'
You could use it to send the restorative Shift In like this:
printf '\ePtmux;%s\e\\' $(printf \\017)
You will also want to tell tmux to redraw itself after this (by default, the refresh-client command is bound to C-b r).
It is probably easier to just suspend and resume (or detach and reattach), but this sequence is useful if that is not possible. It also provides a means toward understanding what kind of sequence might “leak” out of tmux to switch the character set of the external terminal.

Ruby Keyboard event handling

I'm using curses to develop a small console application.
I have a main loop section which waits for user input, it uses the getstr function, of course this waits for the user to press enter.
I would like to capture the up and down and tab keypresses. I suppose this can't be done with getstr.
Anyone have any idea how to do this?
EDIT: I've tried using STDIN.getc wihch blocks the application from running, and getch doesn't catch the arrow keys.
EDIT #2: I'm trying this code on Windows. It seems that Curses.getch works for Linux, but on Windows I get no key sent for the up arrow.
You need to set the "cbreak" mode of the tty so that you get keypresses immediately. If you don't do this, the Unix terminal-handling system will buffer the input until a newline is received (i.e., the user hits ENTER), and the input will all be handed to the process at that point.
This isn't actually Ruby- or even curses-specific; this is the way all applications work that run through a Unix tty.
Try using the curses library cbreak() function to turn on this mode. Don't forget to call nocbreak() to turn it off before you exit!
For reading a single character, STDIN.getc will return a Fixnum of the ASCII code of the character. Quite possibly you'll find STDIN.read(1) to be more convenient, since it returns a one-character string of the next character.
However, you'll find that the "up/down" keys,if by that you mean the arrow keys, are actually a sequence of characters. On most ANSI-like terminal emulators these days (such as xterm), the up arrow will be "\e[A" (that \e is an escape character) for example. There are termcap (terminal capability) databases to deal with this sort of thing, and you can access them through curses, but intially you may find it easiest just to experiment with interpreting these directly; you're not very likely to run into a terminal using anything other than ANSI codes these days.
you want getch rather than getstr. also see curt sampson's comment about the arrow keys not being a single character.

Resources