Python: How to start/stop while True loop on Keyboard event - python

I am trying to write a little script that emulates mouse-clicking. It should start/stop when a combination of keys is pressed on the keyboard, so I figured every time this combination is pressed I would spawn or terminate a child process that just contains a while True loop and does some clicking. Now I can get the loop to start, but not to terminate. I tried creating a new process just for the HookManager, but i got the same results. Any help with this would be very appreciated
import time
import win32api
import win32con
import pythoncom
import pyHook
import multiprocessing
i=0
def click():
while True:
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
time.sleep(0.005)
def OnKeyboardEvent(event):
global i
if (event.Ascii == 4) and (i == 0):
i = 1
c = multiprocessing.Process(target=click())
c.start()
print("started")
elif (event.Ascii == 4) and (i == 1):
i = 0
c.terminate()
print("terminated")
return True
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.HookKeyboard()
pythoncom.PumpMessages()

Obviously you dont want to use while True: statement, but something like this:
def worker():
while running:
#do the work
...
def terminateWorker():
running = false
...
running = true
startWorker()
terminateWorker()

Or you can use break in if statement checking loop stop flag.
def click():
while True:
if(stopNow):break
...
stopNow=false

Related

How to thread two functions where one output effect the others actions

Here's my code:
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
hshtag = int(0)
done = False
fire = False
def StopStart(fire):
while not done:
global fire
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 ==0:
fire = False
else:
fire = True
return fire
def NormalFire():
while not done:
global fire
if fire == True:
#do x
else:
pass
t1 = Thread(target = StopStart)
t2 = Thread(target = NormalFire(fire))
t1.start()
t2.start()
The problem is function StopStart (should) effect what function Normalfire does but as the function only accepts a value for fire when it starts running (so it doesn't work). What I want is change what function normalfire does with function stopstart. and if you're wondering why I am using threading it because '#do x' actually takes a while to work so as one continuous script if I clicked hash at the wrong time it wouldn't stop. Maybe I could do this with classes instead but im not good with classes so if someone could either help with that or fix the above code that would be great thanks.
New attempt at explaining what's wrong with the top code - Ok, so both functions should be running simultaneously (which they are) - so no problems there. but as the function StopStart changes the boolean fire to true/false I want that to cause my NormalFire function to change what it is doing - nothing when I haven't clicked hash yet and something if I've clicked hash once but if I then click hash while its running it will finish whats its running then do nothing waiting for hash to be clicked again.
Sorry, my question wasn't clear take this code as a simplification of my core question.
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def x():
while not done:
fire = True
return fire
def y(fire):
while not done:
if fire:
print('ok')
else:
pass
t1 = Thread(target = x)
t2 = Thread(target = y(fire))
t1.start()
t2.start()
Currently, the above code outputs nothing even though I've set 'fire = true' in function x and returned it how would I edit this code so that when boolean fire changes to true the function y starts printing ok?
Editing like Nair suggested also returns nothing and after 15 second the program stops running edited code:
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def StopStart():
while not done:
fire = True
return fire
def NormalFire():
while not done:
if fire:
print('ok')
else:
pass
t1 = Thread(target = StopStart)
t2 = Thread(target = NormalFire)
t1.start()
t2.start()
I'm unable to comment so I apologize in advanced. I'm having trouble understanding your question above, but I reworked your code - fix/add whatever you need and get back to me!
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
hshtag = int(0)
done = False
fire = False
def StopStart():
while not done:
# global fire - You're setting StopStart up for a param that needs passed, that also is named another variable
# So it will just over write it (Also, no arg is passed for StopStart(fire))
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 == 0 : fire = False
else : fire = true
return fire
def NormalFire():
while not done:
#global fire - Don't need to global it, you would've had to global done if that was the case
if fire: # don't need == true, just need if fire (if true)
print("x")
t1 = Thread(target=StopStart)
t2 = Thread(target=NormalFire)
t1.start()
t2.start()
Not sure that this is exactly what you're asking for. I would maybe listen for keyboard events outside of either thread. Instead, just bind keyboard events to a callback which set a threading.Event object. Sorry for the weird and slightly morbid example:
from pynput import keyboard
from threading import Thread, Event
plate_dropped = Event()
def on_press(key):
if key is keyboard.Key.enter:
plate_dropped.set()
listener = keyboard.Listener(on_press=on_press)
def poll_plate_status():
from time import sleep
from random import choice
messages = [
"It sure is tempting, eh?",
"Are you gonna do it?"
]
print("It'd be a shame if someone would drop this plate and scare grandpa!")
while not plate_dropped.is_set():
print(choice(messages))
sleep(0.5)
print("The plate has been dropped!")
def poll_grandpa_status():
from time import sleep
from random import choice
messages = [
"*zzzZZZzzz*",
"*Snoooreee*"
]
print("Grandpa is sound asleep.")
while not plate_dropped.is_set():
print(choice(messages))
sleep(0.5)
print("HUH!?")
plate_thread = Thread(target=poll_plate_status, daemon=True)
grandpa_thread = Thread(target=poll_grandpa_status, daemon=True)
plate_thread.start()
grandpa_thread.start()
listener.start()
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def StopStart(self, interval=1):
hshtag = 0
self.interval = interval
while not done:
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 ==0:
fire = False
else:
fire = True
def NormalFire():
while not done:
print('NormalFire Runs')
time.sleep(1)
if fire:
print('*fires*')
else:
print('*does nothing*')
#t1 = Thread(target = StopStart, daemon=True)
t2 = Thread(target = NormalFire, daemon=True)
#t1.start()
t2.start()
while not done:
#time.sleep()
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
time.sleep(0.1)
if hshtag % 2 ==0:
fire = False
print(fire)
else:
fire = True
print(fire)
I realised my problem was my ides of threading (im new to it) this achieves what I wanted thanks for all the help.

Disable Mouse Function Python Code problem

Using the following code to disable my mouse and keyboard for 10 seconds. I'm still able to move my mouse and keyboard while the code is running. also I want to call this function from another script in python but I'm not able to. Any suggestions?
import pythoncom, pyHook, time
start = time.time()
time.perf_counter
elapsed = 0
def windoow(event):
global elapsed
if elapsed < 10:
elapsed = time.time() - start
time.sleep(1)
return False
return True
hm = pyHook.HookManager()
hm.MouseAll = windoow
hm.KeyAll = windoow
hm.HookMouse()
hm.HookKeyboard()
pythoncom.PumpMessages()
For me, just two lines of programming solved the problem:
from ctypes import *
ok = windll.user32.BlockInput(True) #enable block
#or
ok = windll.user32.BlockInput(False) #disable block

Python - breaking while loop with function (Raspberry Pi)

I have a project that involves a raspberry pi, the dothat 16x2 lcd display and some python code. I am essentially trying to make a dynamic while loop that displays information on the lcd. I am also trying to add a function that cancels the while loop by pressing one of the touch buttons (reference: https://github.com/pimoroni/dot3k/blob/master/python/REFERENCE.md)
This is what I got so far:
import dothat.lcd as l
import dothat.backlight as b
import dothat.touch as t
from time import sleep
import signal
import os
def main():
i=0
k=0
while True:
l.clear() # Clear LCD screen
b.hue(1.5) # Set background color
l.set_cursor_position(0, 1) # Set cursor position on LCD
l.write("%s" % k) # Write variable "k" to LCD
#t.on(t.CANCEL) # When CANCEL button is pressed then go to function
def cancel(ch, evt):
i=1 # Set variable "i" as 1
return
if i == 1:
break
k=k+1
sleep(1)
l.clear() # Clear LCD screen
b.off() # Turn the LCD Backlight off
cmd='pkill python' #
os(cmd) # Kill all python processes
signal.pause()
main()
The while loop is running but it won't break when the button is pressed. Ideas?
I fixed it, though I'm getting errors about the 'module' object not being callable regarding os(cmd).
The code:
def main():
global i
i=0
k=0
while True:
l.clear()
b.hue(1.5)
l.set_cursor_position(0, 1)
l.write("%s" % k)
#t.on(t.CANCEL)
def cancel(ch, evt):
global i
i=1
return
if i == 1:
break
k=k+1
sleep(1)
l.clear()
b.off()
cmd='pkill python'
os(cmd)
signal.pause()
main()
I don't have a dothat LCD display, so I can't test your code. But I think #Pigface333 was right, the i inside cancel is a local variable, thus the i in your if statement is not set to 1 after pressing Cancel. The following code demonstrates that:
from time import sleep
def main():
i = 0
k = 0
while True:
def cancel():
print "inside cancel"
i = 1
return
cancel()
if i == 1:
break
k = k+1
sleep(1)
exit(0)
main()
This will print inside cancel every 1 second, but won't exit, showing that the i inside cancel is a local variable. To fix it, you can create a class that stores the cancellation status:
from time import sleep
class Cancel(object):
def __init__(self):
self.is_cancelled = False
def cancel(self):
self.is_cancelled = True
def main():
canceller = Cancel()
while True:
canceller.cancel()
if canceller.is_cancelled:
break
sleep(1)
exit(0)
main()
The same method can be applied to your code:
import dothat.lcd as l
import dothat.touch as t
import dothat.backlight as b
from time import sleep
import signal
class Cancel(object):
def __init__(self,):
self.is_cancelled = False
#t.on(t.CANCEL)
def cancel(self, ch, evt):
self.is_cancelled = True
return
def main():
k = 0
cancel = Cancel()
while True:
l.clear() # Clear LCD screen
b.hue(1.5) # Set background color
l.set_cursor_position(0, 1) # Set cursor position on LCD
l.write("%s" % k) # Write variable "k" to LCD
if cancel.is_cancelled:
break
k = k+1
sleep(1)
l.clear() # Clear LCD screen
b.off() # Turn the LCD Backlight off
signal.pause()
exit(0)
main()
To help understanding why the original code didn't work and why using a class is a good idea, I suggest reading about Python's variable's scope and Object-Oriented Prograaming in Python.

Detecting ESCAPE key in python [duplicate]

I am trying to make a simple IRC client in Python (as kind of a project while I learn the language).
I have a loop that I use to receive and parse what the IRC server sends me, but if I use raw_input to input stuff, it stops the loop dead in its tracks until I input something (obviously).
How can I input something without the loop stopping?
(I don't think I need to post the code, I just want to input something without the while 1: loop stopping.)
I'm on Windows.
For Windows, console only, use the msvcrt module:
import msvcrt
num = 0
done = False
while not done:
print(num)
num += 1
if msvcrt.kbhit():
print "you pressed",msvcrt.getch(),"so now i will quit"
done = True
For Linux, this article describes the following solution, it requires the termios module:
import sys
import select
import tty
import termios
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setcbreak(sys.stdin.fileno())
i = 0
while 1:
print(i)
i += 1
if isData():
c = sys.stdin.read(1)
if c == '\x1b': # x1b is ESC
break
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
For cross platform, or in case you want a GUI as well, you can use Pygame:
import pygame
from pygame.locals import *
def display(str):
text = font.render(str, True, (255, 255, 255), (159, 182, 205))
textRect = text.get_rect()
textRect.centerx = screen.get_rect().centerx
textRect.centery = screen.get_rect().centery
screen.blit(text, textRect)
pygame.display.update()
pygame.init()
screen = pygame.display.set_mode( (640,480) )
pygame.display.set_caption('Python numbers')
screen.fill((159, 182, 205))
font = pygame.font.Font(None, 17)
num = 0
done = False
while not done:
display( str(num) )
num += 1
pygame.event.pump()
keys = pygame.key.get_pressed()
if keys[K_ESCAPE]:
done = True
This is the most awesome solution1 I've ever seen. Pasted here in case link goes down:
#!/usr/bin/env python
'''
A Python class implementing KBHIT, the standard keyboard-interrupt poller.
Works transparently on Windows and Posix (Linux, Mac OS X). Doesn't work
with IDLE.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
'''
import os
# Windows
if os.name == 'nt':
import msvcrt
# Posix (Linux, OS X)
else:
import sys
import termios
import atexit
from select import select
class KBHit:
def __init__(self):
'''Creates a KBHit object that you can call to do various keyboard things.
'''
if os.name == 'nt':
pass
else:
# Save the terminal settings
self.fd = sys.stdin.fileno()
self.new_term = termios.tcgetattr(self.fd)
self.old_term = termios.tcgetattr(self.fd)
# New terminal setting unbuffered
self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)
# Support normal-terminal reset at exit
atexit.register(self.set_normal_term)
def set_normal_term(self):
''' Resets to normal terminal. On Windows this is a no-op.
'''
if os.name == 'nt':
pass
else:
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)
def getch(self):
''' Returns a keyboard character after kbhit() has been called.
Should not be called in the same program as getarrow().
'''
s = ''
if os.name == 'nt':
return msvcrt.getch().decode('utf-8')
else:
return sys.stdin.read(1)
def getarrow(self):
''' Returns an arrow-key code after kbhit() has been called. Codes are
0 : up
1 : right
2 : down
3 : left
Should not be called in the same program as getch().
'''
if os.name == 'nt':
msvcrt.getch() # skip 0xE0
c = msvcrt.getch()
vals = [72, 77, 80, 75]
else:
c = sys.stdin.read(3)[2]
vals = [65, 67, 66, 68]
return vals.index(ord(c.decode('utf-8')))
def kbhit(self):
''' Returns True if keyboard character was hit, False otherwise.
'''
if os.name == 'nt':
return msvcrt.kbhit()
else:
dr,dw,de = select([sys.stdin], [], [], 0)
return dr != []
# Test
if __name__ == "__main__":
kb = KBHit()
print('Hit any key, or ESC to exit')
while True:
if kb.kbhit():
c = kb.getch()
if ord(c) == 27: # ESC
break
print(c)
kb.set_normal_term()
1
Made by Simon D. Levy, part of a compilation of software he has written and released under the Gnu Lesser General Public License.
Here a solution that runs under linux and windows using a seperate thread:
import sys
import threading
import time
import Queue
def add_input(input_queue):
while True:
input_queue.put(sys.stdin.read(1))
def foobar():
input_queue = Queue.Queue()
input_thread = threading.Thread(target=add_input, args=(input_queue,))
input_thread.daemon = True
input_thread.start()
last_update = time.time()
while True:
if time.time()-last_update>0.5:
sys.stdout.write(".")
last_update = time.time()
if not input_queue.empty():
print "\ninput:", input_queue.get()
foobar()
My favorite to get non-blocking input is using the python input() in a thread:
import threading
class KeyboardThread(threading.Thread):
def __init__(self, input_cbk = None, name='keyboard-input-thread'):
self.input_cbk = input_cbk
super(KeyboardThread, self).__init__(name=name)
self.start()
def run(self):
while True:
self.input_cbk(input()) #waits to get input + Return
showcounter = 0 #something to demonstrate the change
def my_callback(inp):
#evaluate the keyboard input
print('You Entered:', inp, ' Counter is at:', showcounter)
#start the Keyboard thread
kthread = KeyboardThread(my_callback)
while True:
#the normal program executes without blocking. here just counting up
showcounter += 1
OS independent, only internal libraries, supports multi-character input
On Linux, here's a refactoring of mizipzor's code that makes this a little easier, in case you have to use this code in multiple places.
import sys
import select
import tty
import termios
class NonBlockingConsole(object):
def __enter__(self):
self.old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
return self
def __exit__(self, type, value, traceback):
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)
def get_data(self):
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
return sys.stdin.read(1)
return False
Here's how to use this: This code will print a counter that keeps growing until you press ESC.
with NonBlockingConsole() as nbc:
i = 0
while 1:
print i
i += 1
if nbc.get_data() == '\x1b': # x1b is ESC
break
I think curses library can help.
import curses
import datetime
stdscr = curses.initscr()
curses.noecho()
stdscr.nodelay(1) # set getch() non-blocking
stdscr.addstr(0,0,"Press \"p\" to show count, \"q\" to exit...")
line = 1
try:
while 1:
c = stdscr.getch()
if c == ord('p'):
stdscr.addstr(line,0,"Some text here")
line += 1
elif c == ord('q'): break
"""
Do more things
"""
finally:
curses.endwin()
.... backing to the initial question ...
i am learning python too, it cost me many documentation and examples readings and head crackings... but i think i reached an easy, simple, short and compatible solution... using just input, lists and threads
'''
what i thought:
- input() in another thread
- that were filling a global strings list
- strings are being popped in the main thread
'''
import threading
consoleBuffer = []
def consoleInput(myBuffer):
while True:
myBuffer.append(input())
threading.Thread(target=consoleInput, args=(consoleBuffer,), daemon=True).start() # start the thread
import time # just to demonstrate non blocking parallel processing
while True:
time.sleep(2) # avoid 100% cpu
print(time.time()) # just to demonstrate non blocking parallel processing
while consoleBuffer:
print(repr(consoleBuffer.pop(0)))
until this is the simplest and compatible way i found, be aware by default stdin stdout and stderr share the same terminal so "local echo" of your input may look inconsistent if something is printed on console while you are typing, however after pressing enter the typed string is received well... if you don't want/like this behavior find a way to separate input/output areas like redirections, or try another solution like curses, tkinter, pygame, etc.
BONUS: the ctrl-c keystroke can be easily handled with
try:
# do whatever
except KeyboardInterrupt:
print('cancelled by user') or exit() # overload
I'd do what Mickey Chan said, but I'd use unicurses instead of normal curses.
Unicurses is universal (works on all or at least almost all operating systems)
If you just want a single "escape" from a loop, you can intercept the Ctrl-C signal.
This is cross-platform and very simple!
import signal
import sys
def signal_handler(sig, frame):
print('You pressed Ctrl+C!')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
while True:
# do your work here
With python3.3 and above you can use the asyncio module as mentioned in this answer.
You will have to re factor your code though to work with asyncio.
Prompt for user input using python asyncio.create_server instance
Since I found one of the answers above helpful, here's an example of a similar approach. This code creates a metronome effect while taking input.
The difference is this code uses a closure instead of a class, which feels a little more straight-forward to me. This example also incorporates a flag to kill the thread via my_thread.stop = True, but without using a global variable. I do this by (ab)using the fact that python functions are objects and thus can be monkey-patched, even from inside themselves.
Note: Stopping threads should be done with caution. If your thread has data that needs some kind of clean up process or if the thread spawned its own threads, this approach will unceremoniously kill those processes.
# Begin metronome sound while accepting input.
# After pressing enter, turn off the metronome sound.
# Press enter again to restart the process.
import threading
import time
import winsound # Only on Windows
beat_length = 1 # Metronome speed
def beat_thread():
beat_thread.stop = False # Monkey-patched flag
frequency, duration = 2500, 10
def run(): # Closure
while not beat_thread.stop: # Run until flag is True
winsound.Beep(frequency, duration)
time.sleep(beat_length - duration/1000)
threading.Thread(target=run).start()
while True:
beat_thread()
input("Input with metronome. Enter to finish.\n")
beat_thread.stop = True # Flip monkey-patched flag
input("Metronome paused. Enter to continue.\n\n")
The following is an class wrapper around one of the above solutions:
#!/usr/bin/env python3
import threading
import queue
class NonBlockingInput:
def __init__(self, exit_condition):
self.exit_condition = exit_condition
self.input_queue = queue.Queue()
self.input_thread = threading.Thread(target=self.read_kbd_input, args=(), daemon=True)
self.input_thread.start()
def read_kbd_input(self):
done_queueing_input = False
while not done_queueing_input:
console_input = input()
self.input_queue.put(console_input)
if console_input.strip() == self.exit_condition:
done_queueing_input = True
def input_queued(self):
return_value = False
if self.input_queue.qsize() > 0:
return_value = True
return return_value
def input_get(self):
return_value = ""
if self.input_queue.qsize() > 0:
return_value = self.input_queue.get()
return return_value
if __name__ == '__main__':
NON_BLOCK_INPUT = NonBlockingInput(exit_condition='quit')
DONE_PROCESSING = False
INPUT_STR = ""
while not DONE_PROCESSING:
if NON_BLOCK_INPUT.input_queued():
INPUT_STR = NON_BLOCK_INPUT.input_get()
if INPUT_STR.strip() == "quit":
DONE_PROCESSING = True
else:
print("{}".format(INPUT_STR))
I was writing a program using Linux that has a bigger mainloop that requires regular updates but also needs to read characters in a non-blocking way. But resetting the display, also loses the input buffer.
This is the solution that I came up with. Every time after the screen is updated it sets the terminal to non-blocking, waits for the mainloop to pass and then interprets stdin.
After that the terminal gets reset to the original settings.
#!/usr/bin/python3
import sys, select, os, tty, termios, time
i = 0
l = True
oldtty = termios.tcgetattr(sys.stdin)
stdin_no = sys.stdin.fileno()
while l:
os.system('clear')
print("I'm doing stuff. Press a 'q' to stop me!")
print(i)
tty.setcbreak(stdin_no)
time.sleep(0.5)
if sys.stdin in select.select([sys.stdin], [], [], 0.0)[0]:
line = sys.stdin.read(1)
print (line, len(line))
if "q" in line:
l = False
else:
pass
termios.tcsetattr(stdin_no, termios.TCSADRAIN, oldtty)
i += 1
The Solution by marco is the right idea, but I decided to simplify it to the minimal possible code without any classes. Also it actually shows you how to get the user input with the queue library instead of just printing it:
import time, threading, queue
def collect(que):
msg = input()
que.put(msg)
que = queue.Queue()
thread = threading.Thread(target=collect, args=[que])
thread.start()
while thread.is_alive():
time.sleep(1)
print("The main thread continues while we wait for you...")
msg = que.get()
print('You typed:', msg)
In this example, the main thread continues indefinitely (processing data or whatever), while periodically checking to see if the user has input any data in the spawned thread. When that happens it returns the user input.
I've successfully used this idea in my own script to create a debugger, where I can type "print variable name" at any point during the main loop and it gives me the values in real time without stopping.
My example below does allow for non-blocking reads from stdin under both Windows (only tested under Windows 10) and Linux without requiring external dependencies or using threading. It works for copypasted text, it disables ECHO, so it could be used for e.g. some sort of custom UI and uses a loop, so it would be easy to process anything that was input into it.
With the above in mind, the example is meant for an interactive TTY, not piped input.
#!/usr/bin/env python3
import sys
if(sys.platform == "win32"):
import msvcrt
import ctypes
from ctypes import wintypes
kernel32 = ctypes.windll.kernel32
oldStdinMode = ctypes.wintypes.DWORD()
# Windows standard handle -10 refers to stdin
kernel32.GetConsoleMode(kernel32.GetStdHandle(-10), ctypes.byref(oldStdinMode))
# Disable ECHO and line-mode
# https://learn.microsoft.com/en-us/windows/console/setconsolemode
kernel32.SetConsoleMode(kernel32.GetStdHandle(-10), 0)
else:
# POSIX uses termios
import select, termios, tty
oldStdinMode = termios.tcgetattr(sys.stdin)
_ = termios.tcgetattr(sys.stdin)
# Disable ECHO and line-mode
_[3] = _[3] & ~(termios.ECHO | termios.ICANON)
# Don't block on stdin.read()
_[6][termios.VMIN] = 0
_[6][termios.VTIME] = 0
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, _)
def readStdin():
if(sys.platform == "win32"):
return msvcrt.getwch() if(msvcrt.kbhit()) else ""
else:
return sys.stdin.read(1)
def flushStdin():
if(sys.platform == "win32"):
kernel32.FlushConsoleInputBuffer(kernel32.GetStdHandle(-10))
else:
termios.tcflush(sys.stdin, termios.TCIFLUSH)
try:
userInput = ""
print("Type something: ", end = "", flush = True)
flushStdin()
while 1:
peek = readStdin()
if(len(peek) > 0):
# Stop input on NUL, Ctrl+C, ESC, carriage return, newline, backspace, EOF, EOT
if(peek not in ["\0", "\3", "\x1b", "\r", "\n", "\b", "\x1a", "\4"]):
userInput += peek
# This is just to show the user what they typed.
# Can be skipped, if one doesn't need this.
sys.stdout.write(peek)
sys.stdout.flush()
else:
break
flushStdin()
print(f"\nuserInput length: {len(userInput)}, contents: \"{userInput}\"")
finally:
if(sys.platform == "win32"):
kernel32.SetConsoleMode(kernel32.GetStdHandle(-10), oldStdinMode)
else:
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, oldStdinMode)

pyHook + pythoncom stop working after too much keys pressed [Python]

this is my script:
import pyHook
import pythoncom
hookManager = pyHook.HookManager()
def onKeyboardEvent(event):
if event.KeyID == 113: # F2
#do something#
return True
hookManager.KeyDown = onKeyboardEvent
hookManager.HookKeyboard()
pythoncom.PumpMessages()
after the key specified on the keyboard event, or the F2 key as my script, is pressed for several times, the script stop working...
Anyone knows why? or how to solve it?
I have to restart the script every time this happens, and I have to press the key a lot in my script...
Maybe you can call the function as a Thread to execute asynchronously, add them to your own queue or set a condition to not execute if it's already running, that will stop filling the messagepump which is that is failing.
Option 1. This will add the function execution to the threads queue:
import pythoncom, pyHook, threading
lock = threading.Lock()
def myFunc(i):
lock.acquire() #execute next function until previous has finished
#some code
lock.release()
def OnKeyboardEvent(event):
keyPressed = chr(event.Ascii)
if keyPressed == 'z':
t = threading.Thread(target=myFunc, args=(1,)) #added to queue
t.start()
return True
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.HookKeyboard()
pythoncom.PumpMessages()
Option 2. or this will ignore other processing calls if it's busy:
def myFunc(i):
myFunc.isRunning = True
#some code
myFunc.isRunning = False
myFunc.isRunning = False
def OnKeyboardEvent(event):
keyPressed = chr(event.Ascii)
if keyPressed == 'z':
if not myFunc.isRunning: #if function is being executed ignore this call
t = threading.Thread(target=myFunc,args=(1,))
t.start()
return True
of course you should be careful when you add code by capturing exceptions or the thread will stay blocked.

Categories

Resources