739 lines
27 KiB
Python
739 lines
27 KiB
Python
|
import sys
|
|||
|
import time
|
|||
|
import os
|
|||
|
import resources_rc
|
|||
|
|
|||
|
from PyQt5.QtGui import QIcon
|
|||
|
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
|
|||
|
QPushButton, QLabel, QLineEdit, QCheckBox, QComboBox,
|
|||
|
QGridLayout, QMessageBox, QDialog)
|
|||
|
from PyQt5.QtCore import QTimer, Qt, QSettings
|
|||
|
import win32gui
|
|||
|
import win32con
|
|||
|
import win32api
|
|||
|
import win32process
|
|||
|
import ctypes
|
|||
|
from ctypes import wintypes
|
|||
|
|
|||
|
|
|||
|
# 检查并请求管理员权限
|
|||
|
def is_admin():
|
|||
|
try:
|
|||
|
return ctypes.windll.shell32.IsUserAnAdmin()
|
|||
|
except:
|
|||
|
return False
|
|||
|
|
|||
|
|
|||
|
if not is_admin():
|
|||
|
# 如果不是管理员权限,则重新以管理员身份启动程序
|
|||
|
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
|
|||
|
sys.exit(0)
|
|||
|
|
|||
|
# Constants for virtual keys
|
|||
|
VK_SPACE = 0x20
|
|||
|
VK_RETURN = 0x0D
|
|||
|
VK_TAB = 0x09
|
|||
|
VK_ESCAPE = 0x1B
|
|||
|
VK_BACK = 0x08
|
|||
|
VK_INSERT = 0x2D
|
|||
|
VK_DELETE = 0x2E
|
|||
|
VK_HOME = 0x24
|
|||
|
VK_END = 0x23
|
|||
|
VK_PRIOR = 0x21 # Page Up
|
|||
|
VK_NEXT = 0x22 # Page Down
|
|||
|
VK_LEFT = 0x25
|
|||
|
VK_RIGHT = 0x27
|
|||
|
VK_UP = 0x26
|
|||
|
VK_DOWN = 0x28
|
|||
|
VK_F1 = 0x70
|
|||
|
VK_F2 = 0x71
|
|||
|
VK_F3 = 0x72
|
|||
|
VK_F4 = 0x73
|
|||
|
VK_F5 = 0x74
|
|||
|
VK_F6 = 0x75
|
|||
|
VK_F7 = 0x76
|
|||
|
VK_F8 = 0x77
|
|||
|
VK_F9 = 0x78
|
|||
|
VK_F10 = 0x79
|
|||
|
VK_F11 = 0x7A
|
|||
|
VK_F12 = 0x7B
|
|||
|
|
|||
|
VK_SCROLL = 0x91
|
|||
|
|
|||
|
WM_KEYDOWN = 0x0100
|
|||
|
WM_KEYUP = 0x0101
|
|||
|
WM_CHAR = 0x0102
|
|||
|
|
|||
|
# Constants for SendInput
|
|||
|
INPUT_KEYBOARD = 1
|
|||
|
KEYEVENTF_KEYUP = 0x0002
|
|||
|
KEYEVENTF_UNICODE = 0x0004
|
|||
|
|
|||
|
|
|||
|
# 鼠标事件常量
|
|||
|
MOUSEEVENTF_MOVE = 0x0001
|
|||
|
MOUSEEVENTF_LEFTDOWN = 0x0002
|
|||
|
MOUSEEVENTF_LEFTUP = 0x0004
|
|||
|
MOUSEEVENTF_RIGHTDOWN = 0x0008
|
|||
|
MOUSEEVENTF_RIGHTUP = 0x0010
|
|||
|
MOUSEEVENTF_MIDDLEDOWN = 0x0020
|
|||
|
MOUSEEVENTF_MIDDLEUP = 0x0040
|
|||
|
MOUSEEVENTF_ABSOLUTE = 0x8000
|
|||
|
# SendInput常量
|
|||
|
INPUT_MOUSE = 0
|
|||
|
INPUT_KEYBOARD = 1
|
|||
|
|
|||
|
# Define required structures for SendInput
|
|||
|
class MOUSEINPUT(ctypes.Structure):
|
|||
|
_fields_ = [
|
|||
|
("dx", wintypes.LONG),
|
|||
|
("dy", wintypes.LONG),
|
|||
|
("mouseData", wintypes.DWORD),
|
|||
|
("dwFlags", wintypes.DWORD),
|
|||
|
("time", wintypes.DWORD),
|
|||
|
("dwExtraInfo", ctypes.POINTER(wintypes.ULONG)),
|
|||
|
]
|
|||
|
|
|||
|
|
|||
|
class KEYBDINPUT(ctypes.Structure):
|
|||
|
_fields_ = [
|
|||
|
("wVk", wintypes.WORD),
|
|||
|
("wScan", wintypes.WORD),
|
|||
|
("dwFlags", wintypes.DWORD),
|
|||
|
("time", wintypes.DWORD),
|
|||
|
("dwExtraInfo", ctypes.POINTER(wintypes.ULONG)),
|
|||
|
]
|
|||
|
|
|||
|
|
|||
|
class HARDWAREINPUT(ctypes.Structure):
|
|||
|
_fields_ = [
|
|||
|
("uMsg", wintypes.DWORD),
|
|||
|
("wParamL", wintypes.WORD),
|
|||
|
("wParamH", wintypes.WORD),
|
|||
|
]
|
|||
|
|
|||
|
|
|||
|
class INPUT_union(ctypes.Union):
|
|||
|
_fields_ = [
|
|||
|
("mi", MOUSEINPUT),
|
|||
|
("ki", KEYBDINPUT),
|
|||
|
("hi", HARDWAREINPUT),
|
|||
|
]
|
|||
|
|
|||
|
|
|||
|
class INPUT(ctypes.Structure):
|
|||
|
_fields_ = [
|
|||
|
("type", wintypes.DWORD),
|
|||
|
("u", INPUT_union),
|
|||
|
]
|
|||
|
|
|||
|
|
|||
|
class AboutMeDlg(QDialog): # 从 QWidget 改为 QDialog
|
|||
|
def __init__(self, parent=None):
|
|||
|
super().__init__(parent)
|
|||
|
self.setWindowTitle("关于")
|
|||
|
self.setFixedSize(400, 200)
|
|||
|
|
|||
|
layout = QVBoxLayout()
|
|||
|
|
|||
|
title = QLabel("KeyPresser", self)
|
|||
|
title.setStyleSheet("font-size: 18pt; font-weight: bold;")
|
|||
|
layout.addWidget(title)
|
|||
|
|
|||
|
desc = QLabel("一个简单的按键模拟工具", self)
|
|||
|
layout.addWidget(desc)
|
|||
|
|
|||
|
version = QLabel("版本: 专业版 (Python 无钩子版)", self)
|
|||
|
layout.addWidget(version)
|
|||
|
|
|||
|
author = QLabel("作者: xiao liu", self)
|
|||
|
layout.addWidget(author)
|
|||
|
|
|||
|
ok_button = QPushButton("确定", self)
|
|||
|
ok_button.clicked.connect(self.accept) # 从 self.close 改为 self.accept
|
|||
|
layout.addWidget(ok_button)
|
|||
|
|
|||
|
self.setLayout(layout)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
class KeyPresser(QWidget):
|
|||
|
def __init__(self, parent=None):
|
|||
|
super().__init__(parent)
|
|||
|
|
|||
|
self.target_hwnd = None
|
|||
|
self.timers = []
|
|||
|
|
|||
|
# 设置应用程序和窗口图标
|
|||
|
self.setWindowIcon(QIcon(':/aaa.ico')) # 添加这行代码
|
|||
|
|
|||
|
self.init_ui()
|
|||
|
self.load_settings()
|
|||
|
|
|||
|
# Add a timer to poll for hotkey
|
|||
|
self.hotkeyTimer = QTimer(self)
|
|||
|
self.hotkeyTimer.timeout.connect(self.check_hotkey)
|
|||
|
self.hotkeyTimer.start(50) # Check every 50ms for more responsive hotkey
|
|||
|
|
|||
|
# 添加到__init__或init_ui方法中
|
|||
|
self.mouseTimer = QTimer(self)
|
|||
|
self.mouseTimer.timeout.connect(self.click_mouse)
|
|||
|
|
|||
|
# Last key state
|
|||
|
self.lastKeyState = False
|
|||
|
|
|||
|
def init_ui(self):
|
|||
|
self.setWindowTitle("KeyPresser(无钩子版)")
|
|||
|
self.setFixedWidth(300)
|
|||
|
|
|||
|
layout = QVBoxLayout(self)
|
|||
|
|
|||
|
# Window selection
|
|||
|
label = QLabel("选择窗口:", self)
|
|||
|
layout.addWidget(label)
|
|||
|
|
|||
|
self.selectedWindowLabel = QLabel("未选择窗口", self)
|
|||
|
self.selectedWindowLabel.setStyleSheet("color: green;")
|
|||
|
layout.addWidget(self.selectedWindowLabel)
|
|||
|
|
|||
|
selectButton = QPushButton("选择窗口", self)
|
|||
|
layout.addWidget(selectButton)
|
|||
|
|
|||
|
# Mode selection for key pressing
|
|||
|
modeLayout = QHBoxLayout()
|
|||
|
modeLabel = QLabel("按键模式:", self)
|
|||
|
modeLayout.addWidget(modeLabel)
|
|||
|
|
|||
|
self.modeComboBox = QComboBox(self)
|
|||
|
self.modeComboBox.addItem("PostMessage (常规模式)", "post")
|
|||
|
self.modeComboBox.addItem("SendInput (后台模式)", "send")
|
|||
|
self.modeComboBox.addItem("SendMessage (直接模式)", "send_message")
|
|||
|
modeLayout.addWidget(self.modeComboBox)
|
|||
|
|
|||
|
layout.addLayout(modeLayout)
|
|||
|
|
|||
|
# Space key settings
|
|||
|
spaceLayout = QHBoxLayout()
|
|||
|
self.spaceCheckBox = QCheckBox(self)
|
|||
|
spaceLayout.addWidget(self.spaceCheckBox)
|
|||
|
|
|||
|
spaceLabel = QLabel("空格键时间间隔 (毫秒):", self)
|
|||
|
spaceLayout.addWidget(spaceLabel)
|
|||
|
|
|||
|
self.spaceIntervalLineEdit = QLineEdit(self)
|
|||
|
self.spaceIntervalLineEdit.setText("1000")
|
|||
|
spaceLayout.addWidget(self.spaceIntervalLineEdit)
|
|||
|
|
|||
|
layout.addLayout(spaceLayout)
|
|||
|
|
|||
|
# 鼠标左键设置 - 修复布局
|
|||
|
mouseLayout = QHBoxLayout()
|
|||
|
self.mouseCheckBox = QCheckBox(self)
|
|||
|
mouseLayout.addWidget(self.mouseCheckBox)
|
|||
|
|
|||
|
mouseLabel = QLabel("鼠标左键时间间隔 (毫秒):", self)
|
|||
|
mouseLayout.addWidget(mouseLabel) # 修复:将mouseLabel.addWidget(mouseLabel)改为mouseLayout.addWidget(mouseLabel)
|
|||
|
|
|||
|
self.mouseIntervalLineEdit = QLineEdit(self)
|
|||
|
self.mouseIntervalLineEdit.setText("1000")
|
|||
|
mouseLayout.addWidget(self.mouseIntervalLineEdit)
|
|||
|
|
|||
|
layout.addLayout(mouseLayout)
|
|||
|
|
|||
|
|
|||
|
# Custom keys
|
|||
|
keysLabel = QLabel("自定义按键和时间间隔 (毫秒):", self)
|
|||
|
layout.addWidget(keysLabel)
|
|||
|
|
|||
|
keysLayout = QGridLayout()
|
|||
|
self.keyCheckBoxes = []
|
|||
|
self.keyCombos = []
|
|||
|
self.intervalLineEdits = []
|
|||
|
|
|||
|
for i in range(10):
|
|||
|
checkbox = QCheckBox(self)
|
|||
|
keysLayout.addWidget(checkbox, i, 0)
|
|||
|
self.keyCheckBoxes.append(checkbox)
|
|||
|
|
|||
|
combobox = QComboBox(self)
|
|||
|
self.populate_key_combos(combobox)
|
|||
|
keysLayout.addWidget(combobox, i, 1)
|
|||
|
self.keyCombos.append(combobox)
|
|||
|
|
|||
|
lineEdit = QLineEdit(self)
|
|||
|
lineEdit.setText("1000")
|
|||
|
keysLayout.addWidget(lineEdit, i, 2)
|
|||
|
self.intervalLineEdits.append(lineEdit)
|
|||
|
|
|||
|
# Create timer for this key
|
|||
|
timer = QTimer(self)
|
|||
|
timer.timeout.connect(lambda checked=False, index=i: self.press_keys(index))
|
|||
|
self.timers.append(timer)
|
|||
|
|
|||
|
layout.addLayout(keysLayout)
|
|||
|
|
|||
|
# Start/Stop buttons
|
|||
|
startButton = QPushButton("开始", self)
|
|||
|
layout.addWidget(startButton)
|
|||
|
|
|||
|
stopButton = QPushButton("停止", self)
|
|||
|
layout.addWidget(stopButton)
|
|||
|
|
|||
|
labelPrompt = QLabel("修改配置后需点击开始按钮以使更改生效。", self)
|
|||
|
labelPrompt.setStyleSheet("color: red;")
|
|||
|
layout.addWidget(labelPrompt)
|
|||
|
|
|||
|
self.instructionLabel = QLabel("停止中", self)
|
|||
|
self.instructionLabel.setStyleSheet("color: green;")
|
|||
|
layout.addWidget(self.instructionLabel)
|
|||
|
|
|||
|
# Hotkey selection
|
|||
|
hotkeyLayout = QHBoxLayout()
|
|||
|
hotkeyLabel = QLabel("开始/停止快捷键:", self)
|
|||
|
hotkeyLayout.addWidget(hotkeyLabel)
|
|||
|
|
|||
|
self.hotkeyComboBox = QComboBox(self)
|
|||
|
self.populate_key_combos(self.hotkeyComboBox)
|
|||
|
self.hotkeyComboBox.setCurrentText("Home") # Default to Home key
|
|||
|
hotkeyLayout.addWidget(self.hotkeyComboBox)
|
|||
|
|
|||
|
layout.addLayout(hotkeyLayout)
|
|||
|
|
|||
|
# Debug info
|
|||
|
self.debugLabel = QLabel("状态: 管理员模式 (使用轮询检测热键)", self)
|
|||
|
self.debugLabel.setStyleSheet("color: blue;")
|
|||
|
layout.addWidget(self.debugLabel)
|
|||
|
|
|||
|
# Thread input settings
|
|||
|
threadLayout = QHBoxLayout()
|
|||
|
self.attachThreadCheckBox = QCheckBox("关联线程输入", self)
|
|||
|
self.attachThreadCheckBox.setChecked(True)
|
|||
|
threadLayout.addWidget(self.attachThreadCheckBox)
|
|||
|
|
|||
|
layout.addLayout(threadLayout)
|
|||
|
|
|||
|
aboutButton = QPushButton("关于", self)
|
|||
|
layout.addWidget(aboutButton)
|
|||
|
|
|||
|
# Connect signals
|
|||
|
selectButton.clicked.connect(self.select_window)
|
|||
|
startButton.clicked.connect(self.start_pressing)
|
|||
|
stopButton.clicked.connect(self.stop_pressing)
|
|||
|
aboutButton.clicked.connect(self.about_me)
|
|||
|
|
|||
|
self.spaceTimer = QTimer(self)
|
|||
|
self.spaceTimer.timeout.connect(self.press_space)
|
|||
|
|
|||
|
self.setLayout(layout)
|
|||
|
|
|||
|
def populate_key_combos(self, comboBox):
|
|||
|
keys = [
|
|||
|
("F1", VK_F1), ("F2", VK_F2), ("F3", VK_F3), ("F4", VK_F4),
|
|||
|
("F5", VK_F5), ("F6", VK_F6), ("F7", VK_F7), ("F8", VK_F8),
|
|||
|
("F9", VK_F9), ("F10", VK_F10), ("F11", VK_F11), ("F12", VK_F12),
|
|||
|
("A", ord('A')), ("B", ord('B')), ("C", ord('C')), ("D", ord('D')),
|
|||
|
("E", ord('E')), ("F", ord('F')), ("G", ord('G')), ("H", ord('H')),
|
|||
|
("I", ord('I')), ("J", ord('J')), ("K", ord('K')), ("L", ord('L')),
|
|||
|
("M", ord('M')), ("N", ord('N')), ("O", ord('O')), ("P", ord('P')),
|
|||
|
("Q", ord('Q')), ("R", ord('R')), ("S", ord('S')), ("T", ord('T')),
|
|||
|
("U", ord('U')), ("V", ord('V')), ("W", ord('W')), ("X", ord('X')),
|
|||
|
("Y", ord('Y')), ("Z", ord('Z')),
|
|||
|
("0", ord('0')), ("1", ord('1')), ("2", ord('2')), ("3", ord('3')),
|
|||
|
("4", ord('4')), ("5", ord('5')), ("6", ord('6')), ("7", ord('7')),
|
|||
|
("8", ord('8')), ("9", ord('9')),
|
|||
|
("Space", VK_SPACE), ("Enter", VK_RETURN), ("Tab", VK_TAB),
|
|||
|
("Esc", VK_ESCAPE), ("Backspace", VK_BACK), ("Insert", VK_INSERT),
|
|||
|
("Delete", VK_DELETE), ("Home", VK_HOME), ("End", VK_END),
|
|||
|
("Page Up", VK_PRIOR), ("Page Down", VK_NEXT),
|
|||
|
("Left Arrow", VK_LEFT), ("Right Arrow", VK_RIGHT),
|
|||
|
("Up Arrow", VK_UP), ("Down Arrow", VK_DOWN),("Scroll", VK_SCROLL)
|
|||
|
]
|
|||
|
|
|||
|
for text, value in keys:
|
|||
|
comboBox.addItem(text, value)
|
|||
|
|
|||
|
def check_hotkey(self):
|
|||
|
"""Check if the selected hotkey is pressed"""
|
|||
|
# Get the selected hotkey
|
|||
|
key_code = self.hotkeyComboBox.currentData()
|
|||
|
|
|||
|
# Check if key is pressed
|
|||
|
key_state = win32api.GetAsyncKeyState(key_code) & 0x8000 != 0
|
|||
|
|
|||
|
# Detect key press (transition from not pressed to pressed)
|
|||
|
if key_state and not self.lastKeyState:
|
|||
|
if self.instructionLabel.text() == "运行中":
|
|||
|
self.stop_pressing()
|
|||
|
else:
|
|||
|
self.start_pressing()
|
|||
|
|
|||
|
self.lastKeyState = key_state
|
|||
|
|
|||
|
def select_window(self):
|
|||
|
"""Allow user to select a target window by clicking on it"""
|
|||
|
self.selectedWindowLabel.setText("请点击目标窗口...")
|
|||
|
|
|||
|
# Hide our window temporarily
|
|||
|
self.hide()
|
|||
|
QApplication.processEvents()
|
|||
|
time.sleep(0.2) # Give a small delay for user to see the message
|
|||
|
|
|||
|
# Poll for mouse click
|
|||
|
while True:
|
|||
|
if win32api.GetAsyncKeyState(0x01) & 0x8000: # Left mouse button pressed
|
|||
|
cursor_pos = win32gui.GetCursorPos()
|
|||
|
window_handle = win32gui.WindowFromPoint(cursor_pos)
|
|||
|
if window_handle:
|
|||
|
window_text = win32gui.GetWindowText(window_handle)
|
|||
|
self.target_hwnd = window_handle
|
|||
|
|
|||
|
# Get process ID for debugging
|
|||
|
try:
|
|||
|
_, process_id = win32process.GetWindowThreadProcessId(window_handle)
|
|||
|
self.debugLabel.setText(f"状态: 已选择窗口 (PID: {process_id})")
|
|||
|
except:
|
|||
|
pass
|
|||
|
|
|||
|
self.selectedWindowLabel.setText(window_text)
|
|||
|
break
|
|||
|
QApplication.processEvents()
|
|||
|
time.sleep(0.1)
|
|||
|
|
|||
|
# Show our window again
|
|||
|
time.sleep(0.2) # Wait for mouse release
|
|||
|
self.show()
|
|||
|
|
|||
|
def start_pressing(self):
|
|||
|
"""Start sending keystrokes to the target window"""
|
|||
|
if not self.target_hwnd:
|
|||
|
QMessageBox.warning(self, "警告", "请选择窗口后,再点击开始!")
|
|||
|
return
|
|||
|
|
|||
|
self.instructionLabel.setText("运行中")
|
|||
|
self.stop_all_timers()
|
|||
|
|
|||
|
# Check if target window still exists
|
|||
|
if not win32gui.IsWindow(self.target_hwnd):
|
|||
|
QMessageBox.warning(self, "警告", "目标窗口已关闭,请重新选择!")
|
|||
|
self.target_hwnd = None
|
|||
|
self.selectedWindowLabel.setText("未选择窗口")
|
|||
|
self.instructionLabel.setText("停止中")
|
|||
|
return
|
|||
|
|
|||
|
# Try to attach to target window thread if option is checked
|
|||
|
if self.attachThreadCheckBox.isChecked():
|
|||
|
try:
|
|||
|
target_thread_id = win32process.GetWindowThreadProcessId(self.target_hwnd)[0]
|
|||
|
current_thread_id = win32api.GetCurrentThreadId()
|
|||
|
win32process.AttachThreadInput(current_thread_id, target_thread_id, True)
|
|||
|
self.debugLabel.setText("状态: 已关联线程输入")
|
|||
|
except Exception as e:
|
|||
|
self.debugLabel.setText(f"状态: 线程关联失败 ({str(e)})")
|
|||
|
|
|||
|
# Start the space key timer if checked
|
|||
|
if self.spaceCheckBox.isChecked():
|
|||
|
self.press_space()
|
|||
|
interval = int(self.spaceIntervalLineEdit.text())
|
|||
|
self.spaceTimer.start(interval)
|
|||
|
|
|||
|
# 如果选中,启动鼠标点击定时器
|
|||
|
if self.mouseCheckBox.isChecked():
|
|||
|
self.click_mouse() # 立即执行
|
|||
|
interval = int(self.mouseIntervalLineEdit.text())
|
|||
|
self.mouseTimer.start(interval)
|
|||
|
|
|||
|
# Start the custom key timers if checked
|
|||
|
for i in range(10):
|
|||
|
if self.keyCheckBoxes[i].isChecked() and self.keyCombos[i].currentIndex() != -1:
|
|||
|
self.press_keys(i)
|
|||
|
interval = int(self.intervalLineEdits[i].text())
|
|||
|
self.timers[i].start(interval)
|
|||
|
|
|||
|
def stop_pressing(self):
|
|||
|
"""Stop sending keystrokes"""
|
|||
|
self.instructionLabel.setText("停止中")
|
|||
|
self.stop_all_timers()
|
|||
|
|
|||
|
# Try to detach from target window thread if option is checked
|
|||
|
if self.attachThreadCheckBox.isChecked() and self.target_hwnd:
|
|||
|
try:
|
|||
|
target_thread_id = win32process.GetWindowThreadProcessId(self.target_hwnd)[0]
|
|||
|
current_thread_id = win32api.GetCurrentThreadId()
|
|||
|
win32process.AttachThreadInput(current_thread_id, target_thread_id, False)
|
|||
|
self.debugLabel.setText("状态: 已解除线程关联")
|
|||
|
except:
|
|||
|
pass
|
|||
|
|
|||
|
def about_me(self):
|
|||
|
"""Show the about dialog"""
|
|||
|
about_dialog = AboutMeDlg(self)
|
|||
|
about_dialog.exec_()
|
|||
|
|
|||
|
def stop_all_timers(self):
|
|||
|
"""Stop all key press timers"""
|
|||
|
self.spaceTimer.stop()
|
|||
|
self.mouseTimer.stop() # 添加鼠标定时器停止
|
|||
|
for timer in self.timers:
|
|||
|
timer.stop()
|
|||
|
|
|||
|
def press_space(self):
|
|||
|
"""Send space key to target window"""
|
|||
|
if self.target_hwnd and win32gui.IsWindow(self.target_hwnd):
|
|||
|
key_mode = self.modeComboBox.currentData()
|
|||
|
key_code = VK_SPACE
|
|||
|
|
|||
|
if key_mode == "post":
|
|||
|
# Use PostMessage method (original method)
|
|||
|
scan_code = win32api.MapVirtualKey(key_code, 0)
|
|||
|
|
|||
|
# Key down
|
|||
|
lparam = (scan_code << 16) | 1
|
|||
|
win32gui.PostMessage(self.target_hwnd, WM_KEYDOWN, key_code, lparam)
|
|||
|
|
|||
|
# Key up
|
|||
|
lparam = (scan_code << 16) | (1 | (1 << 30) | (1 << 31))
|
|||
|
win32gui.PostMessage(self.target_hwnd, WM_KEYUP, key_code, lparam)
|
|||
|
|
|||
|
elif key_mode == "send_message":
|
|||
|
# Use SendMessage method (synchronous, might work better for some apps)
|
|||
|
scan_code = win32api.MapVirtualKey(key_code, 0)
|
|||
|
|
|||
|
# Key down
|
|||
|
lparam = (scan_code << 16) | 1
|
|||
|
win32gui.SendMessage(self.target_hwnd, WM_KEYDOWN, key_code, lparam)
|
|||
|
|
|||
|
# Send WM_CHAR message (important for text input)
|
|||
|
win32gui.SendMessage(self.target_hwnd, WM_CHAR, ord(' '), lparam)
|
|||
|
|
|||
|
# Key up
|
|||
|
lparam = (scan_code << 16) | (1 | (1 << 30) | (1 << 31))
|
|||
|
win32gui.SendMessage(self.target_hwnd, WM_KEYUP, key_code, lparam)
|
|||
|
|
|||
|
else: # send mode - uses SendInput
|
|||
|
# Use SendInput method (works for background windows sometimes)
|
|||
|
self.send_key(key_code)
|
|||
|
|
|||
|
def press_keys(self, index):
|
|||
|
"""Send keys to target window"""
|
|||
|
if self.target_hwnd and win32gui.IsWindow(self.target_hwnd):
|
|||
|
key_code = self.keyCombos[index].currentData()
|
|||
|
key_mode = self.modeComboBox.currentData()
|
|||
|
|
|||
|
if key_mode == "post":
|
|||
|
# Use PostMessage method (original method)
|
|||
|
scan_code = win32api.MapVirtualKey(key_code, 0)
|
|||
|
|
|||
|
# Key down
|
|||
|
lparam = (scan_code << 16) | 1
|
|||
|
win32gui.PostMessage(self.target_hwnd, WM_KEYDOWN, key_code, lparam)
|
|||
|
|
|||
|
# Send WM_CHAR for regular keys
|
|||
|
if key_code >= ord('A') and key_code <= ord('Z'):
|
|||
|
# Convert to lowercase for character input
|
|||
|
char_code = key_code + 32 # ASCII difference between uppercase and lowercase
|
|||
|
win32gui.PostMessage(self.target_hwnd, WM_CHAR, char_code, lparam)
|
|||
|
elif key_code >= ord('0') and key_code <= ord('9') or key_code == VK_SPACE:
|
|||
|
win32gui.PostMessage(self.target_hwnd, WM_CHAR, key_code, lparam)
|
|||
|
|
|||
|
# Key up
|
|||
|
lparam = (scan_code << 16) | (1 | (1 << 30) | (1 << 31))
|
|||
|
win32gui.PostMessage(self.target_hwnd, WM_KEYUP, key_code, lparam)
|
|||
|
|
|||
|
elif key_mode == "send_message":
|
|||
|
# Use SendMessage method (synchronous, might work better for some apps)
|
|||
|
scan_code = win32api.MapVirtualKey(key_code, 0)
|
|||
|
|
|||
|
# Key down
|
|||
|
lparam = (scan_code << 16) | 1
|
|||
|
win32gui.SendMessage(self.target_hwnd, WM_KEYDOWN, key_code, lparam)
|
|||
|
|
|||
|
# Send WM_CHAR for regular keys
|
|||
|
if key_code >= ord('A') and key_code <= ord('Z'):
|
|||
|
# Convert to lowercase for character input
|
|||
|
char_code = key_code + 32 # ASCII difference between uppercase and lowercase
|
|||
|
win32gui.SendMessage(self.target_hwnd, WM_CHAR, char_code, lparam)
|
|||
|
elif key_code >= ord('0') and key_code <= ord('9') or key_code == VK_SPACE:
|
|||
|
win32gui.SendMessage(self.target_hwnd, WM_CHAR, key_code, lparam)
|
|||
|
|
|||
|
# Key up
|
|||
|
lparam = (scan_code << 16) | (1 | (1 << 30) | (1 << 31))
|
|||
|
win32gui.SendMessage(self.target_hwnd, WM_KEYUP, key_code, lparam)
|
|||
|
|
|||
|
else: # send mode - uses SendInput
|
|||
|
# Use SendInput method (works for background windows sometimes)
|
|||
|
self.send_key(key_code)
|
|||
|
|
|||
|
def send_key(self, key_code):
|
|||
|
"""Send a key press using SendInput (works for background windows sometimes)"""
|
|||
|
# Try to set target window to foreground (optional, may help in some cases)
|
|||
|
try:
|
|||
|
if self.target_hwnd:
|
|||
|
win32gui.SetForegroundWindow(self.target_hwnd)
|
|||
|
except:
|
|||
|
pass # Ignore if activation fails
|
|||
|
|
|||
|
# Create Input structure for key down
|
|||
|
inputs = (INPUT * 2)()
|
|||
|
|
|||
|
# Key down event
|
|||
|
inputs[0].type = INPUT_KEYBOARD
|
|||
|
inputs[0].u.ki.wVk = key_code
|
|||
|
inputs[0].u.ki.wScan = win32api.MapVirtualKey(key_code, 0)
|
|||
|
inputs[0].u.ki.dwFlags = 0
|
|||
|
inputs[0].u.ki.time = 0
|
|||
|
inputs[0].u.ki.dwExtraInfo = None
|
|||
|
|
|||
|
# Key up event
|
|||
|
inputs[1].type = INPUT_KEYBOARD
|
|||
|
inputs[1].u.ki.wVk = key_code
|
|||
|
inputs[1].u.ki.wScan = win32api.MapVirtualKey(key_code, 0)
|
|||
|
inputs[1].u.ki.dwFlags = KEYEVENTF_KEYUP
|
|||
|
inputs[1].u.ki.time = 0
|
|||
|
inputs[1].u.ki.dwExtraInfo = None
|
|||
|
|
|||
|
# Send input
|
|||
|
ctypes.windll.user32.SendInput(2, ctypes.byref(inputs), ctypes.sizeof(INPUT))
|
|||
|
|
|||
|
def click_mouse(self):
|
|||
|
"""向目标窗口发送鼠标点击"""
|
|||
|
if self.target_hwnd and win32gui.IsWindow(self.target_hwnd):
|
|||
|
key_mode = self.modeComboBox.currentData()
|
|||
|
|
|||
|
# 获取窗口客户区坐标
|
|||
|
try:
|
|||
|
# 获取窗口矩形和客户区矩形
|
|||
|
#window_rect = win32gui.GetWindowRect(self.target_hwnd)
|
|||
|
#client_rect = win32gui.GetClientRect(self.target_hwnd)
|
|||
|
|
|||
|
# 计算客户区中心
|
|||
|
#client_center_x = client_rect[2] // 2
|
|||
|
#client_center_y = client_rect[3] // 2
|
|||
|
|
|||
|
# 将客户区坐标映射到屏幕坐标
|
|||
|
#pt = win32gui.ClientToScreen(self.target_hwnd, (client_center_x, client_center_y))
|
|||
|
|
|||
|
if key_mode == "post" or key_mode == "send_message":
|
|||
|
# 使用PostMessage/SendMessage进行鼠标点击
|
|||
|
# 将屏幕坐标转换为客户区坐标
|
|||
|
#client_pt = win32gui.ScreenToClient(self.target_hwnd, pt)
|
|||
|
lparam = 0 #client_pt[0] | (client_pt[1] << 16)
|
|||
|
|
|||
|
if key_mode == "post":
|
|||
|
# 鼠标按下
|
|||
|
win32gui.PostMessage(self.target_hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
|
|||
|
# 鼠标抬起
|
|||
|
win32gui.PostMessage(self.target_hwnd, win32con.WM_LBUTTONUP, 0, lparam)
|
|||
|
else: # send_message
|
|||
|
# 鼠标按下
|
|||
|
win32gui.SendMessage(self.target_hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
|
|||
|
# 鼠标抬起
|
|||
|
win32gui.SendMessage(self.target_hwnd, win32con.WM_LBUTTONUP, 0, lparam)
|
|||
|
|
|||
|
else: # send模式 - 使用SendInput
|
|||
|
# 移动光标到目标位置(可选,某些应用程序可能需要)
|
|||
|
#try:
|
|||
|
# win32api.SetCursorPos(pt)
|
|||
|
#except:
|
|||
|
# pass
|
|||
|
|
|||
|
# 初始化鼠标输入结构
|
|||
|
inputs = (INPUT * 2)()
|
|||
|
|
|||
|
# 鼠标按下事件
|
|||
|
inputs[0].type = INPUT_MOUSE
|
|||
|
inputs[0].u.mi.dx = 0 #pt[0]
|
|||
|
inputs[0].u.mi.dy = 0 #pt[1]
|
|||
|
inputs[0].u.mi.mouseData = 0
|
|||
|
inputs[0].u.mi.dwFlags = MOUSEEVENTF_LEFTDOWN
|
|||
|
inputs[0].u.mi.time = 0
|
|||
|
inputs[0].u.mi.dwExtraInfo = None
|
|||
|
|
|||
|
# 鼠标抬起事件
|
|||
|
inputs[1].type = INPUT_MOUSE
|
|||
|
inputs[1].u.mi.dx = 0 #pt[0]
|
|||
|
inputs[1].u.mi.dy = 0 #pt[1]
|
|||
|
inputs[1].u.mi.mouseData = 0
|
|||
|
inputs[1].u.mi.dwFlags = MOUSEEVENTF_LEFTUP
|
|||
|
inputs[1].u.mi.time = 0
|
|||
|
inputs[1].u.mi.dwExtraInfo = None
|
|||
|
|
|||
|
# 发送输入
|
|||
|
ctypes.windll.user32.SendInput(2, ctypes.byref(inputs), ctypes.sizeof(INPUT))
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
print(f"发送鼠标点击时出错: {str(e)}")
|
|||
|
|
|||
|
def load_settings(self):
|
|||
|
"""Load saved settings"""
|
|||
|
settings = QSettings("FinnSoft", "KeyPresser")
|
|||
|
|
|||
|
# 在load_settings方法中添加:
|
|||
|
self.mouseCheckBox.setChecked(settings.value("mouseCheckBox", False, type=bool))
|
|||
|
self.mouseIntervalLineEdit.setText(settings.value("mouseIntervalLineEdit", "1000"))
|
|||
|
|
|||
|
self.spaceCheckBox.setChecked(settings.value("spaceCheckBox", False, type=bool))
|
|||
|
self.spaceIntervalLineEdit.setText(settings.value("spaceIntervalLineEdit", "1000"))
|
|||
|
|
|||
|
# Mode selection
|
|||
|
mode_index = settings.value("modeIndex", 0, type=int)
|
|||
|
if 0 <= mode_index < self.modeComboBox.count():
|
|||
|
self.modeComboBox.setCurrentIndex(mode_index)
|
|||
|
|
|||
|
# Thread attachment option
|
|||
|
self.attachThreadCheckBox.setChecked(settings.value("attachThread", True, type=bool))
|
|||
|
|
|||
|
for i in range(10):
|
|||
|
self.keyCheckBoxes[i].setChecked(settings.value(f"keyCheckBox{i}", False, type=bool))
|
|||
|
combo_index = settings.value(f"keyCombo{i}", 0, type=int)
|
|||
|
if 0 <= combo_index < self.keyCombos[i].count():
|
|||
|
self.keyCombos[i].setCurrentIndex(combo_index)
|
|||
|
self.intervalLineEdits[i].setText(settings.value(f"intervalLineEdit{i}", "1000"))
|
|||
|
|
|||
|
# Hotkey
|
|||
|
hotkey_index = settings.value("hotkeyIndex", 47, type=int) # Default to Home key
|
|||
|
if 0 <= hotkey_index < self.hotkeyComboBox.count():
|
|||
|
self.hotkeyComboBox.setCurrentIndex(hotkey_index)
|
|||
|
|
|||
|
def save_settings(self):
|
|||
|
"""Save current settings"""
|
|||
|
settings = QSettings("FinnSoft", "KeyPresser")
|
|||
|
|
|||
|
settings.setValue("spaceCheckBox", self.spaceCheckBox.isChecked())
|
|||
|
settings.setValue("spaceIntervalLineEdit", self.spaceIntervalLineEdit.text())
|
|||
|
|
|||
|
# 在save_settings方法中添加:
|
|||
|
settings.setValue("mouseCheckBox", self.mouseCheckBox.isChecked())
|
|||
|
settings.setValue("mouseIntervalLineEdit", self.mouseIntervalLineEdit.text())
|
|||
|
|
|||
|
# Mode selection
|
|||
|
settings.setValue("modeIndex", self.modeComboBox.currentIndex())
|
|||
|
|
|||
|
# Thread attachment option
|
|||
|
settings.setValue("attachThread", self.attachThreadCheckBox.isChecked())
|
|||
|
|
|||
|
for i in range(10):
|
|||
|
settings.setValue(f"keyCheckBox{i}", self.keyCheckBoxes[i].isChecked())
|
|||
|
settings.setValue(f"keyCombo{i}", self.keyCombos[i].currentIndex())
|
|||
|
settings.setValue(f"intervalLineEdit{i}", self.intervalLineEdits[i].text())
|
|||
|
|
|||
|
# Hotkey
|
|||
|
settings.setValue("hotkeyIndex", self.hotkeyComboBox.currentIndex())
|
|||
|
|
|||
|
def closeEvent(self, event):
|
|||
|
"""Handle window close event"""
|
|||
|
self.save_settings()
|
|||
|
super().closeEvent(event)
|
|||
|
|
|||
|
|
|||
|
if __name__ == '__main__':
|
|||
|
# 启用高DPI缩放支持
|
|||
|
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
|
|||
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
|||
|
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
|
|||
|
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
|||
|
|
|||
|
app = QApplication(sys.argv)
|
|||
|
app.setWindowIcon(QIcon(':/aaa.ico'))
|
|||
|
window = KeyPresser()
|
|||
|
window.show()
|
|||
|
sys.exit(app.exec_())
|