fix: Replace all invokeMethod calls with Qt signals for thread-safety
BUG: QMetaObject.invokeMethod with Q_ARG doesn't work properly in PyQt6 and was causing TypeError exceptions. FIX: - Added proper Qt signals at class level: * update_status_signal(str, bool, bool) * update_session_table_signal(object) * update_counters_signal() * enable_scan_button_signal(bool) - Connected all signals to slot methods in initialize() - Replaced all invokeMethod calls with signal.emit() - Thread callbacks now emit signals instead of calling invokeMethod - UI updates happen in main Qt thread via signal/slot mechanism This is the correct PyQt6 way to do cross-thread communication. All UI updates are now thread-safe and won't cause TypeErrors.
This commit is contained in:
parent
d0ccb791f7
commit
05f8c06312
|
|
@ -150,8 +150,12 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
description = "Uses core OCR and Log services"
|
description = "Uses core OCR and Log services"
|
||||||
hotkey = "ctrl+shift+s"
|
hotkey = "ctrl+shift+s"
|
||||||
|
|
||||||
# Signal for thread-safe hotkey scanning
|
# Signals for thread-safe UI updates
|
||||||
hotkey_triggered = pyqtSignal()
|
hotkey_triggered = pyqtSignal()
|
||||||
|
update_status_signal = pyqtSignal(str, bool, bool) # message, success, error
|
||||||
|
update_session_table_signal = pyqtSignal(object) # skills dict
|
||||||
|
update_counters_signal = pyqtSignal()
|
||||||
|
enable_scan_button_signal = pyqtSignal(bool)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""Setup skill scanner."""
|
"""Setup skill scanner."""
|
||||||
|
|
@ -167,8 +171,12 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
self.current_scan_session = {} # Skills collected in current multi-page scan
|
self.current_scan_session = {} # Skills collected in current multi-page scan
|
||||||
self.pages_scanned = 0
|
self.pages_scanned = 0
|
||||||
|
|
||||||
# Connect hotkey signal
|
# Connect signals
|
||||||
self.hotkey_triggered.connect(self._scan_page_for_multi)
|
self.hotkey_triggered.connect(self._scan_page_for_multi)
|
||||||
|
self.update_status_signal.connect(self._update_multi_page_status_slot)
|
||||||
|
self.update_session_table_signal.connect(self._update_session_table)
|
||||||
|
self.update_counters_signal.connect(self._update_counters_slot)
|
||||||
|
self.enable_scan_button_signal.connect(self.scan_page_btn.setEnabled)
|
||||||
|
|
||||||
# Subscribe to skill gain events from core Log service
|
# Subscribe to skill gain events from core Log service
|
||||||
try:
|
try:
|
||||||
|
|
@ -514,7 +522,7 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
|
|
||||||
ocr_service = get_ocr_service()
|
ocr_service = get_ocr_service()
|
||||||
if not ocr_service.is_available():
|
if not ocr_service.is_available():
|
||||||
self._update_multi_page_status("Error: OCR not available", error=True)
|
self.update_status_signal.emit("Error: OCR not available", False, True)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Capture and OCR
|
# Capture and OCR
|
||||||
|
|
@ -530,33 +538,22 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
|
|
||||||
self.pages_scanned += 1
|
self.pages_scanned += 1
|
||||||
|
|
||||||
# Update UI
|
# Update UI via signals (thread-safe)
|
||||||
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
|
self.update_session_table_signal.emit(self.current_scan_session)
|
||||||
QMetaObject.invokeMethod(
|
|
||||||
self, "_update_session_table",
|
|
||||||
Qt.ConnectionType.QueuedConnection,
|
|
||||||
Q_ARG(object, self.current_scan_session)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Show success with checkmark and beep
|
# Show success with checkmark and beep
|
||||||
self._update_multi_page_status(
|
self.update_status_signal.emit(
|
||||||
f"✅ Page {self.pages_scanned} scanned! {len(skills)} skills found. Click Next Page in game →",
|
f"✅ Page {self.pages_scanned} scanned! {len(skills)} skills found. Click Next Page in game →",
|
||||||
success=True
|
True, False
|
||||||
)
|
)
|
||||||
|
|
||||||
# Play beep sound
|
# Play beep sound
|
||||||
self._play_beep()
|
self._play_beep()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._update_multi_page_status(f"Error: {str(e)}", error=True)
|
self.update_status_signal.emit(f"Error: {str(e)}", False, True)
|
||||||
finally:
|
finally:
|
||||||
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
|
self.enable_scan_button_signal.emit(True)
|
||||||
QMetaObject.invokeMethod(
|
|
||||||
self.scan_page_btn,
|
|
||||||
"setEnabled",
|
|
||||||
Qt.ConnectionType.QueuedConnection,
|
|
||||||
Q_ARG(bool, True)
|
|
||||||
)
|
|
||||||
|
|
||||||
thread = Thread(target=do_scan)
|
thread = Thread(target=do_scan)
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
|
|
@ -609,38 +606,18 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
|
|
||||||
return skills
|
return skills
|
||||||
|
|
||||||
def _update_multi_page_status(self, message, success=False, error=False):
|
def _update_multi_page_status_slot(self, message, success=False, error=False):
|
||||||
"""Update multi-page status label."""
|
"""Slot for updating multi-page status (called via signal)."""
|
||||||
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
|
|
||||||
|
|
||||||
color = "#4ecdc4" if success else "#ff4757" if error else "#ff8c42"
|
color = "#4ecdc4" if success else "#ff4757" if error else "#ff8c42"
|
||||||
|
|
||||||
QMetaObject.invokeMethod(
|
self.multi_page_status.setText(message)
|
||||||
self.multi_page_status,
|
self.multi_page_status.setStyleSheet(f"color: {color}; font-size: 14px;")
|
||||||
"setText",
|
self._update_counters_slot()
|
||||||
Qt.ConnectionType.QueuedConnection,
|
|
||||||
Q_ARG(str, message)
|
|
||||||
)
|
|
||||||
QMetaObject.invokeMethod(
|
|
||||||
self.multi_page_status,
|
|
||||||
"setStyleSheet",
|
|
||||||
Qt.ConnectionType.QueuedConnection,
|
|
||||||
Q_ARG(str, f"color: {color}; font-size: 14px;")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update counters
|
def _update_counters_slot(self):
|
||||||
QMetaObject.invokeMethod(
|
"""Slot for updating counters (called via signal)."""
|
||||||
self.pages_scanned_label,
|
self.pages_scanned_label.setText(f"Pages: {self.pages_scanned}")
|
||||||
"setText",
|
self.total_skills_label.setText(f"Skills: {len(self.current_scan_session)}")
|
||||||
Qt.ConnectionType.QueuedConnection,
|
|
||||||
Q_ARG(str, f"Pages: {self.pages_scanned}")
|
|
||||||
)
|
|
||||||
QMetaObject.invokeMethod(
|
|
||||||
self.total_skills_label,
|
|
||||||
"setText",
|
|
||||||
Qt.ConnectionType.QueuedConnection,
|
|
||||||
Q_ARG(str, f"Skills: {len(self.current_scan_session)}")
|
|
||||||
)
|
|
||||||
|
|
||||||
def _play_beep(self):
|
def _play_beep(self):
|
||||||
"""Play a beep sound to notify user."""
|
"""Play a beep sound to notify user."""
|
||||||
|
|
@ -715,7 +692,7 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
self._start_auto_scan_with_hotkey()
|
self._start_auto_scan_with_hotkey()
|
||||||
elif mode == 1: # Hotkey only
|
elif mode == 1: # Hotkey only
|
||||||
self._register_hotkey()
|
self._register_hotkey()
|
||||||
self._update_multi_page_status("Hotkey F12 ready! Navigate to first page and press F12", success=True)
|
self.update_status_signal.emit("Hotkey F12 ready! Navigate to first page and press F12", True, False)
|
||||||
else: # Manual click
|
else: # Manual click
|
||||||
self._scan_page_for_multi()
|
self._scan_page_for_multi()
|
||||||
|
|
||||||
|
|
@ -729,7 +706,7 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
self._register_hotkey()
|
self._register_hotkey()
|
||||||
|
|
||||||
# Start monitoring
|
# Start monitoring
|
||||||
self._update_multi_page_status("🤖 Auto-detect started! Navigate to page 1...", success=True)
|
self.update_status_signal.emit("🤖 Auto-detect started! Navigate to page 1...", True, False)
|
||||||
|
|
||||||
# Start auto-detection timer
|
# Start auto-detection timer
|
||||||
self.auto_scan_timer = QTimer()
|
self.auto_scan_timer = QTimer()
|
||||||
|
|
@ -797,7 +774,7 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
|
|
||||||
# If page changed, trigger scan
|
# If page changed, trigger scan
|
||||||
if self.last_page_number is not None and current_page != self.last_page_number:
|
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.update_status_signal.emit(f"📄 Page change detected: {current_page}/{total_pages}", True, False)
|
||||||
self._scan_page_for_multi()
|
self._scan_page_for_multi()
|
||||||
|
|
||||||
self.last_page_number = current_page
|
self.last_page_number = current_page
|
||||||
|
|
@ -820,9 +797,9 @@ class SkillScannerPlugin(BasePlugin):
|
||||||
self.auto_scan_active = False
|
self.auto_scan_active = False
|
||||||
|
|
||||||
# Keep hotkey registered
|
# Keep hotkey registered
|
||||||
self._update_multi_page_status(
|
self.update_status_signal.emit(
|
||||||
"⚠️ Auto-detect unreliable. Use F12 hotkey to scan each page manually!",
|
"⚠️ Auto-detect unreliable. Use F12 hotkey to scan each page manually!",
|
||||||
error=True
|
False, True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Play alert sound
|
# Play alert sound
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue