Polling della tastiera (rileva un tasto premuto) in Python
-
08-07-2019 - |
Domanda
Come posso eseguire il polling della tastiera da un'app python per console? In particolare, vorrei fare qualcosa di simile a questo nel mezzo di molte altre attività di I / O (selezione socket, accesso alla porta seriale, ecc.):
while 1:
# 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
Qual è il modo pitone corretto per farlo su Windows? Inoltre, la portabilità su Linux non sarebbe male, anche se non è richiesta.
Soluzione
L'approccio standard consiste nell'utilizzare il modulo select .
Tuttavia, questo non funziona su Windows. Per questo, puoi utilizzare il msvcrt polling da tastiera del modulo.
Spesso, questo viene fatto con più thread: uno per dispositivo è "visto" oltre ai processi in background che potrebbero dover essere interrotti dal dispositivo.
Altri suggerimenti
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
Una soluzione che utilizza il modulo curses. Stampa di un valore numerico corrispondente a ciascun tasto premuto:
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, dato che il mio tentativo di pubblicare la mia soluzione in un commento non è riuscito, ecco cosa stavo cercando di dire. Potrei fare esattamente quello che volevo da Python nativo (su Windows, non altrove) con il seguente codice:
import msvcrt
def kbfunc():
x = msvcrt.kbhit()
if x:
ret = ord(msvcrt.getch())
else:
ret = 0
return ret
Nessuna di queste risposte ha funzionato bene per me. Questo pacchetto, pynput, fa esattamente quello di cui ho bisogno.
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()
Potresti vedere come pygame gestisce questo per rubare alcune idee.
Dai commenti:
import msvcrt # built-in module
def kbfunc():
return ord(msvcrt.getch()) if msvcrt.kbhit() else 0
Grazie per l'aiuto. Ho finito per scrivere una DLL C chiamata PyKeyboardAccess.dll e accedere alle funzioni crt conio, esportando questa routine:
#include <conio.h>
int kb_inkey () {
int rc;
int key;
key = _kbhit();
if (key == 0) {
rc = 0;
} else {
rc = _getch();
}
return rc;
}
E accedo ad esso in Python usando il modulo ctypes (integrato in 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 1:
x = kbfunc()
if x != 0:
print "Got key: %d" % x
else:
time.sleep(.01)
Lo sto usando per verificare la pressione dei tasti, non posso essere molto più semplice:
#!/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)
Mentre maledizioni non funziona su Windows, esiste una versione "unicurses", presumibilmente funzionante su Linux, Windows, Mac, ma non sono riuscito a farlo funzionare
Ho riscontrato un'implementazione multipiattaforma di kbhit
in http://home.wlu.edu/~levys/software/kbhit.py (apportate modifiche per rimuovere il codice irrilevante):
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 != []
Assicurati di read ()
i caratteri in attesa - la funzione continuerà a restituire True
fino a quando non lo fai!
Se combini time.sleep, threading.Thread e sys.stdin.read puoi facilmente attendere un determinato periodo di tempo per l'input e poi continuare, anche questo dovrebbe essere compatibile multipiattaforma.
t = threading.Thread(target=sys.stdin.read(1) args=(1,))
t.start()
time.sleep(5)
t.join()
Puoi anche inserirlo in una funzione come questa
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
Anche se questo non restituirà nulla, quindi dovresti usare il modulo pool multiprocessore, puoi trovarlo qui: come ottenere il valore restituito da un thread in python?