diff --git a/projects/EU-Utility/plugins/calculator/__init__.py b/projects/EU-Utility/plugins/calculator/__init__.py new file mode 100644 index 0000000..b5ba8bf --- /dev/null +++ b/projects/EU-Utility/plugins/calculator/__init__.py @@ -0,0 +1,7 @@ +""" +Calculator Plugin for EU-Utility +""" + +from .plugin import CalculatorPlugin + +__all__ = ["CalculatorPlugin"] diff --git a/projects/EU-Utility/plugins/calculator/plugin.py b/projects/EU-Utility/plugins/calculator/plugin.py new file mode 100644 index 0000000..7b053bd --- /dev/null +++ b/projects/EU-Utility/plugins/calculator/plugin.py @@ -0,0 +1,300 @@ +""" +EU-Utility - Calculator Plugin + +Simple calculator with basic math and EU-specific calculations. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, + QLineEdit, QPushButton, QLabel, QGridLayout, + QTabWidget, QSpinBox, QDoubleSpinBox +) +from PyQt6.QtCore import Qt + +from plugins.base_plugin import BasePlugin + + +class CalculatorPlugin(BasePlugin): + """Simple calculator with EU-specific features.""" + + name = "Calculator" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Basic calculator and EU unit conversions" + hotkey = "ctrl+shift+c" + + def initialize(self): + """Setup calculator.""" + self.current_input = "" + + def get_ui(self): + """Create calculator UI.""" + widget = QWidget() + layout = QVBoxLayout(widget) + + # Tabs for different calculators + tabs = QTabWidget() + tabs.setStyleSheet(""" + QTabWidget::pane { + background-color: #333; + border: 1px solid #555; + } + QTabBar::tab { + background-color: #444; + color: #aaa; + padding: 8px 16px; + border: none; + } + QTabBar::tab:selected { + background-color: #4a9eff; + color: white; + } + """) + + # Basic Calculator Tab + basic_tab = self._create_basic_calculator() + tabs.addTab(basic_tab, "Basic") + + # EU Converter Tab + eu_tab = self._create_eu_converter() + tabs.addTab(eu_tab, "EU Units") + + layout.addWidget(tabs) + return widget + + def _create_basic_calculator(self): + """Create basic calculator.""" + tab = QWidget() + layout = QVBoxLayout(tab) + + # Display + self.calc_display = QLineEdit("0") + self.calc_display.setAlignment(Qt.AlignmentFlag.AlignRight) + self.calc_display.setStyleSheet(""" + QLineEdit { + background-color: #222; + color: #0f0; + font-size: 24px; + font-family: Consolas, monospace; + padding: 10px; + border: 2px solid #555; + border-radius: 4px; + } + """) + self.calc_display.setReadOnly(True) + layout.addWidget(self.calc_display) + + # Button grid + buttons = [ + ['C', '⌫', '%', '÷'], + ['7', '8', '9', '×'], + ['4', '5', '6', '-'], + ['1', '2', '3', '+'], + ['0', '.', '=', ''] + ] + + grid = QGridLayout() + for row, row_buttons in enumerate(buttons): + for col, btn_text in enumerate(row_buttons): + if not btn_text: + continue + + btn = QPushButton(btn_text) + btn.setFixedSize(60, 50) + + # Style based on button type + if btn_text in ['C', '⌫']: + style = """ + QPushButton { + background-color: #c44; + color: white; + font-size: 16px; + font-weight: bold; + border: none; + border-radius: 4px; + } + QPushButton:hover { background-color: #d55; } + """ + elif btn_text in ['÷', '×', '-', '+', '=', '%']: + style = """ + QPushButton { + background-color: #4a9eff; + color: white; + font-size: 18px; + font-weight: bold; + border: none; + border-radius: 4px; + } + QPushButton:hover { background-color: #5aafff; } + """ + else: + style = """ + QPushButton { + background-color: #444; + color: white; + font-size: 18px; + border: none; + border-radius: 4px; + } + QPushButton:hover { background-color: #555; } + """ + + btn.setStyleSheet(style) + btn.clicked.connect(lambda checked, t=btn_text: self._on_calc_button(t)) + grid.addWidget(btn, row, col) + + layout.addLayout(grid) + layout.addStretch() + + return tab + + def _create_eu_converter(self): + """Create EU unit converter.""" + tab = QWidget() + layout = QVBoxLayout(tab) + + # Title + title = QLabel("💰 PED Converter") + title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") + layout.addWidget(title) + + # PED input + ped_layout = QHBoxLayout() + ped_layout.addWidget(QLabel("PED:")) + + self.ped_input = QDoubleSpinBox() + self.ped_input.setRange(0, 1000000) + self.ped_input.setDecimals(4) + self.ped_input.setValue(1.0000) + self.ped_input.setStyleSheet(""" + QDoubleSpinBox { + background-color: #333; + color: #0f0; + font-size: 16px; + padding: 5px; + border: 1px solid #555; + } + """) + self.ped_input.valueChanged.connect(self._convert_ped) + ped_layout.addWidget(self.ped_input) + layout.addLayout(ped_layout) + + # Results + self.pec_result = QLabel("= 1,000 PEC") + self.pec_result.setStyleSheet("color: #aaa; font-size: 14px;") + layout.addWidget(self.pec_result) + + self.usd_result = QLabel("≈ $0.10 USD") + self.usd_result.setStyleSheet("color: #aaa; font-size: 14px;") + layout.addWidget(self.usd_result) + + # Divider + line = QLabel("─" * 40) + line.setStyleSheet("color: #555;") + layout.addWidget(line) + + # DPP Calculator + dpp_title = QLabel("⚔️ DPP Calculator") + dpp_title.setStyleSheet("color: white; font-size: 16px; font-weight: bold; margin-top: 10px;") + layout.addWidget(dpp_title) + + # Damage + dmg_layout = QHBoxLayout() + dmg_layout.addWidget(QLabel("Damage:")) + self.dmg_input = QDoubleSpinBox() + self.dmg_input.setRange(0.01, 10000) + self.dmg_input.setDecimals(2) + self.dmg_input.setValue(50.0) + self.dmg_input.setStyleSheet(""" + QDoubleSpinBox { + background-color: #333; + color: white; + font-size: 14px; + padding: 3px; + border: 1px solid #555; + } + """) + self.dmg_input.valueChanged.connect(self._calc_dpp) + dmg_layout.addWidget(self.dmg_input) + layout.addLayout(dmg_layout) + + # Cost + cost_layout = QHBoxLayout() + cost_layout.addWidget(QLabel("Cost (PEC):")) + self.cost_input = QDoubleSpinBox() + self.cost_input.setRange(0.01, 10000) + self.cost_input.setDecimals(4) + self.cost_input.setValue(2.5) + self.cost_input.setStyleSheet(""" + QDoubleSpinBox { + background-color: #333; + color: white; + font-size: 14px; + padding: 3px; + border: 1px solid #555; + } + """) + self.cost_input.valueChanged.connect(self._calc_dpp) + cost_layout.addWidget(self.cost_input) + layout.addLayout(cost_layout) + + # DPP Result + self.dpp_result = QLabel("DPP: 20.00") + self.dpp_result.setStyleSheet("color: #4a9eff; font-size: 18px; font-weight: bold; margin-top: 5px;") + layout.addWidget(self.dpp_result) + + layout.addStretch() + return tab + + def _on_calc_button(self, text): + """Handle calculator button press.""" + current = self.calc_display.text() + + if text == 'C': + self.calc_display.setText("0") + elif text == '⌫': + self.calc_display.setText(current[:-1] if len(current) > 1 else "0") + elif text == '=': + try: + # Replace display symbols with Python operators + expr = current.replace('×', '*').replace('÷', '/') + result = eval(expr) + self.calc_display.setText(str(result)[:12]) + except: + self.calc_display.setText("Error") + elif text in ['+', '-', '×', '÷', '%']: + if current[-1] not in '+-×÷%': + self.calc_display.setText(current + text) + elif text == '.': + if '.' not in current.split('[-+×÷]')[-1]: + self.calc_display.setText(current + text) + else: + # Number + if current == "0": + self.calc_display.setText(text) + else: + self.calc_display.setText(current + text) + + def _convert_ped(self): + """Convert PED to other units.""" + ped = self.ped_input.value() + pec = ped * 1000 + usd = ped * 0.1 # Approximate + + self.pec_result.setText(f"= {pec:,.0f} PEC") + self.usd_result.setText(f"≈ ${usd:.2f} USD") + + def _calc_dpp(self): + """Calculate DPP (Damage Per Pec).""" + damage = self.dmg_input.value() + cost = self.cost_input.value() + + if cost > 0: + dpp = damage / cost + self.dpp_result.setText(f"DPP: {dpp:.2f}") + else: + self.dpp_result.setText("DPP: ∞") + + def on_hotkey(self): + """Focus calculator when hotkey pressed.""" + pass # Calculator doesn't need focus action diff --git a/projects/EU-Utility/plugins/spotify_controller/__init__.py b/projects/EU-Utility/plugins/spotify_controller/__init__.py new file mode 100644 index 0000000..d283b86 --- /dev/null +++ b/projects/EU-Utility/plugins/spotify_controller/__init__.py @@ -0,0 +1,7 @@ +""" +Spotify Controller Plugin for EU-Utility +""" + +from .plugin import SpotifyControllerPlugin + +__all__ = ["SpotifyControllerPlugin"] diff --git a/projects/EU-Utility/plugins/spotify_controller/plugin.py b/projects/EU-Utility/plugins/spotify_controller/plugin.py new file mode 100644 index 0000000..bad3523 --- /dev/null +++ b/projects/EU-Utility/plugins/spotify_controller/plugin.py @@ -0,0 +1,305 @@ +""" +EU-Utility - Spotify Controller Plugin + +Control Spotify playback from the overlay. +""" + +import subprocess +import platform +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, + QLabel, QPushButton, QSlider, QProgressBar +) +from PyQt6.QtCore import Qt, QTimer + +from plugins.base_plugin import BasePlugin + + +class SpotifyControllerPlugin(BasePlugin): + """Control local Spotify playback.""" + + name = "Spotify" + version = "1.0.0" + author = "ImpulsiveFPS" + description = "Control Spotify playback on your PC" + hotkey = "ctrl+shift+m" # M for Music + + def initialize(self): + """Setup Spotify controller.""" + self.system = platform.system() + self.update_timer = None + self.current_track = "Not playing" + self.is_playing = False + + def get_ui(self): + """Create Spotify controller UI.""" + widget = QWidget() + layout = QVBoxLayout(widget) + + # Title + title = QLabel("🎵 Spotify Controller") + title.setStyleSheet("color: #1DB954; font-size: 18px; font-weight: bold;") + layout.addWidget(title) + + # Track info + self.track_label = QLabel("Not playing") + self.track_label.setStyleSheet(""" + color: white; + font-size: 14px; + font-weight: bold; + padding: 10px; + background-color: #222; + border-radius: 4px; + """) + self.track_label.setWordWrap(True) + layout.addWidget(self.track_label) + + # Artist + self.artist_label = QLabel("") + self.artist_label.setStyleSheet("color: #aaa; font-size: 12px;") + layout.addWidget(self.artist_label) + + # Progress bar (decorative - doesn't show actual progress) + self.progress = QProgressBar() + self.progress.setRange(0, 100) + self.progress.setValue(0) + self.progress.setTextVisible(False) + self.progress.setStyleSheet(""" + QProgressBar { + background-color: #333; + border: none; + height: 4px; + } + QProgressBar::chunk { + background-color: #1DB954; + } + """) + layout.addWidget(self.progress) + + # Control buttons + btn_layout = QHBoxLayout() + btn_layout.setSpacing(20) + + # Previous + prev_btn = QPushButton("⏮") + prev_btn.setFixedSize(50, 50) + prev_btn.setStyleSheet(self._get_button_style("#666")) + prev_btn.clicked.connect(self._previous_track) + btn_layout.addWidget(prev_btn) + + # Play/Pause + self.play_btn = QPushButton("▶") + self.play_btn.setFixedSize(60, 60) + self.play_btn.setStyleSheet(self._get_button_style("#1DB954", "20px")) + self.play_btn.clicked.connect(self._toggle_playback) + btn_layout.addWidget(self.play_btn) + + # Next + next_btn = QPushButton("⏭") + next_btn.setFixedSize(50, 50) + next_btn.setStyleSheet(self._get_button_style("#666")) + next_btn.clicked.connect(self._next_track) + btn_layout.addWidget(next_btn) + + layout.addLayout(btn_layout) + + # Volume slider + volume_layout = QHBoxLayout() + volume_layout.addWidget(QLabel("🔈")) + + self.volume_slider = QSlider(Qt.Orientation.Horizontal) + self.volume_slider.setRange(0, 100) + self.volume_slider.setValue(50) + self.volume_slider.setStyleSheet(""" + QSlider::groove:horizontal { + background: #333; + height: 8px; + border-radius: 4px; + } + QSlider::handle:horizontal { + background: #1DB954; + width: 18px; + margin: -5px 0; + border-radius: 9px; + } + QSlider::sub-page:horizontal { + background: #1DB954; + border-radius: 4px; + } + """) + self.volume_slider.valueChanged.connect(self._set_volume) + volume_layout.addWidget(self.volume_slider) + + volume_layout.addWidget(QLabel("🔊")) + layout.addLayout(volume_layout) + + # Status + self.status_label = QLabel("Click play to control Spotify") + self.status_label.setStyleSheet("color: #666; font-size: 11px; margin-top: 10px;") + layout.addWidget(self.status_label) + + # Install tip + tip = QLabel("💡 Tip: Ensure Spotify is running") + tip.setStyleSheet("color: #444; font-size: 10px;") + layout.addWidget(tip) + + layout.addStretch() + + # Start update timer + self._start_timer() + + return widget + + def _get_button_style(self, color, font_size="16px"): + """Get button stylesheet.""" + return f""" + QPushButton {{ + background-color: {color}; + color: white; + font-size: {font_size}; + font-weight: bold; + border: none; + border-radius: 25px; + }} + QPushButton:hover {{ + background-color: {self._lighten_color(color)}; + }} + QPushButton:pressed {{ + background-color: {self._darken_color(color)}; + }} + """ + + def _lighten_color(self, hex_color): + """Lighten color for hover effect.""" + # Simple approximation + if hex_color == "#1DB954": + return "#1ed760" + return "#777" + + def _darken_color(self, hex_color): + """Darken color for press effect.""" + if hex_color == "#1DB954": + return "#1aa34a" + return "#555" + + def _start_timer(self): + """Start status update timer.""" + self.update_timer = QTimer() + self.update_timer.timeout.connect(self._update_status) + self.update_timer.start(2000) # Update every 2 seconds + + def _update_status(self): + """Update playback status.""" + # In a real implementation, this would query Spotify's API + # For now, just show a placeholder + pass + + def _send_media_key(self, key): + """Send media key press to system.""" + try: + if self.system == "Windows": + # Use Windows media keys + import ctypes + # VK_MEDIA_PLAY_PAUSE = 0xB3 + # VK_MEDIA_NEXT_TRACK = 0xB0 + # VK_MEDIA_PREV_TRACK = 0xB1 + # VK_VOLUME_UP = 0xAF + # VK_VOLUME_DOWN = 0xAE + # VK_VOLUME_MUTE = 0xAD + + key_codes = { + 'play': 0xB3, + 'next': 0xB0, + 'prev': 0xB1, + 'vol_up': 0xAF, + 'vol_down': 0xAE, + } + + if key in key_codes: + ctypes.windll.user32.keybd_event(key_codes[key], 0, 0, 0) + ctypes.windll.user32.keybd_event(key_codes[key], 0, 2, 0) + return True + + elif self.system == "Linux": + # Use playerctl or dbus + cmd_map = { + 'play': ['playerctl', 'play-pause'], + 'next': ['playerctl', 'next'], + 'prev': ['playerctl', 'previous'], + } + if key in cmd_map: + subprocess.run(cmd_map[key], capture_output=True) + return True + + elif self.system == "Darwin": # macOS + # Use osascript + cmd_map = { + 'play': ['osascript', '-e', 'tell application "Spotify" to playpause'], + 'next': ['osascript', '-e', 'tell application "Spotify" to next track'], + 'prev': ['osascript', '-e', 'tell application "Spotify" to previous track'], + } + if key in cmd_map: + subprocess.run(cmd_map[key], capture_output=True) + return True + + except Exception as e: + print(f"Error sending media key: {e}") + + return False + + def _toggle_playback(self): + """Toggle play/pause.""" + if self._send_media_key('play'): + self.is_playing = not self.is_playing + self.play_btn.setText("⏸" if self.is_playing else "▶") + self.track_label.setText("Playing..." if self.is_playing else "Paused") + self.status_label.setText("Command sent to Spotify") + else: + self.status_label.setText("❌ Could not control Spotify") + + def _next_track(self): + """Next track.""" + if self._send_media_key('next'): + self.status_label.setText("⏭ Next track") + else: + self.status_label.setText("❌ Could not skip") + + def _previous_track(self): + """Previous track.""" + if self._send_media_key('prev'): + self.status_label.setText("⏮ Previous track") + else: + self.status_label.setText("❌ Could not go back") + + def _set_volume(self, value): + """Set volume (0-100).""" + try: + if self.system == "Windows": + # Use Windows volume control + # This controls system volume, not Spotify specifically + pass # Would need pycaw library for precise control + elif self.system == "Linux": + subprocess.run(['playerctl', 'volume', str(value / 100)], capture_output=True) + elif self.system == "Darwin": + pass # Would need specific implementation + except: + pass + + def on_hotkey(self): + """Toggle play/pause with hotkey.""" + self._toggle_playback() + + def on_hide(self): + """Stop timer when overlay hidden.""" + if self.update_timer: + self.update_timer.stop() + + def on_show(self): + """Restart timer when overlay shown.""" + if self.update_timer: + self.update_timer.start() + + def shutdown(self): + """Cleanup.""" + if self.update_timer: + self.update_timer.stop() diff --git a/projects/EU-Utility/requirements.txt b/projects/EU-Utility/requirements.txt index 82b8a5f..f815e66 100644 --- a/projects/EU-Utility/requirements.txt +++ b/projects/EU-Utility/requirements.txt @@ -2,9 +2,15 @@ PyQt6>=6.4.0 keyboard>=0.13.5 -# Optional plugins -# spotipy>=2.23.0 # For Spotify plugin -# discord.py>=2.3.0 # For Discord plugin +# Optional plugin dependencies +# Uncomment if using specific plugins: + +# For Spotify Controller (advanced features) +# spotipy>=2.23.0 +# pycaw>=20230407; platform_system=="Windows" + +# For Discord Rich Presence +# pypresence>=4.3.0 # Development # pytest>=7.0.0