Detect arrow keys being pressed on console in Python - linux

I am trying to write a console program in Python 3 that provides some sort of shell for the user, just like the Python 3 shell in a console. I was able to achieve this relatively quickly by using the input()method. However, it would be nice if, in that shell, one could use the arrow keys to cycle through the most recently typed commands, just like you can in other shells. The input() method does not provide this feature, and I did not find any other simple tools to do this, except for the curses module, which needs to take over the whole screen to work. One of my approaches was to read the typed text from stdin byte by byte and then check it against the codes for the special characters I'm looking for. This works pretty well, but it would run into problems when the user (for some reason) types a weird unicode character that contains the code for a key like the arrow key somewhere in the middle. While this is still an acceptable solution for me, I feel like this is a problem which ought to have been solved (better) before, given how often it has got to occur.

In Python 3, sys.stdin.read returns unicode characters as a single character. Escape sequences for arrow keys are delivered as multiple ASCII characters. Here is an example program , using tty and termios, which parses inputs accordingly.
import sys,tty,termios
# Commands and escape codes
END_OF_TEXT = chr(3) # CTRL+C (prints nothing)
END_OF_FILE = chr(4) # CTRL+D (prints nothing)
CANCEL = chr(24) # CTRL+X
ESCAPE = chr(27) # Escape
CONTROL = ESCAPE +'['
# Escape sequences for terminal keyboard navigation
ARROW_UP = CONTROL+'A'
ARROW_DOWN = CONTROL+'B'
ARROW_RIGHT = CONTROL+'C'
ARROW_LEFT = CONTROL+'D'
KEY_END = CONTROL+'F'
KEY_HOME = CONTROL+'H'
PAGE_UP = CONTROL+'5~'
PAGE_DOWN = CONTROL+'6~'
# Escape sequences to match
commands = {
ARROW_UP :'up arrow',
ARROW_DOWN :'down arrow',
ARROW_RIGHT:'right arrow',
ARROW_LEFT :'left arrow',
KEY_END :'end',
KEY_HOME :'home',
PAGE_UP :'page up',
PAGE_DOWN :'page down',
}
# Blocking read of one input character, detecting appropriate interrupts
def getch():
k = sys.stdin.read(1)[0]
if k in {END_OF_TEXT, END_OF_FILE, CANCEL}: raise KeyboardInterrupt
print('raw input 0x%X'%ord(k),end='\r\n')
return k
# Println for raw terminal mode
def println(*args):
print(*args,end='\r\n',flush=True)
# Preserve current terminal settings (we will restore these before exiting)
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
# Enter raw mode (key events sent directly as characters)
tty.setraw(sys.stdin.fileno())
# Loop, waiting for keyboard input
while 1:
# Parse known command escape sequences
read = getch()
while any(k.startswith(read) for k in commands.keys()):
if read in commands:
println('detected command (%s)'%commands[read])
read = ''
break
read += getch()
# Interpret all other inputs as text input
for c in read:
println('detected character 0x%X %c'%(ord(c),c))
# Always clean up
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
println('')
sys.exit(0)

Related

Decoding ANSI escape sequences in Dart

I'm writing some code for Flutter Desktop targeting linux_x64.
I'm extracting some logs from some applications, these logs presents a syntax like this:
Inspecting log file using less logfile
ESC(BESC[mauthentication-msESC(BESC[m
Inspecting log file using less -r logfile I can see colored text into my terminal.
Inspecting log file using cat logfile I can see colored text into my terminal.
Inspecting log file using cat -vte logfile I get this:
^[(B^[[mauthentication-ms^[(B^[[m$
In Flutter using this code
Future<String> readAsString = file.readAsString();
readAsString.then((String value) => _log = utf8.decode(value.runes.toList()));
I get this output in a SelectableText widget
(B[mauthentication-ms(B[m
I'm really confused about this behaviour so if someone has experience on this suggestions are welcome!
There are 2 options:
Cleaning all the logs, visualizing normal text
Trying to decode the text just as less -r does, visualizing colored text into Flutter application.
EDIT:
I solved importing tint plugin: tint: ^2.0.0
and changing the Dart code (using the strip() method from tint plugin) as follows:
Future<String> readAsString = file.readAsString();
readAsString.then((String value) => _log = value.strip());
Those funny characters are called escape sequences, and programs use them to print colours and italics and all of that.
Terminals are designed to decode these escape sequences, but regular programs don't know what to do with them. less and cat are printing exactly what is in the file, it's the terminal you run them in that decodes them.
You'll have to make your program go through and remove all of the escape sequences with a piece of code like this:
m = "h\x1b[34mello\x1b(A.\x1b[H" # Text full of random escape sequences
c = 0 # A count variable
p = True # Are we not in an escape sequence?
o = "" # The output variable
for l in m:
if l == "\x1b":
p = False
elif p:
o += l
elif l in "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm": # Most (maybe all) escape sequences end in letters.
p = True
c += 1 # Move on to the next letter in the input string
print(o) # Text without escape sequences

Stuck in infinite loop while trying to read all lines in proc.stdout.readline

I am trying to read each line in proc.stdout.readline and send the lines over the network, for example:
data = b''
for line in iter(proc.stdout.readline, ''):
data += line
clientsocket.send(data)
When I run this code I seem to be stuck in a inifinite loop unable to escape to the line:
clientsocket.send(data)
Is there a more efficient way to read the data? I've tried also with a while loop and breaking 'if not line':
data = b''
while True:
line += proc.stdout.readline()
data += line
if not line:
break
clientsocket.send(data)
This seems to also produce the same results. Is there a more efficient way to read all of the data from proc.stdout.readline?
I've encountered the same very problem. The strange thing that in Python 2.7 it had no problem to converge and actually stop iterating.
During debug (in Python 3.5) I've noticed that all true lines returned with the '\n' character, whereas the line that wasn't suppose to arrive returned as an empty string, i.e. ''. So, I just added an if-clause checking against '' and breaking the loop if positive.
My final version looks as follows:
lines = []
for _line in iter(process.stdout.readline, b''):
if _line == '':
break
lines.append(_line)
One thing that might be worth to mention, is that I used universal_newlines=True argument upon subprocess.Popen(..) call.
The statement: iter(proc.stdout.readline, "") will do a blocking read until it recieves an EOF.
If you want to read all the lines, then you can just do:
data = b''
data = b"".join(proc.stdout.readlines())
There is no other solution than for the proc to produce lines faster.
If you want, you can read lines with timeout (i.e. you can wait to read a select number of characters, or timeout if that number of characters are not read).
Those answers can be found here:
https://stackoverflow.com/a/10759061/6400614 .
https://stackoverflow.com/a/5413588/6400614

Python deleting input line

I would like to have an input loop in python 3 where the information which gets typed in gets deleted from terminal automatically (f.eks. after 3 seconds)
I know the function with \r to go back in line, but struggle with the automatic new line after input.
while True:
inputStr = (input("Add the hidden word: ")).lower()
processingTheInput(inputStr) #some sort of function using the input word
Ansi escape codes will not work the same on all terminals but this might suit your needs. The ‘\033’ is the escape character. The ‘[1A’ says go up one line and the ‘[K’ says erase to the end of this line.
prompt = 'Add the hidden word: '
inputStr = input(prompt).lower()
print ('\033[1A' + prompt + '\033[K')
You want to clear the terminal with a function
# import only system from os
from os import system, name
# import sleep to show output for some time period
from time import sleep
# define our clear function
def clear():
# for windows
if name == 'nt':
_ = system('cls')
# for mac and linux(here, os.name is 'posix')
else:
_ = system('clear')
Now you need to have a function that adds your word into a list then runs the clear function, then finally can pick a word at the end

Python3 : Using input() how to Read Multiple Lines from pipe (stdin)?

im just curious while learning python3 and didn't found any good explanation on the web, neither here to my question.
reading about input() it says "reads from stdin" so i thought i might experiment and try to use it to read from pipe. and so it does! but only ONE LINE (till EOL). So the next question that came up was
how to read multiple lines from pipe (stdin) using input() ?
i found sys.stdin and used sys.stdin.isatty() to determine if stdin is bound to a tty or not, assuming that if not bound to tty the data is coming from pipe. and so i also found and used successfully sys.stdin.readlines() too to read multiple lines.
but just for my curiosity , is there a way to achieve the same by using the plain input() function ?? so far i didn't found something "to test" if stdin contains more lines without blocking my program.
sorry if all this makes no sense to you.
this is my experimenting code so far without input():
import sys
if sys.stdin.isatty(): # is this a keyboard?
print( "\n\nSorry! i only take input from pipe. "
"not from a keyboard or tty!\n"
"for example:\n$ echo 'hello world' | python3 stdin.py"
""
""
)
else:
print ( "reading from stdin via pipe : \n ")
for line in sys.stdin.readlines():
print(line, end="")
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can these two lines be replaced with
# some construction using plain old input() ?
You can iterate over lines in stdin like any other iterable object:
for line in sys.stdin:
# do something
If you want to read the entire thing into one string, use:
s = sys.stdin.read()
Note that iterating over s would then return one character at a time.
Note that it won't read until there is an EOF terminating stdin.
if you just want to use input() to access lines of the stdin:
print(input()) #prints line 1
print(input()) #prints next line
but lets say you only wanted to access the second line:
input() #accesses the first line
print(input()) #prints second line
Lets say you wanted to take the second line and create an array:
stdin:
10
64630 11735 14216 99233 14470 4978 73429 38120 51135 67060
input()
values = list(map(int, input().split(' ')))
values will equal [64630, 11735, 14216, 99233, 14470, 4978, 73429, 38120, 51135, 67060]

Lua pattern to stop when end of line

I need to get help for a pattern in Lua stopping to read after a line break.
My code:
function getusers(file)
local list, close = {}
local user, value = string.match(file,"(UserName=)(.*)")
print(value)
f:close()
end
f = assert(io.open('file2.ini', "r"))
local t = f:read("*all")
getusers(t)
--file2.ini--
user=a
UserName=Tom
Password=xyz
UserName=Jane
Output of script using file2.ini:
Tom
Password=xyz
UserName=Jane
How to get the pattern to stop after it reaches the end of line?
You can use the pattern
"(UserName=)(.-)\n"
Note that besides the extra \n, the lazy modifier - is used instead of *.
As #lhf points out, make sure the file ends with a new line. I think you can append a \n to the string manually before matching.

Resources