#!/usr/bin/env python3 import sys import json import signal import os from PyQt5.QtCore import Qt, QUrl, QTimer from PyQt5.QtWidgets import ( QApplication, QMainWindow, QSystemTrayIcon, QMenu, QAction, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QComboBox, QPushButton, QMessageBox ) from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings from PyQt5.QtGui import QColor, QIcon CONFIG_PATH = os.path.expanduser("~/.config/hudfx-lnx/config.json") def load_config(): if os.path.exists(CONFIG_PATH): with open(CONFIG_PATH) as f: return json.load(f) return {"url": "", "screen_index": 0} def save_config(config): os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) with open(CONFIG_PATH, "w") as f: json.dump(config, f, indent=2) class Overlay(QMainWindow): def __init__(self, url, screen): super().__init__() self.setWindowFlags( Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool | Qt.WindowTransparentForInput ) self.setAttribute(Qt.WA_TranslucentBackground) view = QWebEngineView() profile = QWebEngineProfile(view) page = QWebEnginePage(profile, view) view.setPage(page) view.page().setBackgroundColor(QColor(0, 0, 0, 0)) view.page().setAudioMuted(False) settings = view.page().settings() settings.setAttribute(QWebEngineSettings.PlaybackRequiresUserGesture, False) settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True) view.load(QUrl(url)) self.setCentralWidget(view) self.setGeometry(screen.geometry()) self.show() class SettingsDialog(QDialog): def __init__(self, config, screens, parent=None): super().__init__(parent) self.setWindowTitle("HudFX Settings") self.setMinimumWidth(420) self.config = config layout = QVBoxLayout(self) layout.addWidget(QLabel("Overlay URL:")) self.url_input = QLineEdit(config.get("url", "")) self.url_input.setPlaceholderText("https://...") layout.addWidget(self.url_input) layout.addWidget(QLabel("Display monitor:")) self.screen_combo = QComboBox() for i, screen in enumerate(screens): s = screen.size() name = f"Monitor {i + 1} — {s.width()}x{s.height()} ({screen.name()})" self.screen_combo.addItem(name) self.screen_combo.setCurrentIndex(config.get("screen_index", 0)) layout.addWidget(self.screen_combo) buttons = QHBoxLayout() save_btn = QPushButton("Save") cancel_btn = QPushButton("Cancel") save_btn.clicked.connect(self.accept) cancel_btn.clicked.connect(self.reject) buttons.addWidget(save_btn) buttons.addWidget(cancel_btn) layout.addLayout(buttons) def get_values(self): return { "url": self.url_input.text().strip(), "screen_index": self.screen_combo.currentIndex(), } class TrayApp: def __init__(self, app): self.app = app self.config = load_config() self.overlay = None self.tray = QSystemTrayIcon() self.tray.setToolTip("HudFX Overlay") icon = QIcon.fromTheme("video-display") if icon.isNull(): icon = QIcon.fromTheme("preferences-desktop-display") self.tray.setIcon(icon) self.menu = QMenu() self.toggle_action = QAction("Start overlay") self.toggle_action.triggered.connect(self.toggle_overlay) self.menu.addAction(self.toggle_action) self.settings_action = QAction("Settings...") self.settings_action.triggered.connect(self.open_settings) self.menu.addAction(self.settings_action) self.menu.addSeparator() self.quit_action = QAction("Quit") self.quit_action.triggered.connect(self.quit) self.menu.addAction(self.quit_action) self.tray.setContextMenu(self.menu) self.tray.show() self.toggle_overlay() def toggle_overlay(self): if self.overlay: self.overlay.close() self.overlay = None self.toggle_action.setText("Start overlay") else: url = self.config.get("url", "").strip() if not url: QMessageBox.warning(None, "HudFX", "No URL set. Open Settings first.") return idx = self.config.get("screen_index", 0) screens = self.app.screens() screen = screens[idx] if idx < len(screens) else self.app.primaryScreen() self.overlay = Overlay(url, screen) self.toggle_action.setText("Stop overlay") def open_settings(self): dialog = SettingsDialog(self.config, self.app.screens()) if dialog.exec_() == QDialog.Accepted: self.config = dialog.get_values() save_config(self.config) def quit(self): if self.overlay: self.overlay.close() self.app.quit() if __name__ == "__main__": app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) signal.signal(signal.SIGINT, signal.SIG_DFL) timer = QTimer() timer.start(200) timer.timeout.connect(lambda: None) tray = TrayApp(app) sys.exit(app.exec_())