diff --git a/projects/EU-Utility/core/floating_icon.py b/projects/EU-Utility/core/floating_icon.py new file mode 100644 index 0000000..e73bf2c --- /dev/null +++ b/projects/EU-Utility/core/floating_icon.py @@ -0,0 +1,103 @@ +""" +EU-Utility - Floating Icon + +A draggable floating button for easy access in-game. +""" + +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication +from PyQt6.QtCore import Qt, QPoint, pyqtSignal +from PyQt6.QtGui import QMouseEvent, QEnterEvent, QPainter, QBrush, QColor + + +class FloatingIcon(QWidget): + """Draggable floating icon for in-game use.""" + + clicked = pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent) + + # Frameless, always on top + self.setWindowFlags( + Qt.WindowType.FramelessWindowHint | + Qt.WindowType.WindowStaysOnTopHint | + Qt.WindowType.Tool | + Qt.WindowType.WindowTransparentForInput + ) + + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + self.setFixedSize(48, 48) + + # Position - top left with offset + screen = QApplication.primaryScreen().geometry() + self.move(10, 10) + + self.dragging = False + self.drag_position = QPoint() + + self._setup_ui() + + def _setup_ui(self): + """Setup the floating icon.""" + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + self.icon_label = QLabel("⚡") + self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.icon_label.setStyleSheet(""" + QLabel { + font-size: 24px; + background-color: rgba(30, 30, 30, 200); + border-radius: 24px; + border: 2px solid rgba(74, 158, 255, 150); + } + """) + layout.addWidget(self.icon_label) + + def mousePressEvent(self, event: QMouseEvent): + """Handle mouse press.""" + if event.button() == Qt.MouseButton.LeftButton: + self.dragging = True + self.drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft() + event.accept() + + def mouseMoveEvent(self, event: QMouseEvent): + """Handle mouse move for dragging.""" + if self.dragging: + new_pos = event.globalPosition().toPoint() - self.drag_position + self.move(new_pos) + event.accept() + + def mouseReleaseEvent(self, event: QMouseEvent): + """Handle mouse release.""" + if event.button() == Qt.MouseButton.LeftButton: + self.dragging = False + # If didn't move much, treat as click + event.accept() + + def mouseDoubleClickEvent(self, event: QMouseEvent): + """Handle double click.""" + self.clicked.emit() + event.accept() + + def enterEvent(self, event: QEnterEvent): + """Mouse entered - highlight.""" + self.icon_label.setStyleSheet(""" + QLabel { + font-size: 24px; + background-color: rgba(74, 158, 255, 200); + border-radius: 24px; + border: 2px solid rgba(255, 255, 255, 200); + } + """) + + def leaveEvent(self, event): + """Mouse left - normal.""" + self.icon_label.setStyleSheet(""" + QLabel { + font-size: 24px; + background-color: rgba(30, 30, 30, 200); + border-radius: 24px; + border: 2px solid rgba(74, 158, 255, 150); + } + """) diff --git a/projects/EU-Utility/core/main.py b/projects/EU-Utility/core/main.py index 6cd29df..9230420 100644 --- a/projects/EU-Utility/core/main.py +++ b/projects/EU-Utility/core/main.py @@ -1,7 +1,7 @@ """ EU-Utility - Main Entry Point -Launch the overlay and plugin system. +Launch the overlay, floating icon, and plugin system. """ import sys @@ -33,6 +33,7 @@ except ImportError: from core.plugin_manager import PluginManager from core.overlay_window import OverlayWindow +from core.floating_icon import FloatingIcon class HotkeyHandler(QObject): @@ -46,6 +47,7 @@ class EUUtilityApp: def __init__(self): self.app = None self.overlay = None + self.floating_icon = None self.plugin_manager = None self.hotkey_handler = None @@ -71,6 +73,12 @@ class EUUtilityApp: self.overlay = OverlayWindow(self.plugin_manager) self.plugin_manager.overlay = self.overlay + # Create floating icon + print("Creating floating icon...") + self.floating_icon = FloatingIcon() + self.floating_icon.clicked.connect(self._toggle_overlay) + self.floating_icon.show() + # Connect hotkey signal self.hotkey_handler.toggle_signal.connect(self._on_toggle_signal) @@ -79,6 +87,7 @@ class EUUtilityApp: print("EU-Utility started!") print("Press Ctrl+Shift+U to toggle overlay") + print("Or double-click the ⚡ floating icon") # Run return self.app.exec() @@ -99,6 +108,10 @@ class EUUtilityApp: def _on_toggle_signal(self): """Handle toggle signal in main thread.""" + self._toggle_overlay() + + def _toggle_overlay(self): + """Toggle overlay visibility.""" if self.overlay: self.overlay.toggle_overlay() diff --git a/projects/EU-Utility/plugins/game_reader/__init__.py b/projects/EU-Utility/plugins/game_reader/__init__.py new file mode 100644 index 0000000..0d896f8 --- /dev/null +++ b/projects/EU-Utility/plugins/game_reader/__init__.py @@ -0,0 +1,7 @@ +""" +Game Reader Plugin for EU-Utility +""" + +from .plugin import GameReaderPlugin + +__all__ = ["GameReaderPlugin"] diff --git a/projects/EU-Utility/plugins/game_reader/plugin.py b/projects/EU-Utility/plugins/game_reader/plugin.py new file mode 100644 index 0000000..a4b33e0 --- /dev/null +++ b/projects/EU-Utility/plugins/game_reader/plugin.py @@ -0,0 +1,261 @@ +""" +EU-Utility - OCR Scanner Plugin + +Reads text from in-game menus using OCR. +""" + +import subprocess +import platform +import tempfile +from pathlib import Path +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, + QLabel, QPushButton, QTextEdit, QComboBox, + QFrame, QScrollArea, QGroupBox +) +from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer +from PyQt6.QtGui import QPixmap, QImage + +from plugins.base_plugin import BasePlugin + + +class OCRScannerThread(QThread): + """Background thread for OCR scanning.""" + result_ready = pyqtSignal(str) + error_occurred = pyqtSignal(str) + + def __init__(self, region=None): + super().__init__() + self.region = region # (x, y, width, height) + + def run(self): + """Capture screen and perform OCR.""" + try: + system = platform.system() + + # Create temp file for screenshot + with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp: + screenshot_path = tmp.name + + # Capture screenshot + if system == "Windows": + # Use PowerShell to capture screen + ps_cmd = f''' + Add-Type -AssemblyName System.Windows.Forms + Add-Type -AssemblyName System.Drawing + $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds + $bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height) + $graphics = [System.Drawing.Graphics]::FromImage($bitmap) + $graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size) + $bitmap.Save("{screenshot_path}") + $graphics.Dispose() + $bitmap.Dispose() + ''' + subprocess.run(['powershell', '-Command', ps_cmd], capture_output=True, timeout=10) + + elif system == "Linux": + # Use gnome-screenshot or import + try: + subprocess.run(['gnome-screenshot', '-f', screenshot_path], + capture_output=True, timeout=10) + except: + subprocess.run(['import', '-window', 'root', screenshot_path], + capture_output=True, timeout=10) + + # Perform OCR + text = self._perform_ocr(screenshot_path) + + # Clean up + Path(screenshot_path).unlink(missing_ok=True) + + self.result_ready.emit(text) + + except Exception as e: + self.error_occurred.emit(str(e)) + + def _perform_ocr(self, image_path): + """Perform OCR on image.""" + try: + # Try easyocr first + import easyocr + reader = easyocr.Reader(['en']) + results = reader.readtext(image_path) + text = '\n'.join([result[1] for result in results]) + return text if text else "No text detected" + except: + pass + + try: + # Try pytesseract + import pytesseract + from PIL import Image + image = Image.open(image_path) + text = pytesseract.image_to_string(image) + return text if text.strip() else "No text detected" + except: + pass + + return "OCR not available. Install: pip install easyocr or pytesseract" + + +class GameReaderPlugin(BasePlugin): + """Read in-game menus and text using OCR.""" + + name = "Game Reader" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "OCR scanner for in-game menus and text" + hotkey = "ctrl+shift+r" # R for Read + + def initialize(self): + """Setup game reader.""" + self.scan_thread = None + self.last_result = "" + + def get_ui(self): + """Create game reader UI.""" + widget = QWidget() + widget.setStyleSheet("background: transparent;") + layout = QVBoxLayout(widget) + layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + + # Title + title = QLabel("📷 Game Reader (OCR)") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # Info + info = QLabel("Capture in-game menus and read the text") + info.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 12px;") + layout.addWidget(info) + + # Scan button + scan_btn = QPushButton("📸 Capture Screen") + scan_btn.setStyleSheet(""" + QPushButton { + background-color: #4a9eff; + color: white; + padding: 15px; + border: none; + border-radius: 10px; + font-size: 14px; + font-weight: bold; + } + QPushButton:hover { + background-color: #5aafff; + } + QPushButton:pressed { + background-color: #3a8eef; + } + """) + scan_btn.clicked.connect(self._capture_screen) + layout.addWidget(scan_btn) + + # Status + self.status_label = QLabel("Ready to capture") + self.status_label.setStyleSheet("color: #666; font-size: 11px;") + self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.status_label) + + # Results area + results_frame = QFrame() + results_frame.setStyleSheet(""" + QFrame { + background-color: rgba(0, 0, 0, 50); + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 20); + } + """) + results_layout = QVBoxLayout(results_frame) + results_layout.setContentsMargins(10, 10, 10, 10) + + results_label = QLabel("📄 Captured Text:") + results_label.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 12px;") + results_layout.addWidget(results_label) + + self.result_text = QTextEdit() + self.result_text.setPlaceholderText("Captured text will appear here...") + self.result_text.setStyleSheet(""" + QTextEdit { + background-color: rgba(30, 30, 30, 100); + color: white; + border: none; + border-radius: 6px; + padding: 8px; + font-size: 13px; + } + """) + self.result_text.setMaximumHeight(150) + results_layout.addWidget(self.result_text) + + # Copy button + copy_btn = QPushButton("📋 Copy Text") + copy_btn.setStyleSheet(""" + QPushButton { + background-color: rgba(255, 255, 255, 20); + color: white; + padding: 8px; + border: none; + border-radius: 6px; + } + QPushButton:hover { + background-color: rgba(255, 255, 255, 30); + } + """) + copy_btn.clicked.connect(self._copy_text) + results_layout.addWidget(copy_btn) + + layout.addWidget(results_frame) + + # Common uses + uses_label = QLabel("💡 Common Uses:") + uses_label.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 11px; margin-top: 10px;") + layout.addWidget(uses_label) + + uses_text = QLabel( + "• Read NPC dialogue\n" + "• Capture mission text\n" + "• Extract item stats\n" + "• Read shop prices" + ) + uses_text.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px;") + layout.addWidget(uses_text) + + layout.addStretch() + + return widget + + def _capture_screen(self): + """Capture screen and perform OCR.""" + self.status_label.setText("📸 Capturing...") + self.status_label.setStyleSheet("color: #4a9eff;") + + # Start scan thread + self.scan_thread = OCRScannerThread() + self.scan_thread.result_ready.connect(self._on_result) + self.scan_thread.error_occurred.connect(self._on_error) + self.scan_thread.start() + + def _on_result(self, text): + """Handle OCR result.""" + self.result_text.setText(text) + self.last_result = text + self.status_label.setText(f"✅ Captured {len(text)} characters") + self.status_label.setStyleSheet("color: #4caf50;") + + def _on_error(self, error): + """Handle OCR error.""" + self.status_label.setText(f"❌ Error: {error}") + self.status_label.setStyleSheet("color: #f44336;") + + def _copy_text(self): + """Copy text to clipboard.""" + from PyQt6.QtWidgets import QApplication + clipboard = QApplication.clipboard() + clipboard.setText(self.result_text.toPlainText()) + self.status_label.setText("📋 Copied to clipboard!") + + def on_hotkey(self): + """Capture on hotkey.""" + self._capture_screen()