I'm currently using multiprocessing so I can obtain user input while running other code. This version of code runs on ubuntu 19.04 for me, but for my friend it doesn't work on windows.
import getch
import time
from multiprocessing import Process, Queue
prev_user_input = ' '
user_input = ' '
# Getting input from the user
queue = Queue(1)
def get_input():
char = ' '
while char != 'x':
char = getch.getch()
queue.put(char)
# Starting the process that gets user input
proc = Process(target=get_input)
proc.start()
while True:
# Getting the users last input
while not queue.empty():
user_input = queue.get()
# Only print user_input if it changes
if prev_user_input != user_input:
print(user_input)
prev_user_input = user_input
time.sleep(1/10)
How can I make this code work on windows?
Also the user input lags behind by one input. If the user presses a button it only prints after he pushes another button. Solutions on how to fix this would also help.
Edit 1:
He's using Python 3.7.4 and I'm using 3.7.3.
I tried this code as suggested
import msvcrt
import time
from multiprocessing import Process, Queue
prev_user_input = ' '
user_input = ' '
# Getting input from the user
queue = Queue(1)
def get_input():
char = ' '
while char != 'x':
char = msvcrt.getch()
queue.put(char)
# Starting the process that gets user input
if __name__ == '__main__':
proc = Process(target=get_input)
proc.start()
while True:
# Getting the users last input
while not queue.empty():
user_input = queue.get()
# Only print user_input if it changes
if prev_user_input != user_input:
print(user_input)
prev_user_input = user_input
time.sleep(1/10)
But no characters were printed.
Edit 2:
I'm using msvcrt module on windows and the getch module on ubuntu. Sorry for not making that clear earlier in the post.
The following works for me on Windows. It incorporates all the changes I suggested in my comments under your question, including the final one about separate memory-spaces.
Something similar should also work under ubuntu using its version of getch(), although I haven't tested it. on The main process creates the Queue and passes it as an argument to the get_input() target function so they're both using the same object to exchange data.
I also decode() the bytes object returned from msvcrt.getch() to convert it into a (1 character) Unicode UTF-8 string.
import msvcrt
import time
from multiprocessing import Process, Queue
prev_user_input = ' '
user_input = ' '
def get_input(queue):
char = ' '
while char != b'x':
char = msvcrt.getch()
queue.put(char.decode()) # Convert to utf-8 string.
if __name__ == '__main__':
# Getting input from the user.
queue = Queue(1)
# Starting the process that gets user input.
proc = Process(target=get_input, args=(queue,))
proc.start()
while True:
# Getting the users last input
while not queue.empty():
user_input = queue.get()
# Only print user_input if it changes
if prev_user_input != user_input:
print(user_input)
prev_user_input = user_input
time.sleep(1/10)
Update
To hide the OS differences and make the code more portable, you could do the importing as shown below, which would also allow you to define the get_input() function more like you did in the code in your question:
import os
import time
from multiprocessing import Process, Queue
try:
import msvcrt
getch = msvcrt.getwch # Wide char variant of getch() that returns Unicode.
except ModuleNotFoundError: # Not Windows OS - no msvcrt.
from getch import getch
prev_user_input = ' '
user_input = ' '
def get_input(queue):
char = ' '
while char != 'x':
char = getch()
queue.put(char)
if __name__ == '__main__':
# For getting input from the user.
queue = Queue(1)
# Starting the process that gets user input.
.
.
.
Related
I need to design a script that uses the top portion of the terminal as output where some lines are printed after each second in an infinite loop, and the bottom portion keeps taking user input and also printing them in the above portion (among the regular periodic outputs).
In other words, I need to design a sort of shell.
I tried multithreading with the naive approach like this:
#!/usr/bin/python3
from math import acos
from threading import Thread
from random import choice
from time import sleep
from queue import Queue, Empty
commandQueue = Queue()
def outputThreadFunc():
outputs = ["So this is another output","Yet another output","Is this even working"] # Just for demo
while True:
print(choice(outputs))
try:
inp = commandQueue.get(timeout=0.1)
if inp == 'exit':
return
else:
print(inp)
except Empty:
pass
sleep(1)
def inputThreadFunc():
while True:
command = input("> ") # The shell
if command == 'exit':
return
commandQueue.put(command)
# MAIN CODE
outputThread = Thread(target=outputThreadFunc)
inputThread = Thread(target=inputThreadFunc)
outputThread.start()
inputThread.start()
outputThread.join()
inputThread.join()
print("Exit")
But as obviously expected, the output lines merge with the input lines as the user keeps typing.
Any ideas?
As discussed in comments, used curses library.
Update
used two subwin for input and output
#!/usr/bin/python3
import curses
from math import acos
from threading import Thread
from random import choice
from time import sleep
from queue import Queue, Empty
commandQueue = Queue()
stdscr = curses.initscr()
stdscr.keypad(True)
upperwin = stdscr.subwin(2, 80, 0, 0)
lowerwin = stdscr.subwin(2,0)
def outputThreadFunc():
outputs = ["So this is another output","Yet another output","Is this even working"] # Just for demo
while True:
upperwin.clear()
upperwin.addstr(f"{choice(outputs)}")
try:
inp = commandQueue.get(timeout=0.1)
if inp == 'exit':
return
else:
upperwin.addch('\n')
upperwin.addstr(inp)
except Empty:
pass
upperwin.refresh()
sleep(1)
def inputThreadFunc():
while True:
global buffer
lowerwin.addstr("->")
command = lowerwin.getstr()
if command:
command = command.decode("utf-8")
commandQueue.put(command)
lowerwin.clear()
lowerwin.refresh()
if command == 'exit':
return
# MAIN CODE
outputThread = Thread(target=outputThreadFunc)
inputThread = Thread(target=inputThreadFunc)
outputThread.start()
inputThread.start()
outputThread.join()
inputThread.join()
stdscr.keypad(False)
curses.endwin()
print("Exit")
Old Solution
I've edited your example to use getch insted of input
#!/usr/bin/python3
import curses
import datetime
from math import acos
from threading import Thread
from random import choice
from time import sleep
from queue import Queue, Empty
INFO_REFRESH_SECONDS = 1
commandQueue = Queue()
buffer = list() # stores your input buffer
stdscr = curses.initscr()
stdscr.keypad(True)
def outputThreadFunc():
outputs = ["So this is another output","Yet another output","Is this even working"] # Just for demo
info = choice(outputs), datetime.datetime.now()
while True:
if datetime.datetime.now() - info[1] > datetime.timedelta(seconds=INFO_REFRESH_SECONDS):
# refresh info after certain period of time
info = choice(outputs), datetime.datetime.now() # timestamp which info was updated
inp = ''
buffer_text = ''.join(buffer)
try:
command = commandQueue.get(timeout=0.1)
if command == 'exit':
return
inp = f"\n{command}"
except Empty:
pass
output_string = f"{info[0]}{inp}\n->{buffer_text}"
stdscr.clear()
stdscr.addstr(output_string)
stdscr.refresh()
if inp:
# to make sure you see the command
sleep(1)
def inputThreadFunc():
while True:
global buffer
# get one character at a time
key = stdscr.getch()
curses.echo()
if chr(key) == '\n':
command = ''.join(buffer)
commandQueue.put(command)
if command == 'exit':
return
buffer = []
elif key == curses.KEY_BACKSPACE:
if buffer:
buffer.pop()
else:
buffer.append(chr(key))
# MAIN CODE
outputThread = Thread(target=outputThreadFunc)
inputThread = Thread(target=inputThreadFunc)
outputThread.start()
inputThread.start()
outputThread.join()
inputThread.join()
stdscr.keypad(False)
curses.endwin()
print("Exit")
The simplest solution is to use two scripts; One, a server that prints the output, and the other, a client that sends the user's input to the server. Then you can use a standard solution like tmux to open the two scripts in two panes.
The two are merging because of the way the terminal writes to the output. It collects outputs in a buffer, and when the time is right it outputs everything at once. An easy fix would be to use a '\n' before each actual statement so that each new output is on a separate line.
#!/usr/bin/python3
.
.
.
if inp == 'exit':
return
else:
print("\n", inp) # CHANGE OVER HERE
.
.
.
command = input("\n> ") # CHANGE OVER HERE
if command == 'exit':
return
.
.
.
print("Exit")
Beware that since two threads are running in parallel, the next output will print before you are done typing and pressing enter to the input (unless you can type really fast and have really fast reflexes). Hope this answers your question!
I am creating a text based game and in order to speed past dialogue I want the user to be able to hit 'Space' and skip past it.
import time
import sys
text_speed = .05
def slow_type(line, speed): #You input the dialogue and speed(smaller = faster)
for l in line:
sys.stdout.write(l)
sys.stdout.flush()
time.sleep(speed)
time.sleep(.5)
if <'Space'> pressed:
text_speed = 0
NL1 = "Huh, I see you finally came. "
slow_type(NL1, text_speed)
The Python representation of a space is u"\u0020", referred here.
And tested with:
if input('enter:') == u"\u0020":
print('pass')
else:
print('fail')
You can use the getch module.
import getch
while getch.getch() != ' ':
pass
Or on Windows, you can use msvcr.getch:
import msvcrt
while msvcrt.getch() != ' ':
pass
I will suggest you to use keyboard.
Here is a sample code to detect space, also note that the code will wait for space key to be pressed.
import keyboard
#.....
if keyboard.is_pressed("space"):
text_speed = 0
#....
I've worked with little personal assistant project lately and now I'm facing this problem/bug and I can't get over it.
Here's part of my code:
import os
import sys
from colorama import Fore, Back, Style
import random
from os import system
from src import commands
system("title Assistant")
actions = {
"open":["pubg", "dota", "origins", "spotify", "dogs"],#"open":{["o"]:["pubg", "dota", "origins", "spotify", "dogs"]},
"hue":["1"],
"clear":"",
"hiber":"",
"shutdown":""
}
class MainClass:
#
logo1 = Fore.CYAN + """Not essential"""
logo2 = Fore.CYAN + """Not essential"""
errorcode = "Something went wrong :("
def getCommand(self):
cmd = input(Fore.MAGENTA + "Assistant > " + Fore.CYAN)
print("cmd: " + cmd)
self.checkCommand(cmd)
def checkCommand(self, cmd):
actions = commands.Commands().actions
words = cmd.lower().split()
print("Words: " + ' '.join(words))
found = False
ekasana = ""
par = ""
print("running if " + words[0] + str(words[0] == "q"))
#Here's the problem. After I imput 'clear', which clear's the screen and runs mainInterface(2.0, randomthing), this if does not work.
# Here's the output
# Not essentialv 2.0
# By Dudecorn
# Assistant > q
# cmd: q
# Words: q
# running if qTrue
# self.errorcode
# clear
# ['clear']
# Assistant >
# Why is is that clear command staying there? I am so confused right now.
# Read line 68
if words[0] == "q":
quit()
sys.exit()
for word in words:
word = ''.join(word)# Sorry about the mess
print(word)
# Check for action without parameters
if word in actions and actions[word] == "" and found == False:
try: # I'm pretty sure that this part of code is causing the problem
# If you remove try and except, and leave just lines 70 and 71, code works as line 58 if statement's value is true.
# This is in the another file -> getattr(commands.Commands, word)(self)
self.mainInterface(2.0, random.randint(1, 2))
break
except:
print("self.errorcode")
print(word)
print(words)
# Check for action that has parameters
elif word in actions and not actions[word] == "" and found == False:
ekasana = word
found = True
# Check for parameters
elif not ekasana == "" and found == True:
for n in actions[ekasana]:
if n == word:
par = word
try:
getattr(commands.Commands, ekasana)(self, par)
except:
print(self.errorcode)
else:
print("Command not found")
self.getCommand()
def mainInterface(self, v, logo):
os.system('cls' if os.name == 'nt' else 'clear')
if logo == 1:
print(self.logo1+"v "+str(v)+"\n By Dudecorn")
else:
print(self.logo2+"v "+str(v)+"\n By Dudecorn")
self.getCommand()
And here's the main file
import test
import random
def main():
m = test.MainClass()
m.mainInterface(2.0, random.randint(1,2))
main()
So, when you run the code and first input 'clear' and then q the if statement won't execute. And I wonder why. I also noticed that if you remove try and except from first if statement after loop the code works perfectly. I could remove them but it wouldn't answer my question, why isn't the code working. Also removing try and except from the file should not have any effect on how the first if statement executes, as it comes up later in the code.
Sorry about bad english as it isn't my main language, and thank you for your answers. Also I want to apologize for that huge mess in the code.
I am not sure if this is the answer that you are looking for, but it may be useful to check.
From the code below,
def main():
m = test.MainClass()
m.mainInterface(2.0, random.randint(1,2))
main()
, I see that by running main(), you also run the m.mainInterface function.
Now..you may want to check this :
The method mainInterface will eventually call the method getCommand, which will eventually call checkCommand, which..will encounter the try block in the for word in actions loop, and inside this try block..there is a calling of mainInterface again..so this process will be keep repeating.
I am wondering how to get Python to validate an input line and complete tasks using the input as it's being typed.
For example:
alphabet = "abcdefghijklmnopqrstuvwxyz"
useralphabet = input("Please enter however much of the alphabet you know:")
while(useralphabet in alphabet):
break
print("You know", 26 - len(useralphabet), "letters of the alphabet!")
Obviously I know this code won't work as intended, but I hope it demonstrates the idea of what I'm trying to do, i.e., get the user to input text until that they have entered is no longer part of the specified string.
The answer depends on your OS (operating system). Usually the OS hands over the input string to python only after you hit the ENTER key. If you need to do it as you type, you may need to invoke some system dependent calls to turn off input buffering.
See Python read a single character from the user for more on this
Here is a working example (Tested with Python 3.8.6 on Linux). See this answer if you need to modify for other systems.
The hinput function reads chars as they are typed and calls on_char for each new character passing both the new char and the entire line.
In my example when the user types x the on_char function returns True which causes the hinput function to stop waiting for new input.
If the user types hello it is autocompleted to hello, world and also terminates hinput
import sys
import termios
import tty
from typing import Callable
def main():
hinput("prompt: ", on_char)
return 0
def on_char(ch: str, line: str) -> bool:
if ch == 'x':
sys.stdout.write('\n')
sys.stdout.flush()
return True
if line+ch == 'hello':
sys.stdout.write("%s, world\n" % ch)
sys.stdout.flush()
return True
return False
def hinput(prompt: str=None, hook: Callable[[str,str], bool]=None) -> str:
"""input with a hook for char-by-char processing."""
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
inpt = ""
while True:
sys.stdout.write('\r')
if prompt is not None:
sys.stdout.write(prompt)
sys.stdout.write(inpt)
sys.stdout.flush()
ch = None
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
if hook is not None and hook(ch, inpt):
break
if ord(ch) == 0x7f: #BACKSPACE
if len(inpt) > 0:
sys.stdout.write('\b \b')
inpt = inpt[:-1]
continue
if ord(ch) == 0x0d: #ENTER
sys.stdout.write('\n')
sys.stdout.flush()
break
if ch.isprintable():
inpt += ch
return inpt
if __name__ == '__main__':
sys.exit(main())
I'm creating part of a program right now for a personal project and I need some help on one aspect of it.
Here is how the program works:
User enters the amount of time to run
User enters the text - Files are modified
Timer is started
optional User can enter "password" to interrupt the timer
Actions are reversed
I have all of the steps coded except the Timer because I'm trying to figure out the best way to do this. Ideally, I'd like the timer to be displaying a countdown, and if the user enters a certain "password" the timer is interrupted and it skips to step number 5.
Would the best way to do this be with a thread? I haven't worked much with threads in the past. I just need someway for the timer to be displayed while also giving control back to the user in case they want to enter that password.
Thanks for any help you provide.
Here's the code:
import time
import urllib
import sys
def restore():
backup = open(r'...backupfile.txt','r')
text = open(r'...file.txt', 'w+')
text.seek(0)
for line in backup:
text.write(line)
backup.close()
text.close()
text = open(r'...file.txt', 'a+')
backup = open(r'...backupfile.txt','w+')
text.seek(0)
for line in text:
backup.write(line)
backup.close()
while True:
url = raw_input('Please enter a URL: ')
try:
if url[:7] != 'http://':
urllib.urlopen('http://' + url)
else:
urllib.urlopen(url)
except IOError:
print "Not a real URL"
continue
text.write(url)
while True:
choice = raw_input('Would you like to enter another url? (y/n): ')
try:
if choice == 'y' or choice == 'n':
break
except:
continue
if choice == 'y':
text.seek(2)
continue
elif choice == 'n':
while True:
choice = raw_input('Would you to restore your file to the original backup (y/n): ')
try:
if choice == 'y' or choice == 'n':
break
except:
continue
if choice == 'y':
text.close()
restore()
sys.exit('Your file has been restored')
else:
text.close()
sys.exit('Your file has been modified')
As you can see, I haven't added the timing part yet. It's pretty straight forward, just adding urls to a text file and then closing them. If the user wants the original file, reverse() is called.
Under Windows you can use msvcrt to ask for a key. Asking for a password is actually more complex, because you have to track several keys. This program stops with F1.
import time
import msvcrt
from threading import Thread
import threading
class worker(Thread):
def __init__(self,maxsec):
self._maxsec = maxsec
Thread.__init__(self)
self._stop = threading.Event()
def run(self):
i = 1
start = time.time()
while not self.stopped():
t = time.time()
dif = t-start
time.sleep(1) # you want to take this out later (implement progressbar)
# print something once in a while
if i%2==0: print '.',
#check key pressed
if msvcrt.kbhit():
if ord(msvcrt.getch()) == 59:
self.stop()
#do stuff
# timeout
if dif > self._maxsec:
break
i+=1
def stop(self):
print 'thread stopped'
self._stop.set()
def stopped(self):
return self._stop.isSet()
print 'number of seconds to run '
timeToRun = raw_input()
#input files
#not implemented
#run
w = worker(timeToRun)
w.run()
#reverse actions