|
|
@@ -0,0 +1,906 @@
|
|
|
+import sys
|
|
|
+import time
|
|
|
+import win32gui
|
|
|
+import win32con
|
|
|
+import win32api
|
|
|
+import win32process
|
|
|
+import threading
|
|
|
+from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|
|
+ QHBoxLayout, QPushButton, QListWidget, QListWidgetItem,
|
|
|
+ QMessageBox, QLabel, QLineEdit, QGroupBox, QTextEdit,
|
|
|
+ QSplitter, QComboBox, QSpinBox, QCheckBox, QRadioButton,
|
|
|
+ QButtonGroup)
|
|
|
+from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QObject
|
|
|
+from PyQt5.QtGui import QFont
|
|
|
+
|
|
|
+
|
|
|
+class WorkerSignals(QObject):
|
|
|
+ """工作线程信号"""
|
|
|
+ status = pyqtSignal(str)
|
|
|
+ progress = pyqtSignal(int, int) # 当前索引, 总数
|
|
|
+ finished = pyqtSignal()
|
|
|
+
|
|
|
+
|
|
|
+class WindowManagerApp(QMainWindow):
|
|
|
+ def __init__(self):
|
|
|
+ super().__init__()
|
|
|
+ self.window_handles = []
|
|
|
+ self.all_windows = []
|
|
|
+ self.is_running = False
|
|
|
+ self.should_stop = False # 停止标志
|
|
|
+ self.initUI()
|
|
|
+
|
|
|
+ def initUI(self):
|
|
|
+ """初始化界面"""
|
|
|
+ self.setWindowTitle('霸器窗口自动点击工具 - 多窗口批量执行')
|
|
|
+ self.setGeometry(100, 100, 950, 750)
|
|
|
+
|
|
|
+ # 设置主窗口样式
|
|
|
+ self.setStyleSheet("""
|
|
|
+ QMainWindow {
|
|
|
+ background-color: #2b2b2b;
|
|
|
+ }
|
|
|
+ QGroupBox {
|
|
|
+ color: #ffffff;
|
|
|
+ border: 2px solid #555;
|
|
|
+ border-radius: 5px;
|
|
|
+ margin-top: 10px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ QGroupBox::title {
|
|
|
+ subcontrol-origin: margin;
|
|
|
+ left: 10px;
|
|
|
+ padding: 0 5px 0 5px;
|
|
|
+ }
|
|
|
+ QPushButton {
|
|
|
+ background-color: #4a4a4a;
|
|
|
+ color: white;
|
|
|
+ border: 1px solid #555;
|
|
|
+ padding: 8px 16px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ QPushButton:hover {
|
|
|
+ background-color: #5a5a5a;
|
|
|
+ }
|
|
|
+ QPushButton:pressed {
|
|
|
+ background-color: #3a3a3a;
|
|
|
+ }
|
|
|
+ QPushButton#refreshBtn {
|
|
|
+ background-color: #0078d4;
|
|
|
+ }
|
|
|
+ QPushButton#refreshBtn:hover {
|
|
|
+ background-color: #1a8ad4;
|
|
|
+ }
|
|
|
+ QPushButton#startBtn {
|
|
|
+ background-color: #107c10;
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 12px 24px;
|
|
|
+ }
|
|
|
+ QPushButton#startBtn:hover {
|
|
|
+ background-color: #1a8c1a;
|
|
|
+ }
|
|
|
+ QPushButton#startBtn:disabled {
|
|
|
+ background-color: #555;
|
|
|
+ }
|
|
|
+ QPushButton#stopBtn {
|
|
|
+ background-color: #d13438;
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 12px 24px;
|
|
|
+ }
|
|
|
+ QPushButton#stopBtn:hover {
|
|
|
+ background-color: #e14448;
|
|
|
+ }
|
|
|
+ QListWidget {
|
|
|
+ background-color: #3c3c3c;
|
|
|
+ color: #ffffff;
|
|
|
+ border: 1px solid #555;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 5px;
|
|
|
+ }
|
|
|
+ QListWidget::item {
|
|
|
+ padding: 8px;
|
|
|
+ border-bottom: 1px solid #444;
|
|
|
+ }
|
|
|
+ QListWidget::item:selected {
|
|
|
+ background-color: #0078d4;
|
|
|
+ }
|
|
|
+ QLabel {
|
|
|
+ color: #cccccc;
|
|
|
+ }
|
|
|
+ QCheckBox {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 5px;
|
|
|
+ }
|
|
|
+ QCheckBox::indicator {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ }
|
|
|
+ QCheckBox::indicator:unchecked {
|
|
|
+ background-color: #3c3c3c;
|
|
|
+ border: 2px solid #555;
|
|
|
+ border-radius: 3px;
|
|
|
+ }
|
|
|
+ QCheckBox::indicator:checked {
|
|
|
+ background-color: #0078d4;
|
|
|
+ border: 2px solid #0078d4;
|
|
|
+ border-radius: 3px;
|
|
|
+ }
|
|
|
+ QRadioButton {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 5px;
|
|
|
+ }
|
|
|
+ QRadioButton::indicator {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ }
|
|
|
+ QRadioButton::indicator:unchecked {
|
|
|
+ background-color: #3c3c3c;
|
|
|
+ border: 2px solid #555;
|
|
|
+ border-radius: 9px;
|
|
|
+ }
|
|
|
+ QRadioButton::indicator:checked {
|
|
|
+ background-color: #0078d4;
|
|
|
+ border: 2px solid #0078d4;
|
|
|
+ border-radius: 9px;
|
|
|
+ }
|
|
|
+ QLineEdit, QComboBox, QSpinBox {
|
|
|
+ background-color: #3c3c3c;
|
|
|
+ color: #ffffff;
|
|
|
+ border: 1px solid #555;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 5px;
|
|
|
+ }
|
|
|
+ QTextEdit {
|
|
|
+ background-color: #1e1e1e;
|
|
|
+ color: #00ff00;
|
|
|
+ border: 1px solid #555;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-family: 'Consolas', monospace;
|
|
|
+ font-size: 10pt;
|
|
|
+ }
|
|
|
+ """)
|
|
|
+
|
|
|
+ # 创建中央窗口
|
|
|
+ central_widget = QWidget()
|
|
|
+ self.setCentralWidget(central_widget)
|
|
|
+ main_layout = QVBoxLayout(central_widget)
|
|
|
+
|
|
|
+ # 标题
|
|
|
+ title_label = QLabel('霸器窗口自动点击工具 - 批量执行')
|
|
|
+ title_label.setAlignment(Qt.AlignCenter)
|
|
|
+ title_font = QFont('Arial', 18, QFont.Bold)
|
|
|
+ title_label.setFont(title_font)
|
|
|
+ title_label.setStyleSheet('color: #00b4d8; margin: 15px;')
|
|
|
+ main_layout.addWidget(title_label)
|
|
|
+
|
|
|
+ # 控制区域
|
|
|
+ control_group = QGroupBox('控制面板')
|
|
|
+ control_layout = QHBoxLayout()
|
|
|
+
|
|
|
+ self.refresh_btn = QPushButton('🔄 加载窗口')
|
|
|
+ self.refresh_btn.setObjectName('refreshBtn')
|
|
|
+ self.refresh_btn.clicked.connect(self.load_processes)
|
|
|
+ control_layout.addWidget(self.refresh_btn)
|
|
|
+
|
|
|
+ self.filter_label = QLabel('筛选:')
|
|
|
+ control_layout.addWidget(self.filter_label)
|
|
|
+
|
|
|
+ self.filter_input = QLineEdit()
|
|
|
+ self.filter_input.setPlaceholderText('输入标题关键词过滤...')
|
|
|
+ self.filter_input.textChanged.connect(self.filter_list)
|
|
|
+ control_layout.addWidget(self.filter_input)
|
|
|
+
|
|
|
+ # 显示模式选择
|
|
|
+ self.show_mode_combo = QComboBox()
|
|
|
+ self.show_mode_combo.addItems(['所有窗口', '仅可见窗口', '仅隐藏窗口'])
|
|
|
+ self.show_mode_combo.currentTextChanged.connect(self.on_show_mode_changed)
|
|
|
+ control_layout.addWidget(QLabel('显示:'))
|
|
|
+ control_layout.addWidget(self.show_mode_combo)
|
|
|
+
|
|
|
+ control_group.setLayout(control_layout)
|
|
|
+ main_layout.addWidget(control_group)
|
|
|
+
|
|
|
+ # 复选框区域 - 功能选择
|
|
|
+ checkbox_group = QGroupBox('功能选择')
|
|
|
+ checkbox_layout = QHBoxLayout()
|
|
|
+
|
|
|
+ self.cb_molong = QCheckBox('争夺魔龙之心')
|
|
|
+ self.cb_molong.setChecked(False)
|
|
|
+ checkbox_layout.addWidget(self.cb_molong)
|
|
|
+
|
|
|
+ self.cb_feicui = QCheckBox('翡翠荣耀旗')
|
|
|
+ self.cb_feicui.setChecked(False)
|
|
|
+ checkbox_layout.addWidget(self.cb_feicui)
|
|
|
+
|
|
|
+ self.cb_jinyan = QCheckBox('金岩霸者棋')
|
|
|
+ self.cb_jinyan.setChecked(False)
|
|
|
+ checkbox_layout.addWidget(self.cb_jinyan)
|
|
|
+
|
|
|
+ self.cb_chiyan = QCheckBox('炽焰战神棋')
|
|
|
+ self.cb_chiyan.setChecked(False)
|
|
|
+ checkbox_layout.addWidget(self.cb_chiyan)
|
|
|
+
|
|
|
+ checkbox_group.setLayout(checkbox_layout)
|
|
|
+ main_layout.addWidget(checkbox_group)
|
|
|
+
|
|
|
+ # 单选按钮区域 - 皇宫占位
|
|
|
+ radio_group = QGroupBox('皇宫占位位置')
|
|
|
+ radio_layout = QHBoxLayout()
|
|
|
+
|
|
|
+ self.radio_random = QRadioButton('随机')
|
|
|
+ self.radio_random.setChecked(True)
|
|
|
+ radio_layout.addWidget(self.radio_random)
|
|
|
+
|
|
|
+ self.radio_door = QRadioButton('门口')
|
|
|
+ radio_layout.addWidget(self.radio_door)
|
|
|
+
|
|
|
+ self.radio_corner = QRadioButton('角落')
|
|
|
+ radio_layout.addWidget(self.radio_corner)
|
|
|
+
|
|
|
+ # 将单选按钮分组
|
|
|
+ self.radio_button_group = QButtonGroup()
|
|
|
+ self.radio_button_group.addButton(self.radio_random)
|
|
|
+ self.radio_button_group.addButton(self.radio_door)
|
|
|
+ self.radio_button_group.addButton(self.radio_corner)
|
|
|
+
|
|
|
+ radio_group.setLayout(radio_layout)
|
|
|
+ main_layout.addWidget(radio_group)
|
|
|
+
|
|
|
+ # 窗口列表 - 支持多选
|
|
|
+ list_group = QGroupBox('窗口列表 (霸器-开头) - 可多选')
|
|
|
+ list_layout = QVBoxLayout()
|
|
|
+
|
|
|
+ self.window_list = QListWidget()
|
|
|
+ self.window_list.setSelectionMode(QListWidget.MultiSelection) # 改为多选模式
|
|
|
+ self.window_list.itemDoubleClicked.connect(self.show_selected_window)
|
|
|
+ list_layout.addWidget(self.window_list)
|
|
|
+
|
|
|
+ # 添加全选/取消全选按钮
|
|
|
+ select_layout = QHBoxLayout()
|
|
|
+ self.select_all_btn = QPushButton('全选')
|
|
|
+ self.select_all_btn.clicked.connect(self.select_all_items)
|
|
|
+ select_layout.addWidget(self.select_all_btn)
|
|
|
+
|
|
|
+ self.deselect_all_btn = QPushButton('取消全选')
|
|
|
+ self.deselect_all_btn.clicked.connect(self.deselect_all_items)
|
|
|
+ select_layout.addWidget(self.deselect_all_btn)
|
|
|
+
|
|
|
+ self.selected_count_label = QLabel('已选: 0 个窗口')
|
|
|
+ self.selected_count_label.setStyleSheet('color: #00b4d8;')
|
|
|
+ select_layout.addWidget(self.selected_count_label)
|
|
|
+ select_layout.addStretch()
|
|
|
+
|
|
|
+ list_layout.addLayout(select_layout)
|
|
|
+
|
|
|
+ list_group.setLayout(list_layout)
|
|
|
+ main_layout.addWidget(list_group)
|
|
|
+
|
|
|
+ # 操作按钮区域
|
|
|
+ action_group = QGroupBox('操作')
|
|
|
+ action_layout = QHBoxLayout()
|
|
|
+
|
|
|
+ self.start_btn = QPushButton('▶️ 开始批量执行')
|
|
|
+ self.start_btn.setObjectName('startBtn')
|
|
|
+ self.start_btn.clicked.connect(self.start_auto_click)
|
|
|
+ action_layout.addWidget(self.start_btn)
|
|
|
+
|
|
|
+ self.stop_btn = QPushButton('⏹️ 停止')
|
|
|
+ self.stop_btn.setObjectName('stopBtn')
|
|
|
+ self.stop_btn.clicked.connect(self.stop_auto_click)
|
|
|
+ self.stop_btn.setEnabled(False)
|
|
|
+ action_layout.addWidget(self.stop_btn)
|
|
|
+
|
|
|
+ self.show_btn = QPushButton('👁️ 显示选中窗口')
|
|
|
+ self.show_btn.setObjectName('showBtn')
|
|
|
+ self.show_btn.clicked.connect(self.show_selected_window)
|
|
|
+ action_layout.addWidget(self.show_btn)
|
|
|
+
|
|
|
+ action_group.setLayout(action_layout)
|
|
|
+ main_layout.addWidget(action_group)
|
|
|
+
|
|
|
+ # 日志区域
|
|
|
+ log_group = QGroupBox('运行日志')
|
|
|
+ log_layout = QVBoxLayout()
|
|
|
+
|
|
|
+ self.log_text = QTextEdit()
|
|
|
+ self.log_text.setReadOnly(True)
|
|
|
+ self.log_text.setMaximumHeight(150)
|
|
|
+ log_layout.addWidget(self.log_text)
|
|
|
+
|
|
|
+ log_group.setLayout(log_layout)
|
|
|
+ main_layout.addWidget(log_group)
|
|
|
+
|
|
|
+ # 状态栏
|
|
|
+ self.status_label = QLabel('就绪')
|
|
|
+ self.status_label.setStyleSheet('color: #888; margin: 5px;')
|
|
|
+ main_layout.addWidget(self.status_label)
|
|
|
+
|
|
|
+ # 自动加载
|
|
|
+ self.load_processes()
|
|
|
+
|
|
|
+ # 工作线程信号
|
|
|
+ self.signals = WorkerSignals()
|
|
|
+ self.signals.status.connect(self.update_status)
|
|
|
+ self.signals.progress.connect(self.update_progress)
|
|
|
+ self.signals.finished.connect(self.on_finished)
|
|
|
+
|
|
|
+ def select_all_items(self):
|
|
|
+ """全选所有窗口"""
|
|
|
+ for i in range(self.window_list.count()):
|
|
|
+ item = self.window_list.item(i)
|
|
|
+ if not item.isHidden():
|
|
|
+ item.setSelected(True)
|
|
|
+ self.update_selected_count()
|
|
|
+
|
|
|
+ def deselect_all_items(self):
|
|
|
+ """取消全选"""
|
|
|
+ self.window_list.clearSelection()
|
|
|
+ self.update_selected_count()
|
|
|
+
|
|
|
+ def update_selected_count(self):
|
|
|
+ """更新已选数量"""
|
|
|
+ count = len(self.window_list.selectedItems())
|
|
|
+ self.selected_count_label.setText(f'已选: {count} 个窗口')
|
|
|
+
|
|
|
+ def get_window_title(self, hwnd):
|
|
|
+ """获取窗口标题"""
|
|
|
+ try:
|
|
|
+ return win32gui.GetWindowText(hwnd)
|
|
|
+ except:
|
|
|
+ return ''
|
|
|
+
|
|
|
+ def is_window_visible(self, hwnd):
|
|
|
+ """检查窗口是否可见"""
|
|
|
+ try:
|
|
|
+ return win32gui.IsWindowVisible(hwnd)
|
|
|
+ except:
|
|
|
+ return False
|
|
|
+
|
|
|
+ def enum_all_windows_callback(self, hwnd, windows_list):
|
|
|
+ """枚举所有窗口的回调函数(包括隐藏窗口)"""
|
|
|
+ if win32gui.IsWindow(hwnd):
|
|
|
+ title = self.get_window_title(hwnd)
|
|
|
+ if title: # 只记录有标题的窗口
|
|
|
+ windows_list.append((hwnd, title))
|
|
|
+ return True
|
|
|
+
|
|
|
+ def load_processes(self):
|
|
|
+ """加载所有进程窗口(包括隐藏的)"""
|
|
|
+ try:
|
|
|
+ self.window_list.clear()
|
|
|
+ self.window_handles = []
|
|
|
+ self.all_windows = []
|
|
|
+
|
|
|
+ windows_list = []
|
|
|
+ win32gui.EnumWindows(self.enum_all_windows_callback, windows_list)
|
|
|
+
|
|
|
+ self.all_windows = windows_list.copy()
|
|
|
+
|
|
|
+ # 筛选以"霸器-"开头的窗口
|
|
|
+ baqi_windows = [(hwnd, title) for hwnd, title in windows_list if title.startswith('霸器-')]
|
|
|
+
|
|
|
+ if not baqi_windows:
|
|
|
+ self.status_label.setText(f'未找到以"霸器-"开头的窗口 (共找到 {len(windows_list)} 个窗口)')
|
|
|
+ self.log_text.append(f'⚠️ 未找到霸器窗口,共找到 {len(windows_list)} 个窗口')
|
|
|
+ return
|
|
|
+
|
|
|
+ # 按标题排序
|
|
|
+ baqi_windows.sort(key=lambda x: x[1])
|
|
|
+
|
|
|
+ for hwnd, title in baqi_windows:
|
|
|
+ item = QListWidgetItem(title)
|
|
|
+ item.setData(Qt.UserRole, hwnd)
|
|
|
+ visible = self.is_window_visible(hwnd)
|
|
|
+ status_text = " ✓" if visible else " ✗"
|
|
|
+ item.setText(title + status_text)
|
|
|
+ item.setData(Qt.UserRole + 1, visible)
|
|
|
+ self.window_list.addItem(item)
|
|
|
+ self.window_handles.append(hwnd)
|
|
|
+
|
|
|
+ visible_count = sum(1 for _, title in baqi_windows if self.is_window_visible(_))
|
|
|
+ hidden_count = len(baqi_windows) - visible_count
|
|
|
+
|
|
|
+ self.status_label.setText(f'找到 {len(baqi_windows)} 个"霸器-"窗口 (可见: {visible_count}, 隐藏: {hidden_count})')
|
|
|
+ self.log_text.append(f'✅ 加载完成,找到 {len(baqi_windows)} 个霸器窗口')
|
|
|
+
|
|
|
+ # 应用显示模式过滤
|
|
|
+ self.apply_show_mode()
|
|
|
+ self.update_selected_count()
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ QMessageBox.critical(self, '错误', f'加载进程失败: {str(e)}')
|
|
|
+ self.status_label.setText('加载失败')
|
|
|
+ self.log_text.append(f'❌ 加载失败: {str(e)}')
|
|
|
+
|
|
|
+ def apply_show_mode(self):
|
|
|
+ """应用显示模式过滤"""
|
|
|
+ mode = self.show_mode_combo.currentText()
|
|
|
+ for i in range(self.window_list.count()):
|
|
|
+ item = self.window_list.item(i)
|
|
|
+ visible = item.data(Qt.UserRole + 1)
|
|
|
+
|
|
|
+ if mode == '所有窗口':
|
|
|
+ item.setHidden(False)
|
|
|
+ elif mode == '仅可见窗口':
|
|
|
+ item.setHidden(not visible)
|
|
|
+ elif mode == '仅隐藏窗口':
|
|
|
+ item.setHidden(visible)
|
|
|
+ self.update_selected_count()
|
|
|
+
|
|
|
+ def on_show_mode_changed(self):
|
|
|
+ """显示模式改变时重新过滤"""
|
|
|
+ self.apply_show_mode()
|
|
|
+
|
|
|
+ def filter_list(self):
|
|
|
+ """根据输入过滤列表"""
|
|
|
+ filter_text = self.filter_input.text().lower()
|
|
|
+ for i in range(self.window_list.count()):
|
|
|
+ item = self.window_list.item(i)
|
|
|
+ title = item.text().replace(' ✓', '').replace(' ✗', '')
|
|
|
+ if filter_text in title.lower():
|
|
|
+ item.setHidden(False)
|
|
|
+ else:
|
|
|
+ item.setHidden(True)
|
|
|
+ self.apply_show_mode()
|
|
|
+
|
|
|
+ def get_selected_windows(self):
|
|
|
+ """获取所有选中的窗口"""
|
|
|
+ selected_items = self.window_list.selectedItems()
|
|
|
+ windows = []
|
|
|
+ for item in selected_items:
|
|
|
+ hwnd = item.data(Qt.UserRole)
|
|
|
+ title = item.text().replace(' ✓', '').replace(' ✗', '')
|
|
|
+ windows.append((hwnd, title))
|
|
|
+ return windows
|
|
|
+
|
|
|
+ def show_selected_window(self):
|
|
|
+ """显示选中的窗口(只显示第一个选中的)"""
|
|
|
+ selected = self.get_selected_windows()
|
|
|
+ if not selected:
|
|
|
+ QMessageBox.warning(self, '警告', '请先选择窗口')
|
|
|
+ return
|
|
|
+
|
|
|
+ hwnd, title = selected[0]
|
|
|
+ try:
|
|
|
+ win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
|
|
|
+ win32gui.SetForegroundWindow(hwnd)
|
|
|
+ if win32gui.IsIconic(hwnd):
|
|
|
+ win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
|
|
|
+
|
|
|
+ self.status_label.setText(f'已显示窗口: {title}')
|
|
|
+ self.log_text.append(f'👁️ 显示窗口: {title}')
|
|
|
+ self.refresh_window_visibility()
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ QMessageBox.critical(self, '错误', f'显示窗口失败: {str(e)}')
|
|
|
+
|
|
|
+ def refresh_window_visibility(self):
|
|
|
+ """刷新窗口中显示的可见状态"""
|
|
|
+ for i in range(self.window_list.count()):
|
|
|
+ item = self.window_list.item(i)
|
|
|
+ hwnd = item.data(Qt.UserRole)
|
|
|
+ title = item.text().replace(' ✓', '').replace(' ✗', '')
|
|
|
+ visible = self.is_window_visible(hwnd)
|
|
|
+ status_text = " ✓" if visible else " ✗"
|
|
|
+ item.setText(title + status_text)
|
|
|
+ item.setData(Qt.UserRole + 1, visible)
|
|
|
+
|
|
|
+ self.apply_show_mode()
|
|
|
+
|
|
|
+ def minimize_window(self, hwnd):
|
|
|
+ """最小化窗口"""
|
|
|
+ try:
|
|
|
+ win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
|
|
|
+ self.signals.status.emit(f'📉 已最小化窗口')
|
|
|
+ return True
|
|
|
+ except Exception as e:
|
|
|
+ self.signals.status.emit(f'❌ 最小化失败: {str(e)}')
|
|
|
+ return False
|
|
|
+
|
|
|
+ def click_at_position(self, hwnd, x_offset, y_offset, button='left'):
|
|
|
+ """
|
|
|
+ 封装的点击函数
|
|
|
+ :param hwnd: 窗口句柄
|
|
|
+ :param x_offset: X轴偏移量
|
|
|
+ :param y_offset: Y轴偏移量
|
|
|
+ :param button: 'left' 或 'right'
|
|
|
+ """
|
|
|
+ # 获取窗口位置
|
|
|
+ rect = win32gui.GetWindowRect(hwnd)
|
|
|
+ window_x = rect[0]
|
|
|
+ window_y = rect[1]
|
|
|
+
|
|
|
+ # 计算点击位置
|
|
|
+ click_x = window_x + x_offset
|
|
|
+ click_y = window_y + y_offset
|
|
|
+
|
|
|
+ # 移动鼠标到目标位置
|
|
|
+ win32api.SetCursorPos((click_x, click_y))
|
|
|
+ time.sleep(0.2)
|
|
|
+
|
|
|
+ # 模拟鼠标点击
|
|
|
+ if button == 'left':
|
|
|
+ win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, click_x, click_y, 0, 0)
|
|
|
+ time.sleep(0.1)
|
|
|
+ win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, click_x, click_y, 0, 0)
|
|
|
+ self.signals.status.emit(f'🖱️ 左键点击: ({click_x}, {click_y})')
|
|
|
+ elif button == 'right':
|
|
|
+ win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTDOWN, click_x, click_y, 0, 0)
|
|
|
+ time.sleep(0.1)
|
|
|
+ win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTUP, click_x, click_y, 0, 0)
|
|
|
+ self.signals.status.emit(f'🖱️ 右键点击: ({click_x}, {click_y})')
|
|
|
+
|
|
|
+ return click_x, click_y
|
|
|
+
|
|
|
+ def get_pixel_color(self, hwnd, x_offset, y_offset):
|
|
|
+ try:
|
|
|
+ # 获取窗口位置
|
|
|
+ rect = win32gui.GetWindowRect(hwnd)
|
|
|
+ window_x = rect[0]
|
|
|
+ window_y = rect[1]
|
|
|
+
|
|
|
+ # 计算屏幕坐标
|
|
|
+ screen_x = window_x + x_offset
|
|
|
+ screen_y = window_y + y_offset
|
|
|
+
|
|
|
+ # 获取桌面DC
|
|
|
+ hdc = win32gui.GetDC(0) # 0 表示桌面
|
|
|
+
|
|
|
+ color_ref = win32gui.GetPixel(hdc, screen_x, screen_y)
|
|
|
+
|
|
|
+ win32gui.ReleaseDC(0, hdc)
|
|
|
+
|
|
|
+ red = color_ref & 0xFF
|
|
|
+ green = (color_ref >> 8) & 0xFF
|
|
|
+ blue = (color_ref >> 16) & 0xFF
|
|
|
+
|
|
|
+ return f"{red:02X}{green:02X}{blue:02X}"
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log_text.append(f'❌ 取色失败: {str(e)}')
|
|
|
+ return "000000"
|
|
|
+
|
|
|
+ def check_and_click(self, hwnd, check_x, check_y, target_color,
|
|
|
+ click_x, click_y, button='left', check_type='equal'):
|
|
|
+ """
|
|
|
+ 检查颜色并执行点击
|
|
|
+
|
|
|
+ :param hwnd: 窗口句柄
|
|
|
+ :param check_x: 检查颜色的X偏移
|
|
|
+ :param check_y: 检查颜色的Y偏移
|
|
|
+ :param target_color: 目标颜色 (如 "FFFFFF")
|
|
|
+ :param click_x: 点击的X偏移
|
|
|
+ :param click_y: 点击的Y偏移
|
|
|
+ :param button: 'left' 或 'right'
|
|
|
+ :param check_type: 'equal' 或 'not_equal'
|
|
|
+ :return: 是否执行了点击
|
|
|
+ """
|
|
|
+ # 获取当前颜色
|
|
|
+ current_color = self.get_pixel_color(hwnd, check_x, check_y)
|
|
|
+
|
|
|
+ self.log_text.append(f'🎨 检测位置({check_x},{check_y})颜色: {current_color} (目标: {target_color})')
|
|
|
+
|
|
|
+ should_click = False
|
|
|
+ if check_type == 'equal':
|
|
|
+ should_click = (current_color == target_color)
|
|
|
+ elif check_type == 'not_equal':
|
|
|
+ should_click = (current_color != target_color)
|
|
|
+
|
|
|
+ if should_click:
|
|
|
+ self.log_text.append(f'✅ 条件满足,执行点击 ({click_x}, {click_y})')
|
|
|
+ self.click_at_position(hwnd, click_x, click_y, button)
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ self.log_text.append(f'❌ 条件不满足,跳过点击')
|
|
|
+ return False
|
|
|
+
|
|
|
+ def start_auto_click(self):
|
|
|
+ """开始自动点击 - 批量执行"""
|
|
|
+ if self.is_running:
|
|
|
+ return
|
|
|
+
|
|
|
+ # 获取所有选中的窗口
|
|
|
+ selected_windows = self.get_selected_windows()
|
|
|
+ if not selected_windows:
|
|
|
+ QMessageBox.warning(self, '警告', '请至少选择一个窗口')
|
|
|
+ return
|
|
|
+
|
|
|
+ # 检查是否至少选择了一个功能
|
|
|
+ if not (self.cb_molong.isChecked() or self.cb_feicui.isChecked() or
|
|
|
+ self.cb_jinyan.isChecked() or self.cb_chiyan.isChecked()):
|
|
|
+ QMessageBox.warning(self, '警告', '请至少选择一个功能')
|
|
|
+ return
|
|
|
+
|
|
|
+ self.is_running = True
|
|
|
+ self.should_stop = False
|
|
|
+ self.start_btn.setEnabled(False)
|
|
|
+ self.stop_btn.setEnabled(True)
|
|
|
+
|
|
|
+ # 记录选择的选项
|
|
|
+ selected = []
|
|
|
+ if self.cb_molong.isChecked(): selected.append('争夺魔龙之心')
|
|
|
+ if self.cb_feicui.isChecked(): selected.append('翡翠荣耀旗')
|
|
|
+ if self.cb_jinyan.isChecked(): selected.append('金岩霸者棋')
|
|
|
+ if self.cb_chiyan.isChecked(): selected.append('炽焰战神棋')
|
|
|
+
|
|
|
+ # 记录皇宫占位选择
|
|
|
+ if self.radio_random.isChecked():
|
|
|
+ palace_pos = '随机'
|
|
|
+ palace_y = 444
|
|
|
+ elif self.radio_door.isChecked():
|
|
|
+ palace_pos = '门口'
|
|
|
+ palace_y = 457
|
|
|
+ else: # corner
|
|
|
+ palace_pos = '角落'
|
|
|
+ palace_y = 470
|
|
|
+
|
|
|
+ self.log_text.append(f'▶️ 开始批量执行,共 {len(selected_windows)} 个窗口')
|
|
|
+ self.log_text.append(f'📋 已选功能: {", ".join(selected)}')
|
|
|
+ self.log_text.append(f'🏰 皇宫占位: {palace_pos} (Y+{palace_y})')
|
|
|
+ self.log_text.append('=' * 50)
|
|
|
+
|
|
|
+ # 在新线程中执行
|
|
|
+ thread = threading.Thread(target=self.batch_auto_click_worker,
|
|
|
+ args=(selected_windows, palace_y))
|
|
|
+ thread.daemon = True
|
|
|
+ thread.start()
|
|
|
+
|
|
|
+ def batch_auto_click_worker(self, selected_windows, palace_y):
|
|
|
+ """批量自动点击工作线程"""
|
|
|
+ total = len(selected_windows)
|
|
|
+
|
|
|
+ for idx, (hwnd, title) in enumerate(selected_windows, 1):
|
|
|
+ # 检查是否被停止
|
|
|
+ if self.should_stop:
|
|
|
+ self.signals.status.emit(f'⏹️ 用户停止执行')
|
|
|
+ break
|
|
|
+
|
|
|
+ self.signals.status.emit(f'📌 正在处理 [{idx}/{total}] {title}')
|
|
|
+ self.signals.progress.emit(idx, total)
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 执行单个窗口的点击流程
|
|
|
+ self.execute_single_window(hwnd, title, palace_y)
|
|
|
+
|
|
|
+ # 等待5秒后最小化窗口
|
|
|
+ self.signals.status.emit(f'⏳ 等待5秒后最小化窗口...')
|
|
|
+ time.sleep(5)
|
|
|
+
|
|
|
+ # 最小化窗口
|
|
|
+ self.signals.status.emit(f'📉 最小化窗口: {title}')
|
|
|
+ self.minimize_window(hwnd)
|
|
|
+
|
|
|
+ # 窗口间间隔1秒
|
|
|
+ if idx < total and not self.should_stop:
|
|
|
+ self.signals.status.emit(f'⏳ 等待1秒后处理下一个窗口...')
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.signals.status.emit(f'❌ 处理窗口失败: {str(e)}')
|
|
|
+ import traceback
|
|
|
+ self.log_text.append(traceback.format_exc())
|
|
|
+ # 出错后继续下一个窗口
|
|
|
+
|
|
|
+ if not self.should_stop:
|
|
|
+ self.signals.status.emit(f'✅ 批量执行完成!共处理 {idx if idx < total else total} 个窗口')
|
|
|
+ self.signals.finished.emit()
|
|
|
+
|
|
|
+ def execute_single_window(self, hwnd, title, palace_y):
|
|
|
+ """执行单个窗口的点击流程"""
|
|
|
+ # 1. 激活窗口
|
|
|
+ self.signals.status.emit(f'🔄 正在激活窗口: {title}')
|
|
|
+
|
|
|
+ # 显示窗口
|
|
|
+ win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
|
|
|
+ if win32gui.IsIconic(hwnd):
|
|
|
+ win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
|
|
|
+
|
|
|
+ # 激活窗口
|
|
|
+ win32gui.SetForegroundWindow(hwnd)
|
|
|
+ time.sleep(2) # 等待窗口激活
|
|
|
+
|
|
|
+ # 步骤1: 左键点击 X+63, Y+81
|
|
|
+ self.click_at_position(hwnd, 29, 41, 'left')
|
|
|
+ time.sleep(1)
|
|
|
+ self.signals.status.emit(f'📍 步骤1: 左键点击 X+63, Y+81')
|
|
|
+ self.click_at_position(hwnd, 63, 81, 'left')
|
|
|
+
|
|
|
+ # 等待2秒
|
|
|
+ self.signals.status.emit(f'⏳ 等待2秒...')
|
|
|
+ time.sleep(2)
|
|
|
+
|
|
|
+ # 步骤2: 右键点击 X+413, Y+168
|
|
|
+ self.signals.status.emit(f'📍 步骤2: 右键点击 X+413, Y+168')
|
|
|
+ self.click_at_position(hwnd, 413, 168, 'right')
|
|
|
+
|
|
|
+ # 等待6秒
|
|
|
+ self.signals.status.emit(f'⏳ 等待6秒...')
|
|
|
+ time.sleep(6)
|
|
|
+
|
|
|
+ # 处理"争夺魔龙之心"
|
|
|
+ if self.cb_molong.isChecked():
|
|
|
+ self.signals.status.emit(f'📍 检查争夺魔龙之心')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=642,
|
|
|
+ check_y=439,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=642,
|
|
|
+ click_y=439,
|
|
|
+ button='left',
|
|
|
+ check_type='equal'
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ self.signals.status.emit(f'📍 未选争夺魔龙之心,执行相反逻辑')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=642,
|
|
|
+ check_y=437,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=642,
|
|
|
+ click_y=437,
|
|
|
+ button='left',
|
|
|
+ check_type='not_equal'
|
|
|
+ )
|
|
|
+
|
|
|
+ # 等待1秒
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ # 处理"翡翠荣耀旗"
|
|
|
+ if self.cb_feicui.isChecked():
|
|
|
+ self.signals.status.emit(f'📍 检查翡翠荣耀旗')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=769,
|
|
|
+ check_y=386,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=769,
|
|
|
+ click_y=386,
|
|
|
+ button='left',
|
|
|
+ check_type='equal'
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ self.signals.status.emit(f'📍 未选翡翠荣耀旗,执行相反逻辑')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=769,
|
|
|
+ check_y=386,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=769,
|
|
|
+ click_y=386,
|
|
|
+ button='left',
|
|
|
+ check_type='not_equal'
|
|
|
+ )
|
|
|
+
|
|
|
+ # 等待1秒
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ # 处理"金岩霸者棋"
|
|
|
+ if self.cb_jinyan.isChecked():
|
|
|
+ self.signals.status.emit(f'📍 检查金岩霸者棋')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=769,
|
|
|
+ check_y=400,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=769,
|
|
|
+ click_y=400,
|
|
|
+ button='left',
|
|
|
+ check_type='equal'
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ self.signals.status.emit(f'📍 未选金岩霸者棋,执行相反逻辑')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=769,
|
|
|
+ check_y=400,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=769,
|
|
|
+ click_y=400,
|
|
|
+ button='left',
|
|
|
+ check_type='not_equal'
|
|
|
+ )
|
|
|
+
|
|
|
+ # 等待1秒
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ # 处理"炽焰战神棋"
|
|
|
+ if self.cb_chiyan.isChecked():
|
|
|
+ self.signals.status.emit(f'📍 检查炽焰战神棋')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=767,
|
|
|
+ check_y=415,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=767,
|
|
|
+ click_y=415,
|
|
|
+ button='left',
|
|
|
+ check_type='equal'
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ self.signals.status.emit(f'📍 未选炽焰战神棋,执行相反逻辑')
|
|
|
+ self.check_and_click(
|
|
|
+ hwnd=hwnd,
|
|
|
+ check_x=767,
|
|
|
+ check_y=415,
|
|
|
+ target_color="FFFFFF",
|
|
|
+ click_x=767,
|
|
|
+ click_y=415,
|
|
|
+ button='left',
|
|
|
+ check_type='not_equal'
|
|
|
+ )
|
|
|
+
|
|
|
+ # 等待1秒
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ # 最后点击 X+871, Y+266
|
|
|
+ self.signals.status.emit(f'📍 最后点击: X+871, Y+266')
|
|
|
+ self.click_at_position(hwnd, 871, 266, 'left')
|
|
|
+
|
|
|
+ time.sleep(1)
|
|
|
+ self.click_at_position(hwnd, 671, 70, 'right')
|
|
|
+
|
|
|
+ time.sleep(1)
|
|
|
+ self.click_at_position(hwnd, 714, 426, 'left')
|
|
|
+
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ # 皇宫占位点击 - 根据选择的选项
|
|
|
+ self.signals.status.emit(f'🏰 皇宫占位点击: X+716, Y+{palace_y}')
|
|
|
+ self.click_at_position(hwnd, 716, palace_y, 'left')
|
|
|
+
|
|
|
+ time.sleep(1)
|
|
|
+ self.click_at_position(hwnd, 901, 311, 'left')
|
|
|
+
|
|
|
+ time.sleep(1)
|
|
|
+ self.click_at_position(hwnd, 1133, 8, 'left')
|
|
|
+
|
|
|
+ self.signals.status.emit(f'✅ 窗口 {title} 执行完成')
|
|
|
+
|
|
|
+ def stop_auto_click(self):
|
|
|
+ """停止自动点击"""
|
|
|
+ self.should_stop = True
|
|
|
+ self.is_running = False
|
|
|
+ self.start_btn.setEnabled(True)
|
|
|
+ self.stop_btn.setEnabled(False)
|
|
|
+ self.status_label.setText('正在停止...')
|
|
|
+ self.log_text.append('⏹️ 正在停止执行...')
|
|
|
+
|
|
|
+ def update_status(self, message):
|
|
|
+ """更新状态"""
|
|
|
+ self.status_label.setText(message)
|
|
|
+ self.log_text.append(message)
|
|
|
+ # 滚动到底部
|
|
|
+ self.log_text.verticalScrollBar().setValue(
|
|
|
+ self.log_text.verticalScrollBar().maximum()
|
|
|
+ )
|
|
|
+
|
|
|
+ def update_progress(self, current, total):
|
|
|
+ """更新进度"""
|
|
|
+ self.status_label.setText(f'进度: {current}/{total}')
|
|
|
+
|
|
|
+ def on_finished(self):
|
|
|
+ """完成回调"""
|
|
|
+ self.is_running = False
|
|
|
+ self.start_btn.setEnabled(True)
|
|
|
+ self.stop_btn.setEnabled(False)
|
|
|
+ self.status_label.setText('就绪')
|
|
|
+ self.log_text.append('=' * 50)
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ app = QApplication(sys.argv)
|
|
|
+ app.setStyle('Fusion')
|
|
|
+
|
|
|
+ window = WindowManagerApp()
|
|
|
+ window.show()
|
|
|
+
|
|
|
+ sys.exit(app.exec_())
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main()
|