5

私は最小限の作業例を投稿しようとしますが、残念ながらこの問題は多くの断片を必要とします。別のスレッドでPythonのキーエコーが最初のキーストロークを表示しない

まず、関数呼び出しでキーを押すことをシミュレートする簡単なスクリプトを使用しています。これは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という名前のファイルに保存しました。

これを使用して、私はPythonシェルで入力中にユーザーが入力したものを検出できる簡単なプログラムを作りたかったのです。私がmsvcrt.getch()を使用してキーを押した後、それがまだ押されているように見えるようにするには、上記のスクリプトを使用します(そして、意味においてキープレスをエコーし​​ます)。

ここにありますコード:

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という名前のファイルにこれを保存

をこれは、すべてのユーザーがEnterキーを押すたびことを除いて、最初のキーが画面に表示されていない、非常にうまく機能しています。コンソールはあなたが入力したことをまだ知っていて、そこには表示されません:

Not displaying all of input

何が起こっているか私は多くのことを試しましたが、私はこの振る舞いを変えるように見えません。

スクリプトが実行されている間にキー入力を非同期的に取得し、コマンドプロンプトに入力した各コマンドのテキストを使用して実行できるようになりました(つまり、storeこれらは配列になります)。私がある場合、私はちょうど思ったんだけど、これは基本的に、彼らはそれを入力した後、ロボットが自分の入力を再入力持つようになるために起因している知っている

Issue of reprint

:私はに実行しています唯一の問題は、このようなものですこれを行う方法は、ロボットが入力するときに入力が実際に表示されるのを防ぐので、ユーザーが期待するように機能します。

+0

ですfgetsなどの関数。したがって、 'getch'がコンソールをrawモードに設定すると、Pythonの' fgets'読み込みを妨げます。 – eryksun

+0

ロックを使って互いに干渉しないようにしても、 'getch'はその行の最初の文字に対してのみ呼び出されます。コマンドライン編集、F7履歴メニューなどで 'fgets'で読み取った読み込みは、残りの行を消費します。私は 'PyOS_InputHook'(ワンタイム設定)と' PyOS_ReadlineFunctionPointer'のフックを組み合わせて、ctypesを使ってロックテストを行いました。あなたが望むなら私はそれを投稿することができますが、最終結果は何にも役立ちません。コンソールから読み込むこれらの2つの方法は、うまく一緒に再生されません。 – eryksun

+0

Hmm。実際にこれを修正する方法はありませんか? – Phylliida

答えて

2

基本的にはeryksunのコメントによって書かれたコードです。何とか彼はすべてを知っているからです。

これは、このような `getch`はstdioのと同じロックを使用していないとして、CRTのコンソールI/O関数はreadcmd.py

# Some if this is from http://nullege.com/codes/show/[email protected]@[email protected]@[email protected]@subprocess.py/380/win32api.GetStdHandle 
# and 
# http://nullege.com/codes/show/[email protected]@[email protected]@[email protected]@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

# 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 
+1

あなたは 'WriteConsoleInput'の代わりに' SendInput'コードを使うことに決めました。参考までに、lucasgには、数週間前に編集したキープレスコードの[別のバージョン](http://stackoverflow.com/a/13615802/205580)があります。私が行った変更の簡単な説明については、編集コメントを参照してください。 – eryksun

+1

ああクール、ありがとう。はい、何らかの理由で、WriteConsoleInputがちょっと必要だった改行を送信できなかったように見えました。それは周りに道がある可能性がありますが、これは既に働いていたので、私はそれがうまくいくと考えました。 – Phylliida

関連する問題