Hide non-standard python user input - python

I am creating a console game, which takes user key presses.
I had trouble with hiding the letters that then spread across the screen simply because the user is pressing a button.
It won't work with modules like getpass, and I have already tried various ANSI codes to attempt to hide the text. The background is also full of text characters and symbols, so that stops just a complete ANSI character disappearo. I also don't wanna have to call os.system("clear") every frame, because even calling it once every second bugs out the terminal.
What I want to know, is whether there is a way to catch key presses without having said key appear on the console
Here is the board class I am using, and the draw() method inside is how I have been drawing it to the terminal:
class board:
def __init__(self,length):
import random
self.random=random
self.characters=[" ","░","▒","▓","█"]
self.length=length
self.dithering=False
self.board=[[self.random.choice(self.characters) for y in range(self.length)] for x in range(self.length)]
def draw(self,colour=None):
if colour==None:
colour=RGB(0,1,0)
for x in range(len(self.board)):
for y in range(len(self.board)):
if self.board[y][x]==None:
continue
f=1
if self.dithering==True:
f=self.random.random()+0.5 # faintness
print(f"\u001b[{y};{x*2+1}H\x1b[38;2;{int(colour.r*f)};{int(colour.g*f)};{int(colour.b*f)}m{str(self.board[y][x])*2}",end="")
print("\x1b[0m")
def redecorate(self,characters=None):
if characters==None:
characters=self.characters
self.board=[[self.random.choice(characters) for y in range(self.length)] for x in range(self.length)]
def empty(self):
self.board=[[None for y in range(self.length)] for x in range(self.length)]
class RGB:
def __init__(self,r,g,b):
self.r=r*255
self.g=g*255
self.b=b*255

To catch key presses without displaying them you need getch function. On Windows you can use msvcrt module to capture it, while on Unix-like and Linux platforms you need to implement it by yourself, although it is easy.
Here is code for Windows:
from msvcrt import getch
And for Linux and Unix-like platforms:
import sys
import termios
import tty
def getch() -> str:
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
NOTE: some keys are more than one char long (like arrow keys which are 3 char long) so you will need to run getch few times to fully capture it.

Related

How to get a command to trigger with any keypress

import time, threading, os, sys, tty, termios
from colr import color
def slow_type_interrupt(phrase,speed,r_value,g_value,b_value):
done = False # this acts as the kill switch, using if statements, you can make certain button presses stop the message printing and outright display it
def type_out():
for char in phrase:
if done:
break
sys.stdout.write(color(char,fore=(r_value,g_value,b_value)))
sys.stdout.flush()
time.sleep(speed)
os.system('clear')
print(color(phrase,fore=(r_value,g_value,b_value)))
t = threading.Thread(target=type_out)
t.start()
def getkey():
ky = sys.stdin.fileno()
Ab = termios.tcgetattr(ky)
try:
tty.setraw(sys.stdin.fileno())
key = sys.stdin.read(1)
finally:
termios.tcsetattr(ky, termios.TCSADRAIN, Ab)
return key
while not done:
key_press = getkey()
if key_press == 'a':
done = True
os.system('clear')
print(color(phrase,fore=(r_value,g_value,b_value)))
slow_type_interrupt("Hello this is a test. Pressing 'a' will end this and immediatley display the message",.05,0,255,0)
I was making an answer for a question on here (The person was wondering how to make a "typing effect" where the letters are printed one after each other to give the effect that someone is typing the message out) I used a function to type out the message. Anyway, there is the "kill key" which stops the printing effect and just straight up prints the message instantly (in case the player wants to skip the effect). I was wondering if there was anyway to have it take any key as an input to skip the dialogue. For example, I want it to set done to True if any key is pressed.

Multimedia Keys in Python (Linux)

I want to detect when the XF86Launch1 key is pressed on my keyboard, using Python.
I have a headless server with a Bluetooth connected keyboard. I'd like to launch a command-line program whenever a specific multimedia key is pressed.
At the moment, I'm using:
import sys
import tty, termios
def getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
print getch()
But it won't detect multimedia keys. Nothing prints out when I press them.
Is there a way to detect these keys on a headless Ubuntu box - or a better way to launch a program on keypress?
Rather than trying to read stdin of the tty, you can use linux's input device api to read them.
On linux, all input devices show up as a file under /dev/input (such as /dev/input/event0) and when one read()s from these files, structured data indicating when keys are pressed and released is returned.
In C, the libevdev library provides a wrapper around those. In python, you could use the python-evdev library. It should also be possible to read directly from the input device (though you may need to carefully read the kernel documentation & source to handle that properly).
I think that your problem is that multimedia keys do not map to terminal input.
It's possible that you could make progress by running xev to trap the key and xmodmap to map the key to a different input.
Alternatively, use something like TKinter and see if a graphical program doesn't collect the keypresses.
from Tkinter import *
root = Tk()
def key(event):
print "pressed", repr(event.char)
def callback(event):
frame.focus_set()
frame = Frame(root, width=100, height=100)
frame.bind("<Key>", key)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()
Another possibility is to map to an F key instead of a multimedia key. (i.e. F9)
Edit: Further research into this resulted in these two links:
Extra Keyboard Keys
Extra Keyboard Keys in Console
The console itself does not support multimedia keys. But it does support custom F keys. F30-F246 are always free. Rather than map to XF86Launch1, map to F70. Then map F70 to keyboard input in your keymap, or use the Python script you already wrote to handle it.
pycopia may be an option. I am using it with this bluetooth button and it seems to work fairly well. I am still working on getting it to reconnect to the button when the button goes to sleep and then comes back. Here's part of the script that I'm using:
keyboard.py:
from pycopia.OS.Linux import Input
from pycopia.OS.Linux import event
class Satechi(Input.EventDevice):
DEVNAME = 'Satechi'
def register_callback(self, cb):
self._callback = cb
def poll(self):
while 1:
ev = self.read()
if ev.evtype == event.EV_KEY:
self._callback(ev)
read_handler = poll
button.py
from keyboard import Satechi
def callback(event):
pass #Do something fun
if __name__ == '__main__':
pm = Satechi()
pm.find()
pm.register_callback(callback)
while 1:
try:
pm.poll()
except OSError:
pm = Satechi()
while True:
try:
pm.find()
pm.register_callback(callback)
break
except IOError:
pass
pm.close()
Where DEVNAME is the devices name in /proc/bus/input/devices.
You can print event in the callback to figure out what the code and value is for the button you are looking for
Try to read xinput test <id> stdout in a loop and catch the events you need.
Here is some example in Bash:
#!/bin/bash
keyboard_id=9 # use xinput to find your keyboard id
xinput test $keyboard_id | while read line ; do
case $line in
"key press 44") echo -e "\n == j pressed ==" ;;
"key press 45") echo -e "\n == k pressed ==" ;;
esac
done

Real-time capture and processing of keypresses (e.g. keypress event)

Note: I want to do this without using any external packages, like PyGame, etc.
I am attempting to capture individual keypresses as they arrive and perform an action for specific characters, whether I simply want to "re-echo" the character, or not display it at all and do something else.
I have found a cross-platform (though not sure about OS X) getch() implementation because I do not want to read a whole line like input() does:
# http://code.activestate.com/recipes/134892/
def getch():
try:
import termios
except ImportError:
# Non-POSIX. Return msvcrt's (Windows') getch.
import msvcrt
return msvcrt.getch
# POSIX system. Create and return a getch that manipulates the tty.
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
[Attempt 1]
I first tried a simple while-true loop to poll getch, but if I type too fast, characters go missing. Reducing the sleep time makes it worse. The debug statements only print on press of the enter key and not consistently in time nor position. (It appears there might be some line buffering going on?) Taking out the loop and sleep lets it work once but perfectly.
#!/usr/bin/env python3
import sys
import tty
import time
def main():
while True:
time.sleep(1)
sys.stdout.write(" DEBUG:Before ")
sys.stdout.write(getch())
sys.stdout.write(" DEBUG:After ")
if __name__ == "__main__":
main()
[Attempt 2]
I got an example for using a threaded approach (https://stackoverflow.com/a/14043979/2752206) but it "locks up" and won't accept any input (including Ctrl-C, and etc)..
#!/usr/bin/env python3
import sys
import tty
import time
import threading
key = 'Z'
def main():
threading.Thread(target=getchThread).start()
while True:
time.sleep(1)
sys.stdout.write(" DEBUG:Before ")
sys.stdout.write(key)
sys.stdout.write(" DEBUG:After ")
def getchThread():
global key
lock = threading.Lock()
while True:
with lock:
key = getch()
if __name__ == "__main__":
main()
Does anyone have any advice or guidance? Or more importantly, can someone explain why the two attempts do not work? Thanks.
First off, I don't really thing you need multithreading. You'd need that if you, for example, wanted to do some tasks like drawing on screen or whatever and capturing keys while you do this.
Let's consider a case where you only want to capture keys and after each keypress execute some action: Exit, if x was pressed, otherwise just print the character. All you need for this case is simple while loop
def process(key):
if key == 'x':
exit('exitting')
else:
print(key, end="", flush=True)
if __name__ == "__main__":
while True:
key = getch()
process(key)
Notice absence of sleep(). I am assuming you thought getch() won't wait for user input so you set 1s sleep time. However, your getch() waits for one entry and then returns it. In this case, global variable is not really useful, so you might as well just call process(getch()) inside the loop.
print(key, end="", flush=True) => the extra arguments will ensure pressed keys stay on one line, not appending newline character every time you print something.
The other case, where you'd want to execute different stuff simultaneously, should use threading.
Consider this code:
n = 0
quit = False
def process(key):
if key == 'x':
global quit
quit = True
exit('exitting')
elif key == 'n':
global n
print(n)
else:
print(key, end="", flush=True)
def key_capturing():
while True:
process(getch())
if __name__ == "__main__":
threading.Thread(target=key_capturing).start()
while not quit:
n += 1
time.sleep(0.1)
This will create global variable n and increment it 10 times a second in main thread. Simultaneously, key_capturing method listens to keys pressed and does the same thing as in previous example + when you press n on your keyboard, current value of the global variable n will be printed.
Closing note: as #zondo noted, you really missed braces in the getch() definition. return msvcrt.getch should most likely be return msvcrt.getch()

Retrieving intermediate values for raw_input [duplicate]

How can I poll the keyboard from a console python app? Specifically, I would like to do something akin to this in the midst of a lot of other I/O activities (socket selects, serial port access, etc.):
while True:
# doing amazing pythonic embedded stuff
# ...
# periodically do a non-blocking check to see if
# we are being told to do something else
x = keyboard.read(1000, timeout = 0)
if len(x):
# ok, some key got pressed
# do something
What is the correct pythonic way to do this on Windows? Also, portability to Linux wouldn't be bad, though it's not required.
The standard approach is to use the select module.
However, this doesn't work on Windows. For that, you can use the msvcrt module's keyboard polling.
Often, this is done with multiple threads -- one per device being "watched" plus the background processes that might need to be interrupted by the device.
A solution using the curses module. Printing a numeric value corresponding to each key pressed:
import curses
def main(stdscr):
# do not wait for input when calling getch
stdscr.nodelay(1)
while True:
# get keyboard input, returns -1 if none available
c = stdscr.getch()
if c != -1:
# print numeric value
stdscr.addstr(str(c) + ' ')
stdscr.refresh()
# return curser to start position
stdscr.move(0, 0)
if __name__ == '__main__':
curses.wrapper(main)
Ok, since my attempt to post my solution in a comment failed, here's what I was trying to say. I could do exactly what I wanted from native Python (on Windows, not anywhere else though) with the following code:
import msvcrt
def kbfunc():
x = msvcrt.kbhit()
if x:
ret = ord(msvcrt.getch())
else:
ret = 0
return ret
None of these answers worked well for me. This package, pynput, does exactly what I need.
https://pypi.python.org/pypi/pynput
from pynput.keyboard import Key, Listener
def on_press(key):
print('{0} pressed'.format(
key))
def on_release(key):
print('{0} release'.format(
key))
if key == Key.esc:
# Stop listener
return False
# Collect events until released
with Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()
import sys
import select
def heardEnter():
i,o,e = select.select([sys.stdin],[],[],0.0001)
for s in i:
if s == sys.stdin:
input = sys.stdin.readline()
return True
return False
From the comments:
import msvcrt # built-in module
def kbfunc():
return ord(msvcrt.getch()) if msvcrt.kbhit() else 0
Thanks for the help. I ended up writing a C DLL called PyKeyboardAccess.dll and accessing the crt conio functions, exporting this routine:
#include <conio.h>
int kb_inkey () {
int rc;
int key;
key = _kbhit();
if (key == 0) {
rc = 0;
} else {
rc = _getch();
}
return rc;
}
And I access it in python using the ctypes module (built into python 2.5):
import ctypes
import time
# first, load the DLL
try:
kblib = ctypes.CDLL("PyKeyboardAccess.dll")
except:
raise ("Error Loading PyKeyboardAccess.dll")
# now, find our function
try:
kbfunc = kblib.kb_inkey
except:
raise ("Could not find the kb_inkey function in the dll!")
# Ok, now let's demo the capability
while True:
x = kbfunc()
if x != 0:
print "Got key: %d" % x
else:
time.sleep(.01)
I've come across a cross-platform implementation of kbhit at http://home.wlu.edu/~levys/software/kbhit.py (made edits to remove irrelevant code):
import os
if os.name == 'nt':
import msvcrt
else:
import sys, select
def kbhit():
''' Returns True if a keypress is waiting to be read in stdin, False otherwise.
'''
if os.name == 'nt':
return msvcrt.kbhit()
else:
dr,dw,de = select.select([sys.stdin], [], [], 0)
return dr != []
Make sure to read() the waiting character(s) -- the function will keep returning True until you do!
You might look at how pygame handles this to steal some ideas.
I am using this for checking for key presses, can't get much simpler:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import curses, time
def main(stdscr):
"""checking for keypress"""
stdscr.nodelay(True) # do not wait for input when calling getch
return stdscr.getch()
while True:
print("key:", curses.wrapper(main)) # prints: 'key: 97' for 'a' pressed
# '-1' on no presses
time.sleep(1)
While curses is not working on windows, there is a 'unicurses' version, supposedly working on Linux, Windows, Mac but I could not get this to work
One more option would be to use sshkeyboard library to enable reacting to key presses instead of polling them periodically, and potentially missing the key press:
from sshkeyboard import listen_keyboard, stop_listening
def press(key):
print(f"'{key}' pressed")
if key == "z":
stop_listening()
listen_keyboard(on_press=press)
Simply pip install sshkeyboard to use it.
This can be done using 'pynput' module in python,
You press a key and it gets printed It's that easy!
PIP Install the module in command prompt, write following text and press enter
pip install pynput
Run the following code:
from pynput.keyboard import Key, Listener
def pressed(key):
print('Pressed:',key)
def released(key):
print('Released:',key)
if key == Key.enter:
# Stop detecting when enter key is pressed
return False
# Below loop for Detcting keys runs until enter key is pressed
with Listener(on_press=pressed, on_release=released) as detector:
detector.join()
You can end the loop with any key you want by changing Key.enter to some other key in the 8th line of the code.
If you combine time.sleep, threading.Thread, and sys.stdin.read you can easily wait for a specified amount of time for input and then continue,
also this should be cross-platform compatible.
t = threading.Thread(target=sys.stdin.read(1) args=(1,))
t.start()
time.sleep(5)
t.join()
You could also place this into a function like so
def timed_getch(self, bytes=1, timeout=1):
t = threading.Thread(target=sys.stdin.read, args=(bytes,))
t.start()
time.sleep(timeout)
t.join()
del t
Although this will not return anything so instead you should use the multiprocessing pool module you can find that here: how to get the return value from a thread in python?

easiest way to pause a command-line python program?

Let's say I have a python program that is spitting out lines of text, such as:
while 1:
print "This is a line"
What's the easiest way to allow one to press a key on the keyboard to pause the loop, then to resume if pressed again---but if nothing is pressed it should just continue on automatically?
I'm hoping I don't have to go into something like curses to get this!
You could try this implementation for Linux / Mac (and possible other Unices) (code attribution: found on ActiveState Code Recipes).
On Windows you should check out msvcrt.
import sys, termios, atexit
from select import select
# save the terminal settings
fd = sys.stdin.fileno()
new_term = termios.tcgetattr(fd)
old_term = termios.tcgetattr(fd)
# new terminal setting unbuffered
new_term[3] = (new_term[3] & ~termios.ICANON & ~termios.ECHO)
# switch to normal terminal
def set_normal_term():
termios.tcsetattr(fd, termios.TCSAFLUSH, old_term)
# switch to unbuffered terminal
def set_curses_term():
termios.tcsetattr(fd, termios.TCSAFLUSH, new_term)
def putch(ch):
sys.stdout.write(ch)
def getch():
return sys.stdin.read(1)
def getche():
ch = getch()
putch(ch)
return ch
def kbhit():
dr,dw,de = select([sys.stdin], [], [], 0)
return dr <> []
Implementing what you're looking for would then become something like this:
atexit.register(set_normal_term)
set_curses_term()
while True:
print "myline"
if kbhit():
print "paused..."
ch = getch()
while True
if kbhit():
print "unpaused..."
ch = getch()
break
The easiest way for me, assuming I was working in bash, would be to hit Control-Z to suspend the job, then use the 'fg' command to restore it when I was ready. But since I don't know what platform you're using, I'll have to go with using ChristopheD's solution as your best starting point.
When you press Ctrl+C, a KeyboardInterrupt exception gets raised in your program. You can catch that exception to create the behavior you want - for instance, to pause the program and resume after 5s:
import time
while True:
try:
# This is where you're doing whatever you're doing
print("This is a line")
except KeyboardInterrupt:
print("Paused! Ctrl+C again within 5 seconds to kill program")
# A second KeyboardInterrupt that happens during this sleep will
# not be caught, so it will terminate the program
time.sleep(5)
print("Continuing...")
Or to pause the program indefinitely until the user hits 'enter':
while True:
try:
# This is where you're doing whatever you're doing
print("This is a line")
except KeyboardInterrupt:
print("Interrupted!")
input("Press enter to continue, or Ctrl+C to terminate.")
print("Continuing...")
If you want to catch a second KeyboardInterrupt as well and do something fancy with that, you can do so by nesting try/except blocks, though I wouldn't really recommend that - it's a good idea to allow a string of KeyboardInterrupts to terminate the program.

Categories

Resources