Add settings dialog with QSettings persistence

Removes hardcoded credentials. Users enter their Steam API key and Steam ID
via a gear-icon settings dialog. Help text in the dialog explains how to
obtain each value with clickable links. Settings persist to
~/.config/butter/steam-dice.conf via QSettings. Dialog auto-opens on first
launch if credentials are not yet configured.
This commit is contained in:
2026-04-02 07:13:33 -04:00
parent 294a5f6810
commit 315724598f

View File

@@ -10,8 +10,9 @@ import requests
# Run natively on Wayland if available, fall back to X11 otherwise # Run natively on Wayland if available, fall back to X11 otherwise
if os.environ.get("WAYLAND_DISPLAY"): if os.environ.get("WAYLAND_DISPLAY"):
os.environ.setdefault("QT_QPA_PLATFORM", "wayland") os.environ.setdefault("QT_QPA_PLATFORM", "wayland")
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QComboBox from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
from PyQt6.QtCore import Qt, QThread, QTimer, pyqtSignal QPushButton, QLabel, QComboBox, QDialog, QDialogButtonBox, QLineEdit)
from PyQt6.QtCore import Qt, QSettings, QThread, QTimer, pyqtSignal
from PyQt6.QtGui import QPixmap, QFont, QIcon from PyQt6.QtGui import QPixmap, QFont, QIcon
VERSION = "v0.1.0" VERSION = "v0.1.0"
@@ -48,9 +49,6 @@ def _scan_installed_appids():
return installed return installed
STEAM_API_KEY = "A2B1B59F6F16FA3CD3107378AE737C3D"
STEAM_ID = "76561198000382373"
IMG_W = 460 IMG_W = 460
IMG_H = 215 IMG_H = 215
MARGIN = 20 MARGIN = 20
@@ -128,11 +126,16 @@ class FetchLibraryThread(QThread):
done = pyqtSignal(list) done = pyqtSignal(list)
error = pyqtSignal(str) error = pyqtSignal(str)
def __init__(self, api_key, steam_id):
super().__init__()
self.api_key = api_key
self.steam_id = steam_id
def run(self): def run(self):
try: try:
url = ( url = (
"https://api.steampowered.com/IPlayerService/GetOwnedGames/v1/" "https://api.steampowered.com/IPlayerService/GetOwnedGames/v1/"
f"?key={STEAM_API_KEY}&steamid={STEAM_ID}&include_appinfo=1&format=json" f"?key={self.api_key}&steamid={self.steam_id}&include_appinfo=1&format=json"
) )
r = requests.get(url, timeout=15) r = requests.get(url, timeout=15)
r.raise_for_status() r.raise_for_status()
@@ -161,6 +164,114 @@ class FetchImageThread(QThread):
self.done.emit(QPixmap()) self.done.emit(QPixmap())
DIALOG_STYLE = """
QDialog, QWidget { background-color: #1b2838; }
QLabel { color: #c6d4df; }
QLineEdit {
background-color: #2a3f5f;
color: #c6d4df;
border: 1px solid #3d5a7a;
border-radius: 4px;
padding: 4px 8px;
}
QLineEdit:focus { border-color: #5a8ab0; }
QPushButton {
background-color: #2a3f5f;
color: #c6d4df;
border: 1px solid #3d5a7a;
border-radius: 4px;
padding: 4px 14px;
min-width: 60px;
}
QPushButton:hover { background-color: #3d5a7a; }
QPushButton:pressed { background-color: #1e3050; }
a { color: #5a8ab0; }
"""
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Steam Dice — Settings")
self.setModal(True)
self.setFixedWidth(440)
self.setStyleSheet(DIALOG_STYLE)
layout = QVBoxLayout(self)
layout.setSpacing(6)
layout.setContentsMargins(20, 20, 20, 20)
# --- API Key ---
layout.addWidget(QLabel("<b>Steam API Key</b>"))
key_row = QHBoxLayout()
key_row.setSpacing(6)
settings = QSettings("butter", "steam-dice")
self.key_edit = QLineEdit(settings.value("api_key", ""))
self.key_edit.setEchoMode(QLineEdit.EchoMode.Password)
self.key_edit.setPlaceholderText("Paste your 32-character key here…")
key_row.addWidget(self.key_edit)
show_btn = QPushButton()
show_btn.setIcon(QIcon.fromTheme("password-show-on"))
show_btn.setFixedSize(30, 30)
show_btn.setCheckable(True)
show_btn.setToolTip("Show / hide key")
show_btn.toggled.connect(lambda on: self.key_edit.setEchoMode(
QLineEdit.EchoMode.Normal if on else QLineEdit.EchoMode.Password
))
key_row.addWidget(show_btn)
layout.addLayout(key_row)
key_help = QLabel(
'Get your free key at '
'<a href="https://steamcommunity.com/dev/apikey">steamcommunity.com/dev/apikey</a>.'
'<br>Log in with Steam, enter any domain name (e.g. <i>localhost</i>), and copy the key shown.'
)
key_help.setOpenExternalLinks(True)
key_help.setWordWrap(True)
key_help.setStyleSheet("color: #8f98a0; font-size: 9pt; padding-bottom: 10px;")
layout.addWidget(key_help)
# --- Steam ID ---
layout.addWidget(QLabel("<b>Steam ID (64-bit)</b>"))
self.id_edit = QLineEdit(settings.value("steam_id", ""))
self.id_edit.setPlaceholderText("e.g. 76561198000000000")
layout.addWidget(self.id_edit)
id_help = QLabel(
'Your 17-digit Steam ID. To find it: open your Steam profile in a browser — '
'the number in the URL (<i>steamcommunity.com/profiles/<b>XXXXXXXXXXXXXXXXX</b></i>) '
'is your ID.<br>'
'If your URL uses a custom name instead, look it up at '
'<a href="https://steamid.io">steamid.io</a>.'
)
id_help.setOpenExternalLinks(True)
id_help.setWordWrap(True)
id_help.setStyleSheet("color: #8f98a0; font-size: 9pt; padding-bottom: 10px;")
layout.addWidget(id_help)
# --- Buttons ---
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self._save)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def _save(self):
api_key = self.key_edit.text().strip()
steam_id = self.id_edit.text().strip()
if not api_key or not steam_id:
self.key_edit.setStyleSheet("border: 1px solid #a04040;" if not api_key else "")
self.id_edit.setStyleSheet("border: 1px solid #a04040;" if not steam_id else "")
return
settings = QSettings("butter", "steam-dice")
settings.setValue("api_key", api_key)
settings.setValue("steam_id", steam_id)
self.accept()
class SteamDice(QMainWindow): class SteamDice(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -201,16 +312,30 @@ class SteamDice(QMainWindow):
refresh_col.setSpacing(4) refresh_col.setSpacing(4)
refresh_col.setContentsMargins(0, 0, 0, 0) refresh_col.setContentsMargins(0, 0, 0, 0)
btn_row = QHBoxLayout()
btn_row.setSpacing(4)
btn_row.setContentsMargins(0, 0, 0, 0)
self.settings_btn = QPushButton()
self.settings_btn.setIcon(QIcon.fromTheme("configure"))
self.settings_btn.setFixedSize(28, 28)
self.settings_btn.setStyleSheet(REFRESH_STYLE)
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.settings_btn.setToolTip("Settings")
self.settings_btn.clicked.connect(self._open_settings)
btn_row.addWidget(self.settings_btn)
self.refresh_btn = QPushButton() self.refresh_btn = QPushButton()
self.refresh_btn.setIcon(QIcon.fromTheme("view-refresh")) self.refresh_btn.setIcon(QIcon.fromTheme("view-refresh"))
self.refresh_btn.setIconSize(self.refresh_btn.sizeHint())
self.refresh_btn.setFixedSize(28, 28) self.refresh_btn.setFixedSize(28, 28)
self.refresh_btn.setStyleSheet(REFRESH_STYLE) self.refresh_btn.setStyleSheet(REFRESH_STYLE)
self.refresh_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.refresh_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.refresh_btn.setToolTip("Refresh game library") self.refresh_btn.setToolTip("Refresh game library")
self.refresh_btn.setEnabled(False) self.refresh_btn.setEnabled(False)
self.refresh_btn.clicked.connect(self._refresh) self.refresh_btn.clicked.connect(self._refresh)
refresh_col.addWidget(self.refresh_btn, alignment=Qt.AlignmentFlag.AlignRight) btn_row.addWidget(self.refresh_btn)
refresh_col.addLayout(btn_row)
self.cooldown_label = QLabel() self.cooldown_label = QLabel()
self.cooldown_label.setFixedHeight(14) self.cooldown_label.setFixedHeight(14)
@@ -313,14 +438,33 @@ class SteamDice(QMainWindow):
layout.addLayout(bottom_row) layout.addLayout(bottom_row)
# Auto-open settings on first launch if credentials are missing
s = QSettings("butter", "steam-dice")
if not s.value("api_key") or not s.value("steam_id"):
QTimer.singleShot(0, self._open_settings)
self._fetch_library() self._fetch_library()
def _fetch_library(self): def _fetch_library(self):
self.fetch_thread = FetchLibraryThread() settings = QSettings("butter", "steam-dice")
api_key = settings.value("api_key", "")
steam_id = settings.value("steam_id", "")
if not api_key or not steam_id:
self.status_label.setText("No credentials — click ⚙ to configure.")
return
self.fetch_thread = FetchLibraryThread(api_key, steam_id)
self.fetch_thread.done.connect(self._on_library_loaded) self.fetch_thread.done.connect(self._on_library_loaded)
self.fetch_thread.error.connect(self._on_library_error) self.fetch_thread.error.connect(self._on_library_error)
self.fetch_thread.start() self.fetch_thread.start()
def _open_settings(self):
dlg = SettingsDialog(self)
if dlg.exec() == QDialog.DialogCode.Accepted:
self.status_label.setText("Loading library…")
self.dice_btn.setEnabled(False)
self.filter_combo.setEnabled(False)
self.refresh_btn.setEnabled(False)
self._fetch_library()
def _on_library_loaded(self, games): def _on_library_loaded(self, games):
self.all_games = games self.all_games = games
self.installed_appids = _scan_installed_appids() self.installed_appids = _scan_installed_appids()