Terminal ignore input while python loop is running - python-3.x

So I am creating a program that executes commands based on live user input. To do this I am using pynput (essentially a keylogger) which reads in each keystroke while the program is active. The problem I am running into is that each keystroke is still being read into the terminal, so once the program ends, the terminal is bombarded with a random string of characters and/or keystrokes. Is there anyway I can get the terminal to ignore these inputs while the program is running?
Code snippet below.
def on_press(key):
global current_command
try:
# check to see if command is standard letter key, if it is, convert it to a command
current_command = commands.Command(key.char)
except AttributeError:
# if not a letter command, check if it's a valid keyboard command; if it is, convert to command
# TODO: finalize valid keys
valid_keys = {
"Key.space": "space",
"Key.shift": "shift",
"Key.tab": "tab",
"Key.enter": "enter",
"Key.caps_lock": "caps",
"Key.esc": "escape"
}
valid_command = valid_keys.get(str(key))
if valid_command is not None:
current_command = commands.Command(valid_command)
else:
# not a recognized command, creating command that will be logged, but ignored
current_command = commands.Command(str(key))
# stops listener
return False
def run_rover():
while True:
# during loop, inputs must be ignored by terminal
command: commands.Command = input.collect_input()
print(command)
output = execute.execute(command)
if output is not None:
print("->" + output + "<-")
if output == "powering off":
exit(0)

Related

tqdm progress bar not updating when using paramiko

When using Paramiko to execute commands remotely, I can't see an updating progress bar using tqdm. I'm guessing this is because it isn't printing a new line when tqdm updates the bar
Here's a simple code example I've been using, but you'll need to supply your own SSH credentials
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.connect('8.tcp.ngrok.io', username=username, get_pty=True)
command = 'python3 -c "import tqdm; import time; [time.sleep(1) for i in tqdm.tqdm(range(5))]"'
stdin, stdout, stderr = ssh_client.exec_command('sudo -S '+command)
stdin.write(password+'\n')
stdin.flush()
###new_method
for l, approach in line_buffered(stdout):
if approach=='print':
print( l)
if approach=='overlay':
print( l, end='\r')
ssh.close()
Is there a way I can print the tqdm bar as it updates?
Based on Martin Prikryl's suggestion, I tried to incorporate the solution from:
Paramiko with continuous stdout
And adapted the code to print regardless of a new line
def line_buffered(f):
line_buf = ""
while not f.channel.exit_status_ready():
# f.read(1).decode("utf-8")
line_buf += f.read(1).decode("utf-8", 'ignore')
if line_buf.endswith('\n'):
yield line_buf, 'print'
line_buf = ''
# elif len(line_buf)>40:
elif line_buf.endswith('\r'):
yield line_buf, 'overlay'
This does successfully print the as the output as it is generated, and reprints on the tqdm line, but when I run this code I get the following output
100%|| 5/5 [00:05<00:00, 1.00s/it]1.00s/it]1.00s/it]1.00s/it]1.00s/it]?, ?it/s]
Not very pretty, and getting swamped by the iteration time. It doesn't seem to be printing the actual progress bar. any ideas?
It probably because you are (correctly) using non-interactive session to automate your command execution.
Most decently designed commands do not print an output intended for an interactive human use, like progress, when executed non-interactively.
If you really want to see the progress display, try setting get_pty argument of SSHClient.exec_command to true.
stdin, stdout, stderr = ssh_client.exec_command('sudo -S '+command, get_pty=True)

Convert text to speech on mac when button pressed

I am trying to create a script which will read a text file and then speak one line of the file every time I press a button on my keyboard. I am using the system accessibility voice on my Mac as the voice sounds more human than some of the python modules I tried.
So far my script runs and speaks a line but the spoken lines are not in order but rather are just lines spoken at random, whereas I would like each line to be spoken once and in order.
Also, I would rather use another key (not alphanumeric) such as right arrow to invoke the function but not sure how to specify that in my script?
I am just learning to code so any help would be much appreciated.
I am using Python 3.9.1 on Mac OSX 10.15.5
filename = ('file.txt')
with open(filename) as f:
lines = f.readlines()
# for x in lines:
# pass
def say():
while True:
try: # used try so that if any key other than the specified key is pressed an error will not be shown
for x in lines:
if keyboard.is_pressed('l'): # if key 'l' is pressed
subprocess.call(['say', x])
print('You Pressed A Key!')
#break # finishing the loop
except:
break # this should break loop if user presses a key other than the given key but doesn't work
say()
Ok I have now managed to fix the issue of reading from a file and reading each line in sequence (see clode below).
And aparantly you can just specify the key with 'up' or 'right' which works. It does output random characters to the screen so if anyone has a better way of doing this, I'd love to hear it
import keyboard
import subprocess
filename = ('text.txt')
with open(filename) as f:
lines = f.readlines()
def say():
i=0
while i <len(lines):
try: # used try so that if any key other than the specified key is pressed an error will not be shown
if keyboard.is_pressed('up'): # if key 'up' is pressed
subprocess.call(['say', lines[i]])
i += 1
except:
break # this should break loop if user presses a key other than the given key but doesn't work
say()
import keyboard
import subprocess
CURRENT_LINE = 0 #records line to be read out, first line is 0.
def ttsLine(line): #TTS function, uses macs say as per you requested.
try:
k = subprocess.check_output(["say",line.strip()]) #using subprocess, line.strip() to clean each line.
return True #returns true so we know if say command was successful
except:
return False #returns false if exceptions occured.
filename = ('text.txt') #text file with lines to be read.
with open(filename) as f:
lines = f.readlines() #saving lines to array.
while True:
keyboard.wait("up") #Waits until up key is pressed.
if not ttsLine(lines[CURRENT_LINE]): #if ttsLine returned false, something went wrong and script halts.
print("Something went wrong while reading out", lines[CURRENT_LINE])
break
CURRENT_LINE += 1 #else we update line to next one
if CURRENT_LINE == len(lines): #if all lines have been read, script ends.
break

Continuous communication between parent and child subprocess in Python (Windows)?

I have this script:
import subprocess
p = subprocess.Popen(["myProgram.exe"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
while True:
out, _ = p.communicate(input().encode())
print(out.decode())
which works fine until the second input where I get:
ValueError: Cannot send input after starting communication
Is there a way to have multiple messages sent between the parent and child process in Windows ?
[EDIT]
I don't have access to the source code of myProgram.exe
It is an interactive command line application returning results from queries
Running >> myProgram.exe < in.txt > out.txt works fine with in.txt:
query1;
query2;
query3;
Interacting with another running process via stdin/stdout
To simulate the use case where a Python script starts a command line interactive process and sends/receives text over stdin/stdout, we have a primary script that starts another Python process running a simple interactive loop.
This can also be applied to cases where a Python script needs to start another process and just read its output as it comes in without any interactivity beyond that.
primary script
import subprocess
import threading
import queue
import time
if __name__ == '__main__':
def enqueue_output(outp, q):
for line in iter(outp.readline, ''):
q.put(line)
outp.close()
q = queue.Queue()
p = subprocess.Popen(["/usr/bin/python", "/test/interact.py"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
# stderr = subprocess.STDOUT,
bufsize = 1,
encoding ='utf-8')
th = threading.Thread(target=enqueue_output, args=(p.stdout, q))
th.daemon = True
th.start()
for i in range(4):
print("dir()", file=p.stdin)
print(f"Iteration ({i}) Parent received: {q.get()}", end='')
# p.stdin.write("dir()\n")
# while q.empty():
# time.sleep(0)
# print(f"Parent: {q.get_nowait()}")
interact.py script
if __name__ == '__main__':
for i in range(2):
cmd = raw_input()
print("Iteration (%i) cmd=%s" % (i, cmd))
result = eval(cmd)
print("Iteration (%i) result=%s" % (i, str(result)))
output
Iteration (0) Parent received: Iteration (0) cmd=dir()
Iteration (1) Parent received: Iteration (0) result=['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'cmd', 'i']
Iteration (2) Parent received: Iteration (1) cmd=dir()
Iteration (3) Parent received: Iteration (1) result=['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'cmd', 'i', 'result']
This Q&A was leveraged to simulate non-blocking reads from the target process: https://stackoverflow.com/a/4896288/7915759
This method provides a way to check for output without blocking in the main thread; q.empty() will tell you if there's no data. You can play around with blocking calls too using q.get() or with a timeout q.get(2) - the parameter is number of seconds. It can be a float value less than zero.
Text based interaction between processes can be done without the thread and queue, but this implementation gives more options on how to retrieve the data coming back.
The Popen() parameters, bufsize=1 and encoding='utf-8' make it possible to use <stdout>.readline() from the primary script and sets the encoding to an ascii compatible codec understood by both processes (1 is not the size of the buffer, it's a symbolic value indicating line buffering).
With this configuration, both processes can simply use print() to send text to each other. This configuration should be compatible for a lot of interactive text based command line tools.

Pexpect - Read from constantly outputting shell

I'm attempting to have pexpect begin running a command which basically continually outputs some information every few milliseconds until cancelled with Ctrl + C.
I've attempted getting pexpect to log to a file, though these outputs are simply ignored and are never logged.
child = pexpect.spawn(command)
child.logfile = open('mylogfile.txt', 'w')
This results in the command being logged with an empty output.
I have also attempted letting the process run for a few seconds, then sending an interrupt to see if that logs the data, but this again, results in an almost empty log.
child = pexpect.spawn(command)
child.logfile = open('mylogfile.txt', 'w')
time.sleep(5)
child.send('\003')
child.expect('$')
This is the data in question:
image showing the data constantly printing to the terminal
I've attempted the solution described here: Parsing pexpect output though it hasn't worked for me and results in a timeout.
Managed to get it working by using Python Subprocess for this, not sure of a way to do it with Pexpect, but this got what I described.
def echo(self, n_lines):
output = []
if self.running is False:
# start shell
self.current_shell = Popen(cmd, stdout=PIPE, shell=True)
self.running = True
i = 0
# Read the lines from stdout, break after reading the desired amount of lines.
for line in iter(self.current_shell.stdout.readline, ''):
output.append(line[0:].decode('utf-8').strip())
if i == n_lines:
break
i += 1
return output

Python: Printing data and receiving user input simultaneously

I want to be able to receive user input and print stuff simultaneously, without them interfering. Ideally, that would be printing regularly and having the user type input to the bottom of the terminal window, for example:
printed line
printed line
printed line
printed line
printed line
(this is where the next print would happen)
Enter Input: writing a new input...
This should look like a chat app or something of that sort.
If there is no good way to do that, any way to print above the input line would be amazing too.
Thanks!
Sadly it is not very feasible to both take input and give output in python, without importing modules to directly interact with the OS.
But you can can get pretty close to it with this code:
import curses
import random # for random messages
#this should be async
def get_message():
message = [str(random.randint(0,9)) for i in range(15)]
return "".join(message)
def handle_command(cmd): # handle commands
if cmd=="exit":
exit(0)
def handle_message(msg): # send a message
raise NotImplementedError
def draw_menu(stdscr):
stdscr.erase()
stdscr.refresh()
curses.raw()
k = 0
typed=""
while True:
# Initialization
stdscr.erase()
height, width = stdscr.getmaxyx()
stdscr.addstr(0, 0, "Welcome to chat app")
msg = get_message()
if msg: # add a message if it exists
stdscr.addstr(1, 0, msg)
if k==ord("\n"): # submit on enter
if typed.startswith("/"): # execute a command
typed = typed[1:]
handle_command(typed)
elif typed.startswith("./"): # bypass commands with a dot
typed = typed[1:]
handle_message(typed)
else:
handle_message(typed) # send the message
typed = ""
elif k==263: # Backspace
typed = typed[:-1] # erase last character
stdscr.addstr(height-1, 0, typed)
elif k==3: # Ctr+C
typed="" # Delete the whole string
stdscr.addstr(height-1, 0, typed)
elif k!=0:
typed += chr(k) # add the char to the string
stdscr.addstr(height-1, 0, typed)
stdscr.refresh() # refresh
# Wait for next input
k = stdscr.getch()
def main():
curses.wrapper(draw_menu)
if __name__ == "__main__": # dont import
main()
the only thing that is to do to update the messages is to type a new char.
And I do not recommend you to build a chat in the terminal for anything other then educational value (trust me I tried it).
It would be better if you tried it using a web platform (e.g. Tauri or Electron)
Also the code cannot:
insert automatic line breaks (it errors)
send any messages (must implement yourself)
show any user names (must implement yourself)

Resources