Python在单独线程中的按键回显不显示第一个按键 [英] Key echo in Python in separate thread doesn't display first key stroke
问题描述
我会尝试发布一个最小的工作示例,但是不幸的是,这个问题只需要很多内容,因此我已尽力将其剥离.
I would try to post a minimal working example, but unfortunately this problem just requires a lot of pieces so I have stripped it down best I can.
首先,我使用一个简单的脚本来模拟通过函数调用的按键.这是从此处进行调整的.
First of all, I'm using a simple script that simulates pressing keys through a function call. This is tweaked from here.
import ctypes
SendInput = ctypes.windll.user32.SendInput
PUL = ctypes.POINTER(ctypes.c_ulong)
class KeyBdInput(ctypes.Structure):
_fields_ = [("wVk", ctypes.c_ushort),
("wScan", ctypes.c_ushort),
("dwFlags", ctypes.c_ulong),
("time", ctypes.c_ulong),
("dwExtraInfo", PUL)]
class HardwareInput(ctypes.Structure):
_fields_ = [("uMsg", ctypes.c_ulong),
("wParamL", ctypes.c_short),
("wParamH", ctypes.c_ushort)]
class MouseInput(ctypes.Structure):
_fields_ = [("dx", ctypes.c_long),
("dy", ctypes.c_long),
("mouseData", ctypes.c_ulong),
("dwFlags", ctypes.c_ulong),
("time",ctypes.c_ulong),
("dwExtraInfo", PUL)]
class Input_I(ctypes.Union):
_fields_ = [("ki", KeyBdInput),
("mi", MouseInput),
("hi", HardwareInput)]
class Input(ctypes.Structure):
_fields_ = [("type", ctypes.c_ulong),
("ii", Input_I)]
def getKeyCode(unicodeKey):
k = unicodeKey
curKeyCode = 0
if k == "up": curKeyCode = 0x26
elif k == "down": curKeyCode = 0x28
elif k == "left": curKeyCode = 0x25
elif k == "right": curKeyCode = 0x27
elif k == "home": curKeyCode = 0x24
elif k == "end": curKeyCode = 0x23
elif k == "insert": curKeyCode = 0x2D
elif k == "pgup": curKeyCode = 0x21
elif k == "pgdn": curKeyCode = 0x22
elif k == "delete": curKeyCode = 0x2E
elif k == "\n": curKeyCode = 0x0D
if curKeyCode == 0:
return 0, int(unicodeKey.encode("hex"), 16), 0x0004
else:
return curKeyCode, 0, 0
def PressKey(unicodeKey):
key, unikey, uniflag = getKeyCode(unicodeKey)
extra = ctypes.c_ulong(0)
ii_ = Input_I()
ii_.ki = KeyBdInput( key, unikey, uniflag, 0, ctypes.pointer(extra) )
x = Input( ctypes.c_ulong(1), ii_ )
ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))
def ReleaseKey(unicodeKey):
key, unikey, uniflag = getKeyCode(unicodeKey)
extra = ctypes.c_ulong(0)
ii_ = Input_I()
ii_.ki = KeyBdInput( key, unikey, uniflag + 0x0002, 0, ctypes.pointer(extra) )
x = Input( ctypes.c_ulong(1), ii_ )
ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))
我将其存储在名为keyPress.py的文件中.
I stored this in a file named keyPress.py.
使用此代码,我想编写一个简单的程序,以检测用户在python shell中键入内容时所键入的内容.我的想法是,我将使用msvcrt.getch()来按下按键,然后在上面的脚本中使其看起来仍然处于按下状态(并在某种意义上回显"按键")
Using this, I wanted to make a simple program that could detect what the user was typing while they were typing it in the python shell. The idea was that I would use msvcrt.getch() to get the key pressed, then the script above to make it seem like it was still pressed (and "echo" the key press in a sense")
这是代码:
import keyPress
import msvcrt
import threading
def getKey():
k = msvcrt.getch()
# Escaped Key: 224 is on the keyboard, 0 is on the numpad
if int(k.encode("hex"), 16) == 224 or int(k.encode("hex"), 16) == 0:
k = msvcrt.getch()
if k == "H": k = "up"
elif k == "P": k = "down"
elif k == "K": k = "left"
elif k == "M": k = "right"
elif k == "G": k = "home"
elif k == "O": k = "end"
elif k == "R": k = "insert"
elif k == "I": k = "pgup"
elif k == "Q": k = "pgdn"
elif k == "S": k = "delete"
# Fix weird linebreak
if k == "\r":
k = "\n"
return k
def actualGetKeys():
while True:
k = getKey()
keyPress.PressKey(k)
keyPress.ReleaseKey(k)
def getKeys():
p = threading.Thread(target=actualGetKeys)
p.daemon = True
p.start()
我将其存储在名为keyGet.py的文件中.
I stored this in a file named keyGet.py.
这一切都很好,除了每当用户按下Enter键时,第一个键都不会显示在屏幕上.控制台仍然知道您已经键入了它,只是它没有显示在那里.像这样:
This is all working very well, except that whenever the user presses enter, the first key isn't displayed on the screen. The console still knows that you typed it, it just doesn't show up there. Something like this:
发生了什么事?我已经尝试了很多事情,但似乎无法改变这种行为.
What is happening? I've tried many many things and I can't seem to get this behavior to change.
我现在能够使它基本运行,因为它可以在脚本运行时异步捕获键输入,并在命令提示符下键入的每个命令的文本执行(这样,您可以存储这些数组).我遇到的唯一问题是这样的:
I am now able to get this essentially working, as in it can capture key input asynchronously while a script is running, and execute with the text of each command you type into a command prompt (so you could, say, store these to an array). The only problem I am running into is something like this:
我知道这基本上是由于机器人在键入输入后必须重新输入他们的输入,我只是想知道是否有一种方法可以防止在输入机器人时实际显示该输入,因此它的行为就像用户期望的那样.
I know this is due to essentially having to have a robot retype their input after they type it, I'm just wondering if there is a way to do this that prevents that input from actually being displayed when the robot types it, so it acts just like the user would expect.
推荐答案
这里是生成的代码,基本上由eryksun的注释编写,因为他不知何故.
Here is the resulting code, basically written by eryksun's comments because somehow he knows all.
这称为readcmd.py
This is called readcmd.py
# Some if this is from http://nullege.com/codes/show/src@e@i@einstein-HEAD@Python25Einstein@Lib@subprocess.py/380/win32api.GetStdHandle
# and
# http://nullege.com/codes/show/src@v@i@VistA-HEAD@Python@Pexpect@winpexpect.py/901/win32console.GetStdHandle.PeekConsoleInput
from ctypes import *
import time
import threading
from win32api import STD_INPUT_HANDLE, STD_OUTPUT_HANDLE
from win32console import GetStdHandle, KEY_EVENT, ENABLE_WINDOW_INPUT, ENABLE_MOUSE_INPUT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT
import keyPress
class CaptureLines():
def __init__(self):
self.stopLock = threading.Lock()
self.isCapturingInputLines = False
self.inputLinesHookCallback = CFUNCTYPE(c_int)(self.inputLinesHook)
self.pyosInputHookPointer = c_void_p.in_dll(pythonapi, "PyOS_InputHook")
self.originalPyOsInputHookPointerValue = self.pyosInputHookPointer.value
self.readHandle = GetStdHandle(STD_INPUT_HANDLE)
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)
def inputLinesHook(self):
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)
inputChars = self.readHandle.ReadConsole(10000000)
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT)
if inputChars == "\r\n":
keyPress.KeyPress("\n")
return 0
inputChars = inputChars[:-2]
inputChars += "\n"
for c in inputChars:
keyPress.KeyPress(c)
self.inputCallback(inputChars)
return 0
def startCapture(self, inputCallback):
self.stopLock.acquire()
try:
if self.isCapturingInputLines:
raise Exception("Already capturing keystrokes")
self.isCapturingInputLines = True
self.inputCallback = inputCallback
self.pyosInputHookPointer.value = cast(self.inputLinesHookCallback, c_void_p).value
except Exception as e:
self.stopLock.release()
raise
self.stopLock.release()
def stopCapture(self):
self.stopLock.acquire()
try:
if not self.isCapturingInputLines:
raise Exception("Keystrokes already aren't being captured")
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)
self.isCapturingInputLines = False
self.pyosInputHookPointer.value = self.originalPyOsInputHookPointerValue
except Exception as e:
self.stopLock.release()
raise
self.stopLock.release()
这是keyPress.py
And here is keyPress.py
# Modified from http://stackoverflow.com/a/13615802/2924421
import ctypes
from ctypes import wintypes
import time
user32 = ctypes.WinDLL('user32', use_last_error=True)
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2
KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP = 0x0002
KEYEVENTF_UNICODE = 0x0004
KEYEVENTF_SCANCODE = 0x0008
MAPVK_VK_TO_VSC = 0
# C struct definitions
wintypes.ULONG_PTR = wintypes.WPARAM
SendInput = ctypes.windll.user32.SendInput
PUL = ctypes.POINTER(ctypes.c_ulong)
class KEYBDINPUT(ctypes.Structure):
_fields_ = (("wVk", wintypes.WORD),
("wScan", wintypes.WORD),
("dwFlags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR))
class MOUSEINPUT(ctypes.Structure):
_fields_ = (("dx", wintypes.LONG),
("dy", wintypes.LONG),
("mouseData", wintypes.DWORD),
("dwFlags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR))
class HARDWAREINPUT(ctypes.Structure):
_fields_ = (("uMsg", wintypes.DWORD),
("wParamL", wintypes.WORD),
("wParamH", wintypes.WORD))
class INPUT(ctypes.Structure):
class _INPUT(ctypes.Union):
_fields_ = (("ki", KEYBDINPUT),
("mi", MOUSEINPUT),
("hi", HARDWAREINPUT))
_anonymous_ = ("_input",)
_fields_ = (("type", wintypes.DWORD),
("_input", _INPUT))
LPINPUT = ctypes.POINTER(INPUT)
def _check_count(result, func, args):
if result == 0:
raise ctypes.WinError(ctypes.get_last_error())
return args
user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (wintypes.UINT, # nInputs
LPINPUT, # pInputs
ctypes.c_int) # cbSize
def KeyDown(unicodeKey):
key, unikey, uniflag = GetKeyCode(unicodeKey)
x = INPUT( type=INPUT_KEYBOARD, ki= KEYBDINPUT( key, unikey, uniflag, 0))
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))
def KeyUp(unicodeKey):
key, unikey, uniflag = GetKeyCode(unicodeKey)
extra = ctypes.c_ulong(0)
x = INPUT( type=INPUT_KEYBOARD, ki= KEYBDINPUT( key, unikey, uniflag | KEYEVENTF_KEYUP, 0))
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))
def KeyPress(unicodeKey):
time.sleep(0.0001)
KeyDown(unicodeKey)
time.sleep(0.0001)
KeyUp(unicodeKey)
time.sleep(0.0001)
def GetKeyCode(unicodeKey):
k = unicodeKey
curKeyCode = 0
if k == "up": curKeyCode = 0x26
elif k == "down": curKeyCode = 0x28
elif k == "left": curKeyCode = 0x25
elif k == "right": curKeyCode = 0x27
elif k == "home": curKeyCode = 0x24
elif k == "end": curKeyCode = 0x23
elif k == "insert": curKeyCode = 0x2D
elif k == "pgup": curKeyCode = 0x21
elif k == "pgdn": curKeyCode = 0x22
elif k == "delete": curKeyCode = 0x2E
elif k == "\n": curKeyCode = 0x0D
if curKeyCode == 0:
return 0, int(unicodeKey.encode("hex"), 16), KEYEVENTF_UNICODE
else:
return curKeyCode, 0, 0
这篇关于Python在单独线程中的按键回显不显示第一个按键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!