Archive
monitoring key presses in a console application in a thread
Problem
I wrote a console application that shows a table and updates the screen every second. Later, I wanted to add a table sorting functionality. For instance, if you press ‘b’, then the table is sorted by the 2nd column, pressing ‘c’ would sort the table by the 3rd column, etc.
I found some keyboard monitoring libraries but they were all blocking, i.e. you had to call a function which was waiting for a key press. If you didn’t press any key, this function was just waiting.
However, in my program I had an infinite loop that was doing the following steps: (1) clear the screen, (2) draw the table, (3) repeat. If I add anywhere the keyboard monitoring, the loop gets blocked somewhere.
Solution
I asked this question on reddit (see here), and /u/Viddog4 suggested that I should use a thread. Of course! I have the main loop that draws the table, and I have a thread in the background that monitors the keyboard.
Here is a simplified code that demonstrates the idea:
#!/usr/bin/env python3
"""
pip3 install pynput xlib
"""
import threading
from time import sleep
from pynput.keyboard import Key, Listener
class myThread(threading.Thread):
def __init__(self, _id, name):
super().__init__()
self.daemon = True # daemon threads are killed as soon as the main program exits
self._id = _id
self.name = name
def on_press(self, key):
print('{0} pressed'.format(key))
def on_release(self, key):
print('{0} release'.format(key))
if key == Key.esc:
# Stop listener
return False
def run(self):
with Listener(on_press=self.on_press, on_release=self.on_release) as listener:
listener.join()
def main():
thread1 = myThread(1, "thread_1")
thread1.start()
# main loop:
while True:
print(".", flush=True)
try:
sleep(1)
except KeyboardInterrupt:
break
##########
if __name__ == "__main__":
main()
You can stop the thread with Esc. You can terminate the whole program with Ctrl+C. The thread is registered as a daemon thread, which means that if the main program exits (e.g. you press Ctrl+C), then daemon threads are automatically stopped.
Links
- pynput, keyboard monitoring library
- An Intro to Threading in Python, to learn more about threads
- Different ways to kill a Thread, I chose to set it as a daemon thread
simple keylogger
I was working on a console application and I wanted to add the functionality to listen to keyboard presses in an infinite loop. I used the pynput library and tried this basic code that I found on the project’s web site:
# pip3 install pynput xlib
# xlib is required for mouse support
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()
It worked well. I let it run and switched to another window when I noticed that the script was still monitoring what keys I press, though I was in another window! So it monitors the keyboard globally. And, under Linux, I didn’t even have to start it with sudo.
So, if you need a simple keylogger, here it is :) You don’t need to add much to the code above to have a working keylogger.
Update (20240204):
Here is a slightly longer example:
from pynput.keyboard import Key, Listener
def on_press(key):
print("{0} pressed".format(key))
if key == Key.esc:
print("# you pressed ESC")
try:
if key.char == "a":
print("# you pressed 'a'")
#
except:
pass
def on_release(key):
print("{0} release".format(key))
print("---")
def main():
print("keylogger running...")
with Listener(on_press=on_press, on_release=on_release) as listener:
try:
listener.join()
except KeyboardInterrupt:
print()
if __name__ == "__main__":
main()
flush the stdin
Problem
I wrote a terminal application that was reading key presses from the keyboard using the pynput library. When the program terminated, it printed on the screen the keys that I pressed while the program was running. How to get rid of this side effect?
Solution
First I tried sys.stdin.flush() but it didn’t help. However, the following worked:
import sys import termios termios.tcflush(sys.stdin, termios.TCIOFLUSH)
Calling this line before quitting successfully flushed the standard input.
