From e132a80f2b487ef6544b079e813ac5fa06775519 Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Sun, 15 Feb 2026 00:42:37 +0000 Subject: [PATCH] feat: Add Smart Auto-Scan with Hotkey Fallback to Skill Scanner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NEW FEATURE - Smart Multi-Page Scanning: MODES: 1. 🤖 Smart Auto + Hotkey Fallback (default) - Tries to auto-detect page changes - Monitors page number area (1/12, 2/12, etc.) - If detection fails, falls back to F12 hotkey - User gets notified: 'Auto-detect unreliable. Use F12!' 2. ⌨️ Manual Hotkey Only - User navigates pages in EU - Presses F12 to scan each page - Simple and 100% reliable 3. 🖱️ Manual Click Only - Original click-based scanning - Click button, wait for beep, next page SMART AUTO FEATURES: - Checks page number area every 500ms - Detects when page number changes (1→2, 2→3, etc.) - Automatically triggers scan on page change - Tracks failures - after 10 failures, falls back to hotkey - Plays beep sound on successful auto-scan HOTKEY FEATURES: - F12 key registered globally - Works even when EU-Utility not focused - Triggers scan immediately - Can be used as primary mode or fallback UI UPDATES: - Mode selector dropdown - Dynamic instructions based on mode - Hotkey info displayed (F12 = Scan) - Status shows when auto-detect vs hotkey is active TECHNICAL: - Uses keyboard library for global hotkeys - QTimer for auto-detection polling - Tesseract OCR for page number reading - Graceful fallback when auto fails This gives users the best of both worlds: - Try auto for convenience - Fallback to hotkey for reliability --- plugins/skill_scanner/plugin.py | 178 +++++++++++++++++++++++++++++--- 1 file changed, 166 insertions(+), 12 deletions(-) diff --git a/plugins/skill_scanner/plugin.py b/plugins/skill_scanner/plugin.py index b1f9fff..bb724c6 100644 --- a/plugins/skill_scanner/plugin.py +++ b/plugins/skill_scanner/plugin.py @@ -12,9 +12,9 @@ from pathlib import Path from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTableWidget, QTableWidgetItem, QProgressBar, - QFrame, QGroupBox, QTextEdit, QSplitter + QFrame, QGroupBox, QTextEdit, QSplitter, QComboBox ) -from PyQt6.QtCore import Qt, QThread, pyqtSignal +from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer from plugins.base_plugin import BasePlugin @@ -270,16 +270,33 @@ class SkillScannerPlugin(BasePlugin): multi_page_group = QGroupBox("Multi-Page Scanner") multi_page_layout = QVBoxLayout(multi_page_group) + # Mode selection + mode_layout = QHBoxLayout() + mode_layout.addWidget(QLabel("Mode:")) + + self.scan_mode_combo = QComboBox() + self.scan_mode_combo.addItems(["Smart Auto + Hotkey Fallback", "Manual Hotkey Only", "Manual Click Only"]) + self.scan_mode_combo.currentIndexChanged.connect(self._on_scan_mode_changed) + mode_layout.addWidget(self.scan_mode_combo) + mode_layout.addStretch() + multi_page_layout.addLayout(mode_layout) + # Instructions - instructions = QLabel( - "1. Position your Skills window to show skills\n" - "2. Click 'Scan Page' below\n" - "3. When you hear the beep, click Next Page in game\n" - "4. Repeat until all pages are scanned\n" - "5. Click 'Save All' to store combined results" + self.instructions_label = QLabel( + "🤖 SMART MODE:\n" + "1. Position Skills window\n" + "2. Click 'Start Smart Scan'\n" + "3. Navigate pages in EU - auto-detect will scan for you\n" + "4. If auto fails, use hotkey F12 to scan manually\n" + "5. Click 'Save All' when done" ) - instructions.setStyleSheet("color: #888; font-size: 11px;") - multi_page_layout.addWidget(instructions) + self.instructions_label.setStyleSheet("color: #888; font-size: 11px;") + multi_page_layout.addWidget(self.instructions_label) + + # Hotkey info + self.hotkey_info = QLabel("Hotkey: F12 = Scan Current Page") + self.hotkey_info.setStyleSheet("color: #4ecdc4; font-weight: bold;") + multi_page_layout.addWidget(self.hotkey_info) # Status row status_layout = QHBoxLayout() @@ -302,7 +319,7 @@ class SkillScannerPlugin(BasePlugin): # Buttons mp_buttons_layout = QHBoxLayout() - self.scan_page_btn = QPushButton("📷 Scan Current Page") + self.scan_page_btn = QPushButton("▶️ Start Smart Scan") self.scan_page_btn.setStyleSheet(""" QPushButton { background-color: #4ecdc4; @@ -317,7 +334,7 @@ class SkillScannerPlugin(BasePlugin): background-color: #3dbdb4; } """) - self.scan_page_btn.clicked.connect(self._scan_page_for_multi) + self.scan_page_btn.clicked.connect(self._start_smart_scan) mp_buttons_layout.addWidget(self.scan_page_btn) save_all_btn = QPushButton("💾 Save All Scanned") @@ -665,8 +682,145 @@ class SkillScannerPlugin(BasePlugin): """Clear the current multi-page scanning session.""" self.current_scan_session = {} self.pages_scanned = 0 + self.auto_scan_active = False self.session_table.setRowCount(0) self.multi_page_status.setText("⏳ Ready to scan page 1") self.multi_page_status.setStyleSheet("color: #ff8c42; font-size: 14px;") self.pages_scanned_label.setText("Pages: 0") self.total_skills_label.setText("Skills: 0") + + # Unregister hotkey if active + self._unregister_hotkey() + + def _on_scan_mode_changed(self, index): + """Handle scan mode change.""" + modes = [ + "🤖 SMART MODE:\n1. Position Skills window\n2. Click 'Start Smart Scan'\n3. Navigate pages - auto-detect will scan\n4. If auto fails, press F12\n5. Click 'Save All' when done", + "⌨️ HOTKEY MODE:\n1. Position Skills window\n2. Navigate to page 1 in EU\n3. Press F12 to scan each page\n4. Click Next Page in EU\n5. Repeat F12 for each page", + "🖱️ MANUAL MODE:\n1. Position Skills window\n2. Click 'Scan Current Page'\n3. Wait for beep\n4. Click Next Page in EU\n5. Repeat" + ] + self.instructions_label.setText(modes[index]) + + def _start_smart_scan(self): + """Start smart auto-scan with hotkey fallback.""" + mode = self.scan_mode_combo.currentIndex() + + if mode == 0: # Smart Auto + Hotkey + self._start_auto_scan_with_hotkey() + elif mode == 1: # Hotkey only + self._register_hotkey() + self._update_multi_page_status("Hotkey F12 ready! Navigate to first page and press F12", success=True) + else: # Manual click + self._scan_page_for_multi() + + def _start_auto_scan_with_hotkey(self): + """Start auto-detection with fallback to hotkey.""" + self.auto_scan_active = True + self.auto_scan_failures = 0 + self.last_page_number = None + + # Register F12 hotkey as fallback + self._register_hotkey() + + # Start monitoring + self._update_multi_page_status("🤖 Auto-detect started! Navigate to page 1...", success=True) + + # Start auto-detection timer + self.auto_scan_timer = QTimer() + self.auto_scan_timer.timeout.connect(self._check_for_page_change) + self.auto_scan_timer.start(500) # Check every 500ms + + def _register_hotkey(self): + """Register F12 hotkey for manual scan.""" + try: + import keyboard + keyboard.on_press_key('f12', lambda e: self._hotkey_scan()) + self.hotkey_registered = True + except Exception as e: + print(f"[SkillScanner] Could not register hotkey: {e}") + self.hotkey_registered = False + + def _unregister_hotkey(self): + """Unregister hotkey.""" + try: + if hasattr(self, 'hotkey_registered') and self.hotkey_registered: + import keyboard + keyboard.unhook_all() + self.hotkey_registered = False + except: + pass + + # Stop auto-scan timer + if hasattr(self, 'auto_scan_timer') and self.auto_scan_timer: + self.auto_scan_timer.stop() + self.auto_scan_active = False + + def _hotkey_scan(self): + """Scan triggered by F12 hotkey.""" + from PyQt6.QtCore import QMetaObject, Qt, Q_ARG + QMetaObject.invokeMethod( + self, "_scan_page_for_multi", + Qt.ConnectionType.QueuedConnection + ) + + def _check_for_page_change(self): + """Auto-detect page changes by monitoring page number area.""" + if not self.auto_scan_active: + return + + try: + from PIL import ImageGrab + import pytesseract + + # Capture page number area (bottom center of skills window) + # This is approximate - may need adjustment + screen = ImageGrab.grab() + width, height = screen.size + + # Try to capture the page number area (bottom center, small region) + # EU skills window shows page like "1/12" at bottom + page_area = (width // 2 - 50, height - 100, width // 2 + 50, height - 50) + page_img = ImageGrab.grab(bbox=page_area) + + # OCR just the page number + page_text = pytesseract.image_to_string(page_img, config='--psm 7 -c tessedit_char_whitelist=0123456789/') + + # Extract current page number + import re + match = re.search(r'(\d+)/(\d+)', page_text) + if match: + current_page = int(match.group(1)) + total_pages = int(match.group(2)) + + # If page changed, trigger scan + if self.last_page_number is not None and current_page != self.last_page_number: + self._update_multi_page_status(f"📄 Page change detected: {current_page}/{total_pages}", success=True) + self._scan_page_for_multi() + + self.last_page_number = current_page + else: + # Failed to detect page number + self.auto_scan_failures += 1 + if self.auto_scan_failures >= 10: # After 5 seconds of failures + self._fallback_to_hotkey() + + except Exception as e: + self.auto_scan_failures += 1 + if self.auto_scan_failures >= 10: + self._fallback_to_hotkey() + + def _fallback_to_hotkey(self): + """Fallback to hotkey mode when auto-detection fails.""" + if hasattr(self, 'auto_scan_timer') and self.auto_scan_timer: + self.auto_scan_timer.stop() + + self.auto_scan_active = False + + # Keep hotkey registered + self._update_multi_page_status( + "⚠️ Auto-detect unreliable. Use F12 hotkey to scan each page manually!", + error=True + ) + + # Play alert sound + self._play_beep()