Hello,
I try to build a system which takes rectangular screen shoots from part of the screen. The part in question is defined by the user with his mouse and a special key. Each time the special key is pressed, the program should log the mouse position and define the size of the image to save.
My problem is that if i lose focus on the cmd windows of my program (by opening a photo on my desktop for exemple), the program doesn't listen anymore to my keypress. I don't understand how to do so that it keeps listening.
Any suggestion is appreciated as I don't know where to search !
I'm writing a program that automatically types sentences when you press keyboard shortcuts. I'm using autopy.key.type_string() for sending the keyboard events, but I need a way to be able to recognize when the keyboard shortcut is being pressed, even when my Python script doesn't have focus. After Googling a bit, it seems like these are the two most commonly suggested solutions:
-
Make a Tkinter window and get keyboard events from that. This won't work because the Tkinter window needs focus to get keyboard input, and if it has focus that means that
type_string()would be sending the text right back to that same window instead of the window I want. -
Hook some Win32 APIs. This won't work because I'm on Linux.
So is there a way of doing this on Linux? What would you guys recommend?
I know nothing of the details, but I'm pretty sure this requires hooking into the X server to listen for keyboard events, which may turn out to be difficult in pure Python. What I have done in the past is to use some other program to catch the keyboard shortcuts and call my script with different arguments (some part of your desktop environment can probably do this, or a standalone program like sxhkd).
I'd recommend .XCompose
How can I detect any key press in Python without the program losing focus
Can I detect key presses if my Python script is running in the background
How to handle special keys like arrow keys in Python
Try using the keyboard module. But there is one downside, you must run the file as root. But here is how you would do it:
import keyboard
import threading
# Do stuff
def key_handler():
while app_is_running:
key = keyboard.read_key()
if key == "W":
# do stuff
thread = threading.Thread(target=key_handler, daemon=True)
thread.start()
# Do stuff
This is not the only way you can do it, you can also use hotkeys
The start method from pynput's keyboard listener joins the listener with the current thread. So just call it in the thread that starts the Kivy application.
from kivy.app import App
from kivy.uix.label import Label
from pynput.keyboard import Listener
def on_press(key):
print(f"Key press was listened to from Kivy event loop! Key pressed: {key}")
class TestApp(App):
def build(self):
return Label(text="Check console to witness keyboard being listened to")
def run(self):
Listener(on_press=on_press).start()
super().run()
if __name__ == "__main__":
TestApp().run()
» pip install keyboard
There's an SDL environment variable for this! Simply setting it will enable background input.
import os
os.environ["SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = "1"
Add that to the top of the sample in the joystick docs and try it out.
Tested with pygame 2.1.2, sdl 2.0.18, Windows 10 (21H2), Xbox One like controller
ref:
- https://wiki.libsdl.org/SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS
- https://wiki.libsdl.org/CategoryHints
As per two comments directly above, IN order to keep focus on the pygame window (effectively keep the cursor within the bounds of the pygame window until a I was able to make this work with the following code:
pygame.event.set_grab(True) # Keeps the cursor within the pygame window
Combine this code with a way to quit the program with a key press such as the ESCAPE key (since it will be impossible to close the window by moving your cursor to the frame of the window to close it that way by pygame.QUIT):
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
run = False
I'm working on a little TUI file manager written in Python and C to try to bone up on programming again, but I'm not sure how you're actually supposed to go about capturing keystrokes only when the terminal running it is focused. Pynput and Keyboard modules both seem to capture input system wide.
On Linux, specifically Debian btw.
Ok, i figured it out, figured I would post the answer.
pythons keyboard module, docs and source here
as stated in the "Known Limitations" section, (even though I don't think this is a limitation!)
"To avoid depending on X, the Linux parts reads raw device files (/dev/input/input*) but this requires root."
So this does bring up security issues obviously since the program now needs root privileges, but for my case this is not an issue.
pip install keyboard
simple program
import keyboard
import time
def key_press(key):
print(key.name)
keyboard.on_press(key_press)
while True:
time.sleep(1)
I am currently working on a similar problem. I got an idea how to solve this. But did not try it yet. Not sure if I should put this into a new question.
- set up a virtual console that automatically logs in a user
- this console does not show an interactive shell (bash) but just a process that listens for key-presses and sends them to MPD
That's it. You still may switch to a standard console using Ctrl-Fx and then log-in normally without any side effects like unwanted control of the MPD.
There are still several problems unsolved:
does linux open up the virtual consoles at all if there is no monitor attached?
how to set up this console? /etc/login.defs?
what language and library to use for the listener process (I implemented everything in python until now)
how to pass the events to MPD. (through mpc command line client or python mpc client library?)
I marked this as community wiki. So anybody feel free to contribute to this answer.
Background: I implemented a headless audio-player using MPD on raspberry pi that currently is controlled via GPIO pins. Now I would like to be able to control it via keyboard as well.
I've been messing around with Pyautogui and having it do keystrokes and mouse clicks and it works great, but it obviously unfocuses the python shell with the mouse clicks. Some parts move quick and it's iffy to click the shell to focus it and then hit Ctrl+C. I'm hoping there is a way to program a keypress or something into the script to kill it whenever I need to.
One thought I had was to set the shell to the first slot on the taskbar in Windows and then attach a function to a keystroke, but I'm not sure how to attach a function to a keystroke. So something like this pseudocode:
If keypress = Ctrl + Shift + S:
keyboard.press_and_release('Win+1')
keyboard.press_and_release('Ctrl+C')Any ideas on how to accomplish this would be great though. Thanks guys.
I suggest not to use pyHook anymore, because it's an old, not maintained library (last updated in 2008) Alternatively there are many other libraries, which are actively maintained for example 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:
return False
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
keyboard can be an another alternative which may be worth considering.
pyHook seems like it would work well for this (mentioned by furas)
from pyHook import HookManager
from win32gui import PumpMessages, PostQuitMessage
class Keystroke_Watcher(object):
def __init__(self):
self.hm = HookManager()
self.hm.KeyDown = self.on_keyboard_event
self.hm.HookKeyboard()
def on_keyboard_event(self, event):
try:
if event.KeyID == keycode_youre_looking_for:
self.your_method()
finally:
return True
def your_method(self):
pass
def shutdown(self):
PostQuitMessage(0)
self.hm.UnhookKeyboard()
watcher = Keystroke_Watcher()
PumpMessages()