I have a program that captures all key presses using pyHook, then runs a few functions.
I notice that after a while (of random duration), the program was stop receiving key triggers, even though I am pressing keys?
Is pyHook unstable?
I'm not changing what keys are pressed or pressing them prematurely or anything like that.
Here's my code:
import time
import win32api
import win32con
import pythoncom
import pyHook
import os
import ctypes
def Click(x,y):
win32api.SetCursorPos((x,y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,x,y,0,0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,x,y,0,0)
def DeleteRun(event):
if event.Ascii == 96:
BreakHook()
return False
Click(1250, 741)
time.sleep(0.2)
Click(649,261)
time.sleep(0.2)
Click(651, 348)
time.sleep(0.2)
Click(800, 442)
time.sleep(0.2)
Click(865, 612)
time.sleep(0.2)
Click(25, 744)
time.sleep(3)
Click(25, 744)
time.sleep(1.5)
Click(1112,297)
Click(145,392)
return True
def BreakHook():
ctypes.windll.user32.PostQuitMessage(0)
KeyGrabber = pyHook.HookManager()
KeyGrabber.KeyDown = DeleteRun
KeyGrabber.HookKeyboard()
pythoncom.PumpMessages()
Why does is suddenly stop working?
It's very frustrating as the process remains active on my computer, even if I stop the program through the IDE.
Specs:
python 2.7.2
Windows 7 (32)
Similar (dare I say identical?) problems were discussed and resolved here: pyHook + pythoncom stop working after too much keys pressed [Python]
and here: Pyhook stops capturing key events after 6 presses
You may be trying to do to much from withing the event callback.
Any event function callback as configured via HookManager and PumpMessages should return as quickly as possible.
When you press a key, Windows is kind enough to inform you of the event, but there may be other programs who also need the event. You are doing sleep calls within your event, but while you sleep, Windows is waiting for your response on THIS callback.
My guess is that after a certain number of opportunities to return in a timely manner, your event registration is being voided and ignored by Windows.
Move your sleep commands outside of the event, and instead trigger your actually click-sleep sequence outside of the hookmanager callback.
Edit: Links/Reference:
The PyHook API Documentation is one of the best (unfortunately), http://pyhook.sourceforge.net/doc_1.5.0/ If you notice the many things you can do from within the event, it becomes clear why time is of the essence. Windows wants to know how to handle the keypress (for example), and keypresses happen very fast, so it wants to know ASAP.
Its important to understand that PyHook is a very thin layer, and most of the functionality is provided by Windows, so the best documents are from MSDN http://msdn.microsoft.com/en-us/library/ms632589(v=vs.85).aspx. Also might want to take a look up a level at some of the information on 'Messages' (thats where our PumpMessages ultimately derives) The written text is very descriptive, and many of the constants and values are reflected properly through PyHook, although the good code segments are not written in Python.
Here is a pretty direct reference to proper handling of Messages (which is what hookmanager knows how to get, and by which PumpMessages delivers), http://msdn.microsoft.com/en-us/library/ms644927(v=vs.85).aspx
If a top-level window stops responding to messages for more than several seconds, the system considers the window to be not responding.
and
Message Handling
An application must remove and process messages posted to the message queues of its threads
When you call your sleeps, you are hanging in your current message, and neglecting the others that might be stacking up. Even if you grab the message and immediately return, Windows doesn't care what you do with it, as long as you are consuming.
Related
This question already has an answer here:
SendMessage doesn't work for some applications
(1 answer)
Closed last month.
I want to send keyboard input to a game running in the background (game: Knight Online) but the win32api.SendMessage, PostMessage methods are not working. how can I do that
I tried the code in this link
code:
from time import sleep
import win32gui
import win32con
def callback(handle, param):
s = win32gui.GetClassName(handle)
try:
print(f'Sending key to {handle}, {s}')
win32gui.SendMessage(handle, 0x0102, 0x5A, 0)
sleep(.5)
except Exception:
print('Exception sending to {handle}, {s}')
window_id = win32gui.FindWindow(None, "Knight OnLine Client")
win32gui.EnumChildWindows(window_id, callback, 0)
output:
Sending key to 23004754, Edit
Sending key to 1639850, Edit
Sending key to 10421696, Edit
the input does not work in the game
Why it doesn't work
There are many stops along the pipeline from a keypress to an application. And applications can choose where along that pipeline to receive keyboard input.
You're sending 0x0102 which is WM_CHAR. That's about as far to the end of the pipeline as you can go. It's likely the game is tapping the pipe earlier.
When you press a physical key, the keyboard driver places an input event in an input queue.
When the OS pulls that event from the input queue, it places a WM_KEYDOWN message in the message queue for the thread that owns the window with the input focus.
When the application's GUI thread pulls the message from queue, it may choose to route it through an API called TranslateMessage, which watches for low level keyboard messages like WM_KEYDOWN and WM_KEYUP.
TranslateMessage synthesizes WM_CHAR (and/or WM_UNICHAR) and sends them to the window (just before allowing the keyboard message to be processed). These messages tell the program that the user has typed a character (for example, a capital E with an acute accent) which can be done only with a series of keyboard messages.
Meanwhile, part of the system is tracking the state of the entire keyboard at different points in time.
One of those is the asynchronous keyboard state, which watches that input queue so that it knows what's happening on the keyboard right now. Games can query this with GetAsyncKeyState (and maybe with the legacy DirectInput API). A fast video game might rely on this (after checking that they are the "active" window).
There's also a synchronous keyboard state, which is tracked per GUI thread as the threads pull keyboard messages from their queues. Imagine if a thread fell behind and a lot of keyboard messages were still queued up. The synchronous keyboard state (from GetKeyState or GetKeyboardState) would indicate the keyboard state at the time the most recently processed window message was posted, which may be different that the asynchronous state which would already reflect all of the messages still in the queue.
The character input messages, like WM_CHAR, are useful for text editors, word processors, etc. But even they must tap the pipeline earlier for keystrokes like Page Up and Page Down.
Many apps work primarily with the WM_KEYDOWN and WM_KEYUP messages.
Fast video games likely use the asynchronous keyboard state or perhaps the synchronous one. And they may purposely not process any keyboard input when they are in the background.
What you can try
The easiest thing to try is to send (or better, post) WM_KEYDOWN and WM_KEYUP messages instead of WM_CHAR. That won't guarantee success.
Windows has the SendInput API for putting events in the input queue (the way the keyboard driver does). I don't know if there's a Python library that covers that. Even if there is, it's not likely to help, since the system won't send your injected keyboard input events to a background window.
I am trying to complete a simple GUI automation program that merely opens a web page and then clicks on a specific spot on the page every 0.2 seconds until I tell it to stop. I want my code to run and have its loop run infinitely until a keybind I specify breaks the loop (or entire program). I started out with the classic KeyboardInterrupt, which supposedly enables CTRL+C to exit a program. Here is my code:
import webbrowser, pyautogui, time
webbrowser.open('https://example.com/')
print('Press Ctrl-C to quit.')
time.sleep(5)
#pyautogui.moveTo(1061, 881)
try:
while True:
time.sleep(0.2)
pyautogui.click(1061,881)
except KeyboardInterrupt:
print('\nDone.')
Unfortunately, KeyboardInterrupt and using CTRL-C to exit do not seem to work for this script (likely due to the while loop?). This causes the loop to continue to run infinitely without a way to be stopped. So my questions are: why isn't the Keyboard Interrupt working? I've seen similar examples in other scripts. Additionally, if the KeyboardInterrupt doesn't work, is there a way I can code a simple keybind to exit the program/loop?
Use the following code
pyautogui.FAILSAFE = True
Then to stop, move the mouse to the upper-left corner of the screen
I suspect it may have something do to with you having a different active window than the script; when you use webbrowser, open a webpage, and click on it, it moves your active window to the webpage rather than the Python console. So ctrl+c will only produce a KeyboardInterrupt when the console is your active window. Your script may be in fact correct; but your active window is not on Python, so you would have to test it by clicking back into the Python console while the program runs.
To answer your comment: No, I do not know of any other "quick" way to do such a thing.
I'm late, but I can provide a solution which allows you to press CTRL + C to stop the program. You need to install the keyboard module and use keyboard.is_pressed() to catch when you press the keys you want as flag:
import keyboard
# You program...
while True:
time.sleep(0.2)
pyautogui.click(1061,881)
if keyboard.is_pressed("ctrl+c"):
break
You need to be careful though because the program will check if you have pressed the keys only when it executes the if statement. If your program runs for 10 seconds and you place the if at the end, you will only be able to exit every 10 seconds and for a very brief moment.
I also suggest to keep the keys pressed while you wait for the program to catch them to avoid missing the moment.
If you instead need to instantly terminate the program without having to always check if CTRL+C are pressed, you can place it in a process and kill it whenever you want. It's a bit overkill and it's not the recommended way, but if you really need it, here it is.
import pyautogui
import keyboard
import time
from multiprocessing import Process
def execute_program():
"""Long program which you want to interrupt instantly"""
while True:
pyautogui.click()
time.sleep(10)
if __name__ == '__main__':
# The failsafe allows you to move the cursor on the upper left corner of the screen to terminate the program.
# It is STRONGLY RECOMMENDED to keep it True
pyautogui.FAILSAFE = True
# Spawn and start the process
execute_program_process = Process(target=execute_program)
execute_program_process.start()
while True:
if keyboard.is_pressed('ctrl+c'):
execute_program_process.terminate()
break
print('\nDone.')
I'm making a personal assistant like Google Assistant or Siri, and I want the user to be able to set reminders. For example, if they type "Remind me to wash the dishes at 5pm" I would like it to pop up later and remind them. However I also want code to be able to run while waiting, so you could set multiple reminders or check the weather.
time.sleep simply stops the program. I'm pretty sure there's a way to do it with threads but I'm not sure how. Please help!
Python threading has a Timer which does exactly what you ask for:
from datetime import datetime
from threading import Timer
def create_notification(time, name):
delay = (time - datetime.now()).total_seconds()
Timer(delay, show_notification, args=[name]).start()
def show_notification(name):
print(f'notification: {name}!')
create_notification(datetime(2034, 1, 1), 'Hello future!')
One thing to watch out for is this approach creates a single thread for each event (which doesn't scale well for lots of events). This also suffers from the problem that if the user closes your program, your program crashes, computer shuts down, power loss, etc. you lose all of your notifications. If you need to handle this, then save them to a file. If you need the notifications to show up even when your program isn't running look into solutions provided by the OS like cronjobs.
I have a python graphical application that runs in a terminal and accepts keyboard input using the pynput module. The issue I am having is that pynput accepts input all the time, even when the terminal running the application is not in focus by my window manager. This makes it difficult to shift focus to another task without accidentally typing into the application.
Ideally, I would like to be able to either:
Have pynput reject input when the terminal is not in focus
OR
Have some way of determining when the application gains/loses focus (so I can simulate #1 myself)
I have searched the pynput documentation, but I cannot find a solution. Any advice on how to accomplish this is appreciated. I am using Linux.
import time
from threading import Thread
from pynput import keyboard
from win32gui import GetWindowText, GetForegroundWindow
class KeyLogger:
def __init__(self) -> None:
self.is_paused=False # pause keylog listener
self.is_closed=False # stop and close keylog
self.l=None # listener
self.listened_window=GetWindowText(GetForegroundWindow()) # set listened window name
self.focused_checker=Thread(target=self.check_focused) # check if out of focused window in a thread
self.focused_checker.start()
def start(self):
# initialize and start listener
self.l=keyboard.Listener( on_press=self.on_press, on_release=self.on_release)
self.l.start()
def close(self):
# stop and close keylog
self.is_closed=True
self.stop()
def stop(self):
# stop listener
self.l.stop()
self.l.join()
def check_focused(self):
while not self.is_closed:
if GetWindowText(GetForegroundWindow())!=self.listened_window: # compare now focused window with listened window
if not self.is_paused: # if different and not paused, stop listening
self.stop()
self.is_paused=True
elif self.is_paused: # if same but paused, restart listening
self.start()
self.is_paused=False
time.sleep(0.1)
Hi, I faced a similar problem on windows, and this is my solution. Different platform, but may give you some inspirations.
In short, you need a function to check out the focused window in a loop, and change the state of listener by a boolean variable as an internal flag.
Not a built-in function, but the solution is indicated in documentation, listed as below.
Once pynput.keyboard.Listener.stop has been called, the listener
cannot be restarted, since listeners are instances of
threading.Thread.
If your application requires toggling listening
events, you must either add an internal flag to ignore events when not
required, or create a new listener when resuming listening.
pynput documentation
I noticed that in my version of Tkinter, the after() call does not survive system clock rewinding.
If the after(x, func) was called, and the system clock was rewinded, func will be called only after the clock returned to its time before the rewind + x milliseconds.
I assume this is because Tkinter uses the system-clock instead of the "time.clock" (the amount of time that the program is running).
I tested it only on windows, and maybe its because I have an old version of Tkinter.
I want my App to work on computers that synchronize their clock from the network...
Does anyone have a simple solution?
Unfortunately, neither Tkinter nor Tcl interpreter have an straightforward solution to your problem. The after(ms, func) method is based on the Tcl command of the same name, which creates an internal timer based on the current system time plus the amount of milliseconds passed as parameter.
In case you are curious, you can check it out directly from the Tcl/Tk source code:
Tcl_GetTime(&wakeup);
wakeup.sec += (long)(ms / 1000);
wakeup.usec += ((long)(ms % 1000)) * 1000;
if (wakeup.usec > 1000000) {
wakeup.sec++;
wakeup.usec -= 1000000;
}
afterPtr->token = TclCreateAbsoluteTimerHandler(&wakeup,
AfterProc, afterPtr);
Given this limitation, I would go for a pure Python approach, like using a Timer:
import time
import threading
import tkinter as tk
root = tk.Tk()
def say_hi():
print(time.perf_counter(), "-", "Hi after 30sec!")
root.destroy()
print(time.perf_counter(), "-", "Waiting 30sec")
threading.Timer(30, say_hi).start()
root.mainloop()
It also has the advantage that runs on a separate thread, preventing not only blocking the GUI during the timer interval but also while executing the callback function.
Explanation
As you suspected, when you use .after() tkinter is scheduling an event to happen at current_time + delay_ms. This means that if system time changes between those events, it will subvert the scheduled event.
This is based on the fact that tkinter is simply calling the after command of tcl (the underlying system that tkinter is communicating to). tcl docs tell us:
after uses the system time to determine when it is time to perform a scheduled event. This means that with the exception of after 0 and after idle, it can be subverted by changes in the system time.
Now notice that after idle is exempt from the system clock coupling as well as after 0 but those probably aren't good replacements for you.
after 0
This schedules a script for immediate execution. It's useful for getting the tightest possible event. Warning: This places the scheduled event at the front of the queue, so a command that perpeually reschedules itself in this manner can lock up the queue.
So using after 0 would not be optimal as it is in the front of the event queue meaning nothing else would happen.
after idle
This is similarly exempt but it probably won't be best either. [docs]
The script will be run exactly once, the next time the event loop is entered and there are no events to process.
So the script would run when the system next becomes idle. You could use after x with after idle and that would wait until the events are clear and the system is idle, then wait x milliseconds before running that command but I suspect that isn't what you want to do either.
Tl;dr
So to your final question, what can you do if the clock could be expected to be reversed? Native to tkinter: not much. Unless you caught the reverse happening and reset your after() event or writing your own event scheduler based on time.process_time() (formerly time.clock()), I don't see a way to have .after() perform differently.