What Is the Easiest Way to Detect Key Presses in Python 3 on a Linux MAChine

What is the easiest way to detect key presses in python 3 on a linux machine?

This is a simple loop that will put stdin in raw mode (disabling buffering so you don't have to press enter) to get single characters. You should do something smarter (like a with statement to disable it) but you get the idea here:

import tty
import sys
import termios

orig_settings = termios.tcgetattr(sys.stdin)

tty.setcbreak(sys.stdin)
x = 0
while x != chr(27): # ESC
x=sys.stdin.read(1)[0]
print("You pressed", x)

termios.tcsetattr(sys.stdin, termios.TCSADRAIN, orig_settings)

I think you'd have to loop to detect key releases in Python.

ETA some more explanation:

On Linux, input to your program will be line buffered. This means that the operating system will buffer up input until it has a whole line, so your program won't even see anything the user typed until the user also hits 'enter'. In other words, if your program is expecting the user to type 'w' and the user does this, 'w' will be sitting in the OS's buffer until the user hits 'enter'. At this point the entire line is delivered to your program so you will get the string "w\n" as the user's input.

You can disable this by putting the tty in raw mode. You do this with the Python function tty.setcbreak which will make a call down the tty driver in linux to tell it to stop buffering. I passed it the sys.stdin argument to tell it which stream I wanted to turn buffering off for1. So after the tty.setcbreak call, the loop above will give you output for every key the user presses.

A complication, though, is that once your program exits, the tty is still in raw mode. You'll generally find this unsatisfying since you don't get any of the power that modern terminal settings offer (like when you use control or escape sequences). For example, notice that you might have trouble exiting the program with ctrl-C. Consequently you should put the terminal back into cooked mode once you are done reading input characters. The termios.tcsetattr call simply says "put the terminal back the way I found it". It knows how to do this by first calling termios.tcgetattr at the beginning of the program which is saying "tell me all the current settings for the terminal".

Once you understand all that, you should easily be able to encapsulate the functionality in a function that suits your program.

1 stdin is the stream that input comes to you from the user. Wikipedia can tell you more about standard streams.

How to key press detection on a Linux terminal, low level style in python

By default the standard input is buffered and uses canonical mode. This allows you to edit your input. When you press the enter key, the input can be read by Python.

If you want a lower level access to the input you can use tty.setraw() on the standard input file descriptor. This allows you to read one character at a time using sys.stdin.read(1). Note that in this case the Python script will be responsible for handling special characters, and you will lose some of the functionality like character echoing and deleting. For more information take a look at termios(3).

You can read about escape sequences which are used for up and down keys on Wikipedia.

You should be able to replicate the standard shell behavior if you handle everything in one process.

You may also want to try using a subprocess (not referring to the module - you can use fork() or popen()). You would parse the unbuffered input in the main process and send it to stdin (which can be buffered) of the subprocess. You will probably need to have some inter-process communication to share history with the main process.

Here is an example of the code needed to capture the input this way. Note that it is only doing some basic processing and needs more work in order to fit your use-case.

import sys
import tty
import termios

def getchar():
fd = sys.stdin.fileno()
attr = termios.tcgetattr(fd)
try:
tty.setraw(fd)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSANOW, attr)

EOT = '\x04' # CTRL+D
ESC = '\x1b'
CSI = '['

line = ''

while True:
c = getchar()
if c == EOT:
print('exit')
break
elif c == ESC:
if getchar() == CSI:
x = getchar()
if x == 'A':
print('UP')
elif x == 'B':
print('DOWN')
elif c == '\r':
print([line])
line = ''
else:
line += c

Python 3 pressed key detect

I got some answer, i don't know all imported libraries are builtin python or not, but it worked perfectly

#!/usr/bin/python3

# adapted from https://github.com/recantha/EduKit3-RC-Keyboard/blob/master/rc_keyboard.py

import sys, termios, tty, os, time

def getch():
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

button_delay = 0.2

while True:
char = getch()

if (char == "p"):
print("Stop!")
exit(0)

if (char == "a"):
print("Left pressed")
time.sleep(button_delay)

elif (char == "d"):
print("Right pressed")
time.sleep(button_delay)

elif (char == "w"):
print("Up pressed")
time.sleep(button_delay)

elif (char == "s"):
print("Down pressed")
time.sleep(button_delay)

elif (char == "1"):
print("Number 1 pressed")
time.sleep(button_delay)

How to detect key-press combinations in background on linux & windows with python?

on windows this can be done using
pyhook

on ubuntu I did it with help of this
pyxhook

Edit: another awesome library for Windows & Linux - keyboard

How to intercept keys pressed in (and only in) the terminal window of a Python program?

Have a look at the curses module. It's in the python standard library, but does not support windows out of the box.
There is a regularly maintained project called "windows-curses" you can have a look at. I have not tested it, but it supposedly will allow you to use the python curses module on windows.
https://pypi.org/project/windows-curses/

import curses

def listen(window):
while True:
key = window.getch()
window.addstr(f'You pressed the "{key}" key!\n')
if key == 'q':
break
handle_keypress(key)

curses.wrapper(listen)

If the curses approach doesn't work for you, or you still need a bit more granularity, then you can roll your own cross-platform approach fairly easily. You can try something like this:

from sys import platform

class Keyboard:
def __new__(cls):
if platform in ['Windows', 'win32', 'cygwin']:
cls = winKeyboard
elif platform in ['Mac', 'darwin', 'os2', 'os2emx']:
cls = MacKeyboard
else:
raise Exception(f'Unrecognized platform, {platform}')
return super(Keyboard, cls).__new__(cls)

def listen(self):
while True:
key = self.getch()
print(f'You pressed the "{key}" key!')
if key == 27:
break
return self.handle_key(key)

class MacKeyboard(Keyboard):
def getch(self):
implement_mac_logic()

class WinKeyboard(Keyboard):
def getch(self):
implement_win_logic()

keyboard = Keyboard()
keyboard.listen()

Keyboard.__new__ does the work of providing the appropriate solution at runtime for the current os.

This approach will still register key-presses regardless of the active window.

In order to do this, you will need access to the active window, which will be another os-specific procedure.
Have a look at this: https://stackoverflow.com/a/36419702/1420455

You could implement a function that checked the name of the current window

class Keyboard:
...
def listen(self):
while True:
if self.get_active_window() != desired_window:
continue
key = self.getch()
print(f'You pressed the "{key}" key!')
if key == 27:
break
return self.handle_key(key)

Then you can just implement the approate logic in WinKeyboard.get_active_window and MacKeyboard.get_active_window
This wont take into account being in different tabs. This may be possible, but I am not familiar enough with the apis to tell you.

There are also options such as pygame that will require you to create and manage your own windows but will meet your requirements.

Edit: Changed WinKeyboard and MacKeyboard to inherit from Keyboard.



Related Topics



Leave a reply



Submit