feat: All plugins disabled by default with enable/disable UI
CHANGES: - All plugins now disabled by default (empty enabled list) - Settings > Plugins tab shows all discovered plugins - Checkboxes to enable/disable individual plugins - Enable All / Disable All buttons - Changes take effect immediately (no restart needed for most) - Plugin state saved to config/plugins.json PLUGIN MANAGER: - is_plugin_enabled() - must be explicitly enabled - enable_plugin() - loads and initializes plugin - disable_plugin() - unloads plugin - get_all_discovered_plugins() - returns all available plugins
This commit is contained in:
parent
72c3c132ca
commit
bcd4574b7f
|
|
@ -1,67 +1,60 @@
|
|||
# 2026-02-13 - EU-Utility Development Session
|
||||
# 2026-02-13 - EU-Utility Major Update
|
||||
|
||||
## Bug Fixes
|
||||
- **Fixed**: `AttributeError: 'dict' object has no attribute 'lower'` in universal_search plugin
|
||||
- Location: `plugins/universal_search/plugin.py` line ~501
|
||||
- Fix: Changed `'click' in item.lower()` to `'click' in item`
|
||||
- Committed: `5e08f56`
|
||||
## UI Fixes Applied
|
||||
- **Removed decorative lines** from header (user complaint about "weird lines")
|
||||
- **Added scroll area** to sidebar - prevents expanding out of screen
|
||||
- **Added drag functionality** - click and drag header to move window
|
||||
- **Removed ALL emojis** from all plugins (replaced with text only)
|
||||
|
||||
## UI Redesign - Spotlight Style
|
||||
- **Complete redesign** of overlay window to match macOS Spotlight aesthetic
|
||||
- Features:
|
||||
- Frosted glass effect with `rgba(40, 40, 40, 180)` background
|
||||
- 20px rounded corners
|
||||
- Drop shadow for depth
|
||||
- Header bar with search icon
|
||||
- Circular plugin icon buttons at bottom (🔍 🧮 🎵 🌐)
|
||||
- Transparent content area for plugins
|
||||
- Committed: `8dbbf4d`
|
||||
## New Features Implemented
|
||||
|
||||
## Game Integration Features
|
||||
### 1. Resizable Window
|
||||
- Removed `FramelessWindowHint`
|
||||
- Window now fully resizable with minimum size 600x400
|
||||
- Shows in taskbar properly
|
||||
|
||||
### Floating Icon
|
||||
- Draggable floating button (⚡) for in-game access
|
||||
- Positioned at top-left (250, 10) near game UI icons
|
||||
- EU-styled: dark blue/gray `rgba(20, 25, 35, 220)` with subtle border
|
||||
- Single-click opens overlay, drag to reposition
|
||||
- Hover effect with brighter border
|
||||
- Blue glow effect using QGraphicsDropShadowEffect
|
||||
- Committed: `b007927`
|
||||
### 2. Plugin Index Bug Fix
|
||||
- **Fixed**: Clicking Spotify opened TP Runner instead
|
||||
- Root cause: Plugin index tracking was incorrect
|
||||
- Solution: Track buttons in list, store correct index in UserRole data
|
||||
|
||||
### Game Reader Plugin (OCR)
|
||||
- New plugin for reading in-game menus and text
|
||||
- Hotkey: `Ctrl+Shift+R`
|
||||
- Features:
|
||||
- Screen capture with OCR (EasyOCR or Tesseract)
|
||||
- Extract text from NPC dialogue, missions, prices
|
||||
- Copy captured text to clipboard
|
||||
- Status feedback (capturing/error/success)
|
||||
- Location: `plugins/game_reader/plugin.py`
|
||||
- Dependencies: `pip install easyocr` or `pip install pytesseract`
|
||||
- Committed: `d74de07`
|
||||
### 3. Skill Scanner v2.0 (Complete Rewrite)
|
||||
**OCR Features:**
|
||||
- Screenshot capture of skills window
|
||||
- EasyOCR/Pytesseract fallback
|
||||
- Parses skill names, ranks, points
|
||||
|
||||
## User Feedback - Game Screenshots
|
||||
User shared screenshots showing ideal use cases:
|
||||
1. **Trade Terminal** - Price checking, category browsing
|
||||
2. **Inventory** - Item management, PED balance (26.02 PED), weight tracking
|
||||
3. **Auction** - Price tracking, markup analysis, bid monitoring
|
||||
**Log Watching:**
|
||||
- Real-time chat.log monitoring
|
||||
- Automatic skill gain detection
|
||||
- Patterns: "Aim has improved by 5.2 points", "You gained 10 points in Rifle"
|
||||
- Automatically adds gains to total values
|
||||
- Real-time gain display in UI
|
||||
|
||||
## All Hotkeys
|
||||
| Hotkey | Action |
|
||||
|--------|--------|
|
||||
| `Ctrl+Shift+U` | Toggle overlay |
|
||||
| `Ctrl+Shift+F` | Universal Search |
|
||||
| `Ctrl+Shift+C` | Calculator |
|
||||
| `Ctrl+Shift+M` | Spotify |
|
||||
| `Ctrl+Shift+R` | Game Reader (OCR) |
|
||||
### 4. Profession Scanner (NEW PLUGIN)
|
||||
- OCR scan of professions window
|
||||
- Tracks profession ranks (Elite, Champion, etc.)
|
||||
- Progress percentage with visual bars
|
||||
- 21st plugin added to EU-Utility
|
||||
|
||||
## Repository
|
||||
- EU-Utility: `https://git.lemonlink.eu/impulsivefps/EU-Utility`
|
||||
- Local: `/home/impulsivefps/.openclaw/workspace/projects/EU-Utility/`
|
||||
- Run: `python -m core.main` (from projects/EU-Utility/)
|
||||
### 5. Customizable Dashboard
|
||||
- Click "Customize" to select widgets
|
||||
- Available widgets:
|
||||
- PED Balance
|
||||
- Skills Tracked
|
||||
- Inventory Items
|
||||
- Current DPP
|
||||
- Today's Skill Gains
|
||||
- Professions Count
|
||||
- Active Missions
|
||||
- Codex Progress
|
||||
- Globals/HOFs
|
||||
- Session Time
|
||||
- Auto-refresh every 5 seconds
|
||||
- Reads data from all plugin JSON files
|
||||
|
||||
## Next Ideas (From User)
|
||||
- Auction Price Tracker - capture listings, track history, price alerts
|
||||
- Inventory Value Calculator - total TT value, market price comparison
|
||||
- Auto-Capture Mode - detect window type, extract relevant data automatically
|
||||
- Better OCR region selection for specific windows
|
||||
## Commits Made
|
||||
- `5b127cf` - UI fixes (drag, scroll, no emojis)
|
||||
- `72c3c13` - Resizable window, OCR scanners, customizable dashboard
|
||||
|
||||
## Total Plugins: 21
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ class PluginManager:
|
|||
return json.loads(config_path.read_text())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return {"enabled": [], "disabled": [], "settings": {}}
|
||||
# Default: no plugins enabled - all disabled by default
|
||||
return {"enabled": [], "settings": {}}
|
||||
|
||||
def save_config(self) -> None:
|
||||
"""Save plugin configuration."""
|
||||
|
|
@ -51,6 +52,40 @@ class PluginManager:
|
|||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_path.write_text(json.dumps(self.config, indent=2))
|
||||
|
||||
def is_plugin_enabled(self, plugin_id: str) -> bool:
|
||||
"""Check if a plugin is enabled."""
|
||||
# Must be explicitly enabled - default is disabled
|
||||
return plugin_id in self.config.get("enabled", [])
|
||||
|
||||
def enable_plugin(self, plugin_id: str) -> bool:
|
||||
"""Enable a plugin and load it."""
|
||||
if plugin_id in self.config.get("enabled", []):
|
||||
return True
|
||||
|
||||
# Add to enabled list
|
||||
if "enabled" not in self.config:
|
||||
self.config["enabled"] = []
|
||||
self.config["enabled"].append(plugin_id)
|
||||
self.save_config()
|
||||
|
||||
# Load the plugin
|
||||
plugin_class = self.plugin_classes.get(plugin_id)
|
||||
if plugin_class:
|
||||
return self.load_plugin(plugin_class)
|
||||
return False
|
||||
|
||||
def disable_plugin(self, plugin_id: str) -> bool:
|
||||
"""Disable a plugin and unload it."""
|
||||
if "enabled" in self.config and plugin_id in self.config["enabled"]:
|
||||
self.config["enabled"].remove(plugin_id)
|
||||
self.save_config()
|
||||
|
||||
# Unload if loaded
|
||||
if plugin_id in self.plugins:
|
||||
self.unload_plugin(plugin_id)
|
||||
return True
|
||||
return False
|
||||
|
||||
def discover_plugins(self) -> List[Type[BasePlugin]]:
|
||||
"""Discover all available plugin classes with error handling."""
|
||||
discovered = []
|
||||
|
|
@ -153,18 +188,23 @@ class PluginManager:
|
|||
return False
|
||||
|
||||
def load_all_plugins(self) -> None:
|
||||
"""Discover and load all available plugins."""
|
||||
"""Load only enabled plugins."""
|
||||
discovered = self.discover_plugins()
|
||||
|
||||
for plugin_class in discovered:
|
||||
plugin_id = f"{plugin_class.__module__}.{plugin_class.__name__}"
|
||||
|
||||
# Check if disabled
|
||||
if plugin_id in self.config.get("disabled", []):
|
||||
print(f"Skipping disabled plugin: {plugin_class.name}")
|
||||
continue
|
||||
# Only load if explicitly enabled
|
||||
if self.is_plugin_enabled(plugin_id):
|
||||
self.load_plugin(plugin_class)
|
||||
else:
|
||||
# Just store class reference but don't load
|
||||
self.plugin_classes[plugin_id] = plugin_class
|
||||
print(f"[PluginManager] Plugin available (disabled): {plugin_class.name}")
|
||||
|
||||
self.load_plugin(plugin_class)
|
||||
def get_all_discovered_plugins(self) -> Dict[str, type]:
|
||||
"""Get all discovered plugin classes (including disabled)."""
|
||||
return self.plugin_classes.copy()
|
||||
|
||||
def get_plugin(self, plugin_id: str) -> Optional[BasePlugin]:
|
||||
"""Get a loaded plugin by ID."""
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ from PyQt6.QtWidgets import (
|
|||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QCheckBox, QLineEdit, QComboBox,
|
||||
QSlider, QTabWidget, QGroupBox, QListWidget,
|
||||
QListWidgetItem, QFrame, QFileDialog
|
||||
QListWidgetItem, QFrame, QFileDialog, QScrollArea
|
||||
)
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
|
||||
from core.settings import get_settings
|
||||
from plugins.base_plugin import BasePlugin
|
||||
|
|
@ -38,7 +38,7 @@ class SettingsPlugin(BasePlugin):
|
|||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Title
|
||||
title = QLabel("⚙️ Settings")
|
||||
title = QLabel("Settings")
|
||||
title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;")
|
||||
layout.addWidget(title)
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ class SettingsPlugin(BasePlugin):
|
|||
# Save/Reset buttons
|
||||
btn_layout = QHBoxLayout()
|
||||
|
||||
save_btn = QPushButton("💾 Save Settings")
|
||||
save_btn = QPushButton("Save Settings")
|
||||
save_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #4caf50;
|
||||
|
|
@ -98,7 +98,7 @@ class SettingsPlugin(BasePlugin):
|
|||
save_btn.clicked.connect(self._save_settings)
|
||||
btn_layout.addWidget(save_btn)
|
||||
|
||||
reset_btn = QPushButton("↺ Reset to Default")
|
||||
reset_btn = QPushButton("Reset to Default")
|
||||
reset_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: rgba(255,255,255,20);
|
||||
|
|
@ -184,68 +184,116 @@ class SettingsPlugin(BasePlugin):
|
|||
return tab
|
||||
|
||||
def _create_plugins_tab(self):
|
||||
"""Create plugins management tab."""
|
||||
"""Create plugins management tab - enable/disable plugins."""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
layout.setSpacing(15)
|
||||
|
||||
# Installed plugins
|
||||
plugins_group = QGroupBox("Installed Plugins")
|
||||
plugins_group.setStyleSheet(self._group_style())
|
||||
plugins_layout = QVBoxLayout(plugins_group)
|
||||
# Info label
|
||||
info = QLabel("Check plugins to enable them. Uncheck to disable. Changes take effect immediately.")
|
||||
info.setStyleSheet("color: rgba(255,255,255,150);")
|
||||
layout.addWidget(info)
|
||||
|
||||
self.plugins_list = QListWidget()
|
||||
self.plugins_list.setStyleSheet("""
|
||||
QListWidget {
|
||||
background-color: rgba(30, 35, 45, 200);
|
||||
color: white;
|
||||
border: 1px solid rgba(100, 110, 130, 80);
|
||||
border-radius: 4px;
|
||||
}
|
||||
QListWidget::item {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid rgba(100, 110, 130, 40);
|
||||
}
|
||||
QListWidget::item:selected {
|
||||
background-color: rgba(255, 140, 66, 100);
|
||||
}
|
||||
""")
|
||||
# Scroll area for plugin list
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
||||
scroll.setStyleSheet("background: transparent; border: none;")
|
||||
|
||||
# Add sample plugins
|
||||
plugins = [
|
||||
("Universal Search", True),
|
||||
("Calculator", True),
|
||||
("Spotify", True),
|
||||
("Skill Scanner", True),
|
||||
("Loot Tracker", True),
|
||||
("Mining Helper", False),
|
||||
("Chat Logger", True),
|
||||
]
|
||||
scroll_content = QWidget()
|
||||
plugins_layout = QVBoxLayout(scroll_content)
|
||||
plugins_layout.setSpacing(8)
|
||||
plugins_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
for name, enabled in plugins:
|
||||
item = QListWidgetItem(f"{'✓' if enabled else '✗'} {name}")
|
||||
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||
item.setCheckState(Qt.CheckState.Checked if enabled else Qt.CheckState.Unchecked)
|
||||
self.plugins_list.addItem(item)
|
||||
self.plugin_checkboxes = {}
|
||||
|
||||
plugins_layout.addWidget(self.plugins_list)
|
||||
# Get all discovered plugins from plugin manager
|
||||
if hasattr(self.overlay, 'plugin_manager'):
|
||||
plugin_manager = self.overlay.plugin_manager
|
||||
all_plugins = plugin_manager.get_all_discovered_plugins()
|
||||
|
||||
# Plugin store button
|
||||
store_btn = QPushButton("🛒 Open Plugin Store")
|
||||
store_btn.setStyleSheet("""
|
||||
# Sort by name
|
||||
sorted_plugins = sorted(all_plugins.items(), key=lambda x: x[1].name)
|
||||
|
||||
for plugin_id, plugin_class in sorted_plugins:
|
||||
row = QHBoxLayout()
|
||||
|
||||
# Checkbox
|
||||
cb = QCheckBox(plugin_class.name)
|
||||
cb.setChecked(plugin_manager.is_plugin_enabled(plugin_id))
|
||||
cb.setStyleSheet("""
|
||||
QCheckBox {
|
||||
color: white;
|
||||
spacing: 8px;
|
||||
}
|
||||
QCheckBox::indicator {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
""")
|
||||
# Connect to enable/disable
|
||||
cb.stateChanged.connect(
|
||||
lambda state, pid=plugin_id: self._toggle_plugin(pid, state == Qt.CheckState.Checked.value)
|
||||
)
|
||||
self.plugin_checkboxes[plugin_id] = cb
|
||||
row.addWidget(cb)
|
||||
|
||||
# Version
|
||||
version_label = QLabel(f"v{plugin_class.version}")
|
||||
version_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
|
||||
row.addWidget(version_label)
|
||||
|
||||
# Description
|
||||
desc_label = QLabel(f"- {plugin_class.description}")
|
||||
desc_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
|
||||
desc_label.setWordWrap(True)
|
||||
row.addWidget(desc_label, 1)
|
||||
|
||||
row.addStretch()
|
||||
plugins_layout.addLayout(row)
|
||||
|
||||
# Separator
|
||||
sep = QFrame()
|
||||
sep.setFrameShape(QFrame.Shape.HLine)
|
||||
sep.setStyleSheet("background-color: rgba(100, 110, 130, 40);")
|
||||
sep.setFixedHeight(1)
|
||||
plugins_layout.addWidget(sep)
|
||||
|
||||
plugins_layout.addStretch()
|
||||
scroll.setWidget(scroll_content)
|
||||
layout.addWidget(scroll)
|
||||
|
||||
# Buttons
|
||||
btn_layout = QHBoxLayout()
|
||||
|
||||
enable_all_btn = QPushButton("Enable All")
|
||||
enable_all_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #ff8c42;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
plugins_layout.addWidget(store_btn)
|
||||
enable_all_btn.clicked.connect(self._enable_all_plugins)
|
||||
btn_layout.addWidget(enable_all_btn)
|
||||
|
||||
layout.addWidget(plugins_group)
|
||||
layout.addStretch()
|
||||
disable_all_btn = QPushButton("Disable All")
|
||||
disable_all_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: rgba(255,255,255,20);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
disable_all_btn.clicked.connect(self._disable_all_plugins)
|
||||
btn_layout.addWidget(disable_all_btn)
|
||||
|
||||
btn_layout.addStretch()
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
return tab
|
||||
|
||||
|
|
@ -470,3 +518,44 @@ class SettingsPlugin(BasePlugin):
|
|||
def _clear_data(self):
|
||||
"""Clear all data."""
|
||||
pass
|
||||
|
||||
def _toggle_plugin(self, plugin_id: str, enable: bool):
|
||||
"""Enable or disable a plugin."""
|
||||
if not hasattr(self.overlay, 'plugin_manager'):
|
||||
return
|
||||
|
||||
plugin_manager = self.overlay.plugin_manager
|
||||
|
||||
if enable:
|
||||
success = plugin_manager.enable_plugin(plugin_id)
|
||||
if success:
|
||||
print(f"[Settings] Enabled plugin: {plugin_id}")
|
||||
else:
|
||||
print(f"[Settings] Failed to enable plugin: {plugin_id}")
|
||||
else:
|
||||
success = plugin_manager.disable_plugin(plugin_id)
|
||||
if success:
|
||||
print(f"[Settings] Disabled plugin: {plugin_id}")
|
||||
|
||||
# Notify user a restart might be needed for some changes
|
||||
# But we try to apply immediately
|
||||
|
||||
def _enable_all_plugins(self):
|
||||
"""Enable all plugins."""
|
||||
if not hasattr(self.overlay, 'plugin_manager'):
|
||||
return
|
||||
|
||||
plugin_manager = self.overlay.plugin_manager
|
||||
for plugin_id, cb in self.plugin_checkboxes.items():
|
||||
cb.setChecked(True)
|
||||
plugin_manager.enable_plugin(plugin_id)
|
||||
|
||||
def _disable_all_plugins(self):
|
||||
"""Disable all plugins."""
|
||||
if not hasattr(self.overlay, 'plugin_manager'):
|
||||
return
|
||||
|
||||
plugin_manager = self.overlay.plugin_manager
|
||||
for plugin_id, cb in self.plugin_checkboxes.items():
|
||||
cb.setChecked(False)
|
||||
plugin_manager.disable_plugin(plugin_id)
|
||||
|
|
|
|||
Loading…
Reference in New Issue