Tema clar/fosc segons el sistema i títol de finestra

This commit is contained in:
2026-05-29 21:07:00 +02:00
parent b71df66e22
commit 9d13c2434b
4 changed files with 131 additions and 6 deletions
+3
View File
@@ -9,11 +9,14 @@ from PySide6.QtWidgets import QApplication, QMessageBox
from .config import load_config from .config import load_config
from .paths import config_file, data_root from .paths import config_file, data_root
from .ui.main_window import MainWindow from .ui.main_window import MainWindow
from .ui.theme import apply_theme, watch_system_theme
def main() -> int: def main() -> int:
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setApplicationName("jlauncher") app.setApplicationName("jlauncher")
apply_theme(app)
watch_system_theme(app)
cfg_path = config_file() cfg_path = config_file()
try: try:
+3 -2
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
from PySide6.QtCore import Qt, Signal from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QPixmap from PySide6.QtGui import QPalette, QPixmap
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QFrame, QFrame,
QHBoxLayout, QHBoxLayout,
@@ -52,7 +52,8 @@ class GameRow(QFrame):
self.name_label.setStyleSheet("font-weight: bold; font-size: 14px;") self.name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
self.desc_label = QLabel("") self.desc_label = QLabel("")
self.desc_label.setWordWrap(True) self.desc_label.setWordWrap(True)
self.desc_label.setStyleSheet("color: #aaaaaa;") # Texto atenuado que sigue la paleta (claro/oscuro) en vez de un gris fijo.
self.desc_label.setForegroundRole(QPalette.PlaceholderText)
self.status_label = QLabel("") self.status_label = QLabel("")
self.status_label.setStyleSheet("color: #6fae6f; font-size: 11px;") self.status_label.setStyleSheet("color: #6fae6f; font-size: 11px;")
text_box.addWidget(self.name_label) text_box.addWidget(self.name_label)
+5 -4
View File
@@ -18,6 +18,9 @@ from ..config import Config, Game
from ..workers import DownloadWorker, RunWorker from ..workers import DownloadWorker, RunWorker
from .game_row import GameRow from .game_row import GameRow
APP_NAME = "Jail Launcher"
WINDOW_TITLE = f"© 2026 {APP_NAME} — JailDesigner"
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self, config: Config, root: Path, parent=None) -> None: def __init__(self, config: Config, root: Path, parent=None) -> None:
@@ -31,7 +34,7 @@ class MainWindow(QMainWindow):
# llegue al hilo principal, y la UI nunca se refresca. # llegue al hilo principal, y la UI nunca se refresca.
self._workers: set = set() self._workers: set = set()
self.setWindowTitle("jlauncher — Juegos jailgames") self.setWindowTitle(WINDOW_TITLE)
self.resize(720, 640) self.resize(720, 640)
splitter = QSplitter(Qt.Vertical) splitter = QSplitter(Qt.Vertical)
@@ -58,9 +61,7 @@ class MainWindow(QMainWindow):
self.log_view = QPlainTextEdit() self.log_view = QPlainTextEdit()
self.log_view.setReadOnly(True) self.log_view.setReadOnly(True)
self.log_view.setMaximumBlockCount(5000) self.log_view.setMaximumBlockCount(5000)
self.log_view.setStyleSheet( self.log_view.setStyleSheet("font-family: monospace; font-size: 11px;")
"font-family: monospace; font-size: 11px; background:#1e1e1e; color:#d4d4d4;"
)
splitter.addWidget(self.log_view) splitter.addWidget(self.log_view)
splitter.setStretchFactor(0, 3) splitter.setStretchFactor(0, 3)
splitter.setStretchFactor(1, 1) splitter.setStretchFactor(1, 1)
+120
View File
@@ -0,0 +1,120 @@
"""Tema claro/oscuro siguiendo el esquema de color del sistema.
Usa el estilo Fusion (consistente entre plataformas) y aplica una paleta clara u
oscura según ``QStyleHints.colorScheme()``. Los widgets que no fuerzan colores
propios (consola de log, fondos) siguen automáticamente esta paleta.
"""
from __future__ import annotations
import subprocess
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QApplication
def _portal_is_dark() -> bool | None:
"""Consulta el portal XDG (org.freedesktop.appearance color-scheme).
Devuelve True (1=dark), False (2=light) o None (0=sin preferencia / no disponible).
Es la fuente más fiable en Linux cuando Qt no detecta el esquema.
"""
try:
out = subprocess.run(
[
"gdbus", "call", "--session",
"--dest", "org.freedesktop.portal.Desktop",
"--object-path", "/org/freedesktop/portal/desktop",
"--method", "org.freedesktop.portal.Settings.Read",
"org.freedesktop.appearance", "color-scheme",
],
capture_output=True, text=True, timeout=2,
)
except (OSError, subprocess.SubprocessError):
return None
if out.returncode != 0:
return None
if "uint32 1" in out.stdout:
return True
if "uint32 2" in out.stdout:
return False
return None
def _gsettings_is_dark() -> bool | None:
"""Respaldo: gsettings color-scheme de GNOME ('prefer-dark')."""
try:
out = subprocess.run(
["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"],
capture_output=True, text=True, timeout=2,
)
except (OSError, subprocess.SubprocessError):
return None
if out.returncode != 0:
return None
return "dark" in out.stdout.lower()
def system_is_dark(app: QApplication) -> bool:
"""True si el sistema está en modo oscuro.
Prioriza el aviso de Qt; si Qt dice claro (o no soporta la API), consulta el
portal XDG y gsettings, que en sesiones xcb suelen ser más fiables que Qt.
"""
hints = app.styleHints()
if hasattr(hints, "colorScheme"):
try:
if hints.colorScheme() == Qt.ColorScheme.Dark:
return True
except Exception: # noqa: BLE001 - APIs viejas/raras: caer al fallback
pass
for probe in (_portal_is_dark, _gsettings_is_dark):
val = probe()
if val is not None:
return val
return app.palette().color(QPalette.Window).lightness() < 128
def _dark_palette() -> QPalette:
p = QPalette()
window = QColor(0x2b, 0x2b, 0x2b)
base = QColor(0x1e, 0x1e, 0x1e)
alt = QColor(0x35, 0x35, 0x35)
text = QColor(0xdc, 0xdc, 0xdc)
disabled = QColor(0x7f, 0x7f, 0x7f)
highlight = QColor(0x2a, 0x82, 0xda)
p.setColor(QPalette.Window, window)
p.setColor(QPalette.WindowText, text)
p.setColor(QPalette.Base, base)
p.setColor(QPalette.AlternateBase, alt)
p.setColor(QPalette.ToolTipBase, window)
p.setColor(QPalette.ToolTipText, text)
p.setColor(QPalette.Text, text)
p.setColor(QPalette.Button, alt)
p.setColor(QPalette.ButtonText, text)
p.setColor(QPalette.BrightText, Qt.red)
p.setColor(QPalette.Link, highlight)
p.setColor(QPalette.Highlight, highlight)
p.setColor(QPalette.HighlightedText, Qt.black)
p.setColor(QPalette.PlaceholderText, disabled)
for role in (QPalette.WindowText, QPalette.Text, QPalette.ButtonText):
p.setColor(QPalette.Disabled, role, disabled)
return p
def apply_theme(app: QApplication) -> None:
"""Aplica estilo Fusion + paleta acorde al esquema del sistema."""
app.setStyle("Fusion")
if system_is_dark(app):
app.setPalette(_dark_palette())
else:
app.setPalette(app.style().standardPalette())
def watch_system_theme(app: QApplication) -> None:
"""Re-aplica el tema cuando el sistema cambia entre claro/oscuro en caliente."""
hints = app.styleHints()
if hasattr(hints, "colorSchemeChanged"):
hints.colorSchemeChanged.connect(lambda _scheme: apply_theme(app))