How to use _Getch? - python

I want to detect a keypress in my code to allow the running processes to advance only if a certain time has passed and the user has requested the code to advance (must be cross-platform).
The problem is I am using Windows (so I can use msvcrt) but people that will run my code may (and probably will) have Linux or MacOS and I found some code online by Danny Yoo (http://code.activestate.com/recipes/134892/) which is supposed to do what I want but I don't know how to use it properly. I'm not even sure how to import it into my code even though it's in the same directory.
class _Getch:
"""Gets a single character from standard input. Does not echo to the screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
getch = _Getch()
I tried calling import _Getch in my code and creating a variable getch = _Getch._Getch() but I don't feel this is doing what I want. What I want is to have this supposed cross-platform getch to detect if the user types 'n' for example and then continues to the next part of the process code.

Related

Python 3.8.0 Weird printing bug

I have a very weird print() bug while using Threads and Termios. I have a repeating Thread catching a key via Termios while printing some stuff. But always it prints a new line it doesn't start at the beginning of the line but where the last line ended.
This is my code:
def func1():
while True:
try:
var = int(inputChar())
except ValueError:
var = 0
Thread(target=func1).start()
while True:
print("stuff")
time.sleep(2)
This is my inputChar() function:
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
This ist the expected output:
stuff
stuff
stuff
stuff
stuff
This is the output:
stuff
stuff
stuff
stuff
stuff
I have no idea why this is happening but you can fix it by replacing the print command with
print("stuff\r")
or
sys.stdout.write("stuff\n\r")
the \r at the end is known as a cartridge return. It's useful when sys.stdout.write() is not a viable option

Python move cursor two places forward

I have written the program below which is intended to act as the terminal UI for a chat I am about to build for a university project. At its present state, the aim is to always have the last line act as the area to write a message, and when enter is pressed the message is written above and the last line becomes blank again.
That works fine if that's all I want. But, I also want to always have a number of "prompt symbols" at the start of the last line, namely here :>. To make that happen, when enter is pressed,the whole current line is deleted, the bare message is printed, a newline character inserted, and finally I wish to print the :> in the new line and repeat.
What happens, however, is that the prompt string is indeed printed, but the cursor, after the first enter pressed, begins at the start of the line, which means any subsequent input will overwrite the prompt characters. That does not happen the first time for some reason, where the first prompt is printed before anything else happens.
So in the end, I would like for a way for the cursor to actually start after the two prompt characters when a newline is printed. This is all I want as regards to functionality of the terminal, therefore I would like to find an easy way to solve this and be done with it instead of meddling with ncurses library and the likes. Thank you all for your time. The point of interest in the code where happens whatever I want to happen is inside the last while loop.
The code should be run with Python3.
import sys
from string import printable
import termios
import tty
class _Getch:
"""Gets a single character from standard input. Does not echo to the
screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
# ch = sys.stdin.read(1)
ch = sys.stdin.read(1)[0]
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
# enter: ord 13
#backspace: ord 127
current_input = ""
prompt_msg = ":> "
print(10*"\n"+prompt_msg,end="")
getch = _Getch()
def clear_input():
linelen = (len(current_input)+len(prompt_msg))
sys.stdout.write("\b"*linelen+" "*linelen+"\b"*linelen)
sys.stdout.flush()
while(True):
ch=getch()
if ord(ch)==3:# ctrl+c
exit()
# >>>>>>>.POINT OF INTEREST<<<<<<<<<<<
if ord(ch)==13:# enter
clear_input()
print(current_input)
current_input = ""
# print(prompt_msg,end="")
sys.stdout.write(prompt_msg)
sys.stdout.flush()
if ord(ch)==127 and len(current_input)>0:# backspace
sys.stdout.write("\b"+" "+"\b")
sys.stdout.flush()
current_input=current_input[:-1]
if ch in printable or ord(ch)>127: # printable
current_input+=ch
sys.stdout.write(ch)
sys.stdout.flush()
Rather than trying to get the pointer to go two places forward - which I had no luck finding an answer for - I simply deleted the carriage return character ("\r") from the current_input string in every place that should be done - there were rogue carriage return characters surviving in the string as it seems, that caused the problem.

How can I create a Python script that runs in the background and reacts to keyboard input?

At the moment I'm using AutoHotKey for triggering scripts via keyboard shortcuts. I like programming in Python much more than dealing with AutoHotKey and every time I touch my AutoHotKey scripts I wish I could simply write clean AutoHotkey code.
Let's take the simply AutoHotKey script that prints hello world in whatever window I am when I press the insert key:
foo(){
send, "hello world"
}
Insert:: foo()
How would I do the same thing in Python3 on Windows?
You will have to hook into the gizzards of windows to achieve this. You'd probably have to do that via the ctypes or CFFI module, because the necessary API's don't seem to exist in pywin32.
According to this page, you will need to use three windows API calls:
SetWindowsHookEx, using the idHook WH_KEYBOARD_LL to set up a keyboard hook; a function that peeks at keyboard events.
UnhookWindowsHookEx to eventually remove the hook.
And if you're not interested in a particular keypress, CallNextHookEx to pass it on to the next hook.
You can combine two answers from StackOverflow to (almost) solve this issue.
Use this answer (by tehvan) to create a getch() like method to read in one character from the user without the need for a \n. (repeated below from the answer)
Use the Python3 version of this answer (by Barafu Albino) to call the previously defined _Getch() class in a separate process.
Please note that the following code works for Python3 only and uses any key to stop the process, not just the insert key.
# This code is a combination of two StackOverflow answers
# (links given in the answer)
# ------- Answer 1 by tehvan -----------------------------------
class _Getch:
"""Gets a single character from standard input.
Does not echo to the screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
getch = _Getch()
# -------- Answer 2 by Barafu Albino (modified) -------
import _thread
def input_thread(a_list):
_Getch().__call__()
a_list.append(True)
def do_stuff():
a_list = []
_thread.start_new_thread(input_thread, (a_list,))
print('Press any key to stop.')
while not a_list:
pass
# This is where you can put the stuff
# you want to do until the key is pressed
print('Stopped.')
do_stuff()

unexpected indent outputs of print function with multithreads in python

I have been trying to write a keyboard listener without installing any packages. What I wanted was to create a non-blocking way of reading only one character of user input. So I created another thread besides the main one. Here is my code:
import sys, os
import thread
import time
try:
from msvcrt import getch
except ImportError:
def getch():
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
char = None;
def key_listener():
global char;
while True:
char = getch()
# escape key to exit
if ord(char) == 27:
break
#print char #comment this line for test
thread.start_new_thread(key_listener, ())
while True:
print("Whatever")
time.sleep(1);
And the printed strings are a bit weird:
Yinans-MacBook-Pro:anaconda game Yinan$ python inputtest.py
Whatever
Whatever
Whatever
Whatever
Whatever
Whatever
See those indents? I never expected to have that. And I have been trying a whole afternoon to solve it but failed. Does anybody know how to solve this? I will be so grateful. (btw I'm using a macbook pro.)
Putting STDIN in raw mode put STDOUT in raw mode as well, so the normal \n is not expanded to a CRLF. You will need to print a \r at the end of your string in order to return the cursor to the first column.

Reading a single character (getch style) in Python is not working in Unix

Any time I use the recipe at http://code.activestate.com/recipes/134892/ I can't seem to get it working. It always throws the following error:
Traceback (most recent call last):
...
old_settings = termios.tcgetattr(fd)
termios.error: (22, 'Invalid argument)
My best thought is that it is because I'm running it in Eclipse so termios is throwing a fit about the file descriptor.
This is working on Ubuntu 8.04.1 , Python 2.5.2, i get no such error. May be you should try it from command line, eclipse may be using its own stdin, i get exact same error if I run it from Wing IDE, but from command line it works great.
Reason is that IDE e.g Wing is using there own class netserver.CDbgInputStream as sys.stdin
so sys.stdin.fileno is zero, thats why the error.
Basically IDE stdin is not a tty (print sys.stdin.isatty() is False)
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
getch = _GetchUnix()
print getch()
Putting terminal into raw mode isn't always a good idea. Actually it's enough to clear ICANON bit. Here is another version of getch() with timeout support:
import tty, sys, termios
import select
def setup_term(fd, when=termios.TCSAFLUSH):
mode = termios.tcgetattr(fd)
mode[tty.LFLAG] = mode[tty.LFLAG] & ~(termios.ECHO | termios.ICANON)
termios.tcsetattr(fd, when, mode)
def getch(timeout=None):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
setup_term(fd)
try:
rw, wl, xl = select.select([fd], [], [], timeout)
except select.error:
return
if rw:
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
if __name__ == "__main__":
print getch()

Categories