-
Notifications
You must be signed in to change notification settings - Fork 0
SteamMachine DIY Control Center
PyQt6 dashboard for system management, YAML configuration editing, and log analysis.
Default tab. Logs are fetched in a background thread via load_logs() and auto-refresh each time the user switches to this tab. on_tab_changed detects the diagnostics tab dynamically via self.tabs.indexOf(self.diag_tab) โ no magic index.
-
Component Filter: Combo with
ALL,CORE,STEAM,SYSTEM. Each selection callsget_journal_cmd(tag)which runsjournalctl -t <tag>(last 12 hours, 300 entries, export format). -
Gamescope integration: When the filter is
ALLorSTEAM, a second journal query (journalctl -t steam -t python3 --since "1 hour ago" -o short-iso) fetches gamescope output and merges it into the display. Lines are accepted only when their payload matches the gamescope log format ([Info]/[Warn]/[Error]/[Gamescope WSI]or/usr/bin/gamescope:) so substring noise (e.g. file managers acting ongamescope.example.yaml) is filtered out. Already-seenLAUNCH_ARGSstrings are deduplicated. -
Log deduplication: Consecutive identical lines are collapsed by
_display_colored_logs()into a "โคท Repeated N times" note. -
Export: Copy to clipboard (
copy_logs()), or save to a user-chosen file (export_support_log()).
Privileged operations (backup, restore, log vacuum) run in a background threading.Thread via _run_pkexec. Results surface via the process_finished PyQt signal. Non-privileged launches (Switch to Steam, Open Konsole, Browse Config Folder) use spawn_native from utils.py (detached, start_new_session=True). Edit SSoT uses subprocess.Popen directly to preserve the GUI error dialog on failure.
Buttons in order:
| Button | Action |
|---|---|
| Switch to Steam (Game Mode) | Calls session_select.py steam via spawn_native. |
| Edit System Config (SSoT) | Opens /etc/default/steamos_diy.conf in kate (falls back to kwrite). |
| Clean System Logs (Vacuum) | Runs pkexec journalctl --rotate --vacuum-time=1s in a single invocation. |
| Create Full System Backup | Runs pkexec python3 backup.py in a background thread. |
| Restore from Archive | Opens a file picker for a .tar.gz, then runs pkexec python3 restore.py <path>. |
| Open Konsole Terminal | Spawns konsole. |
| Browse Config Folder | Opens ~/.config/steamos_diy/ via xdg-open. |
| Open Project Wiki | Opens the wiki URL via QDesktopServices. |
Text editor for YAML configuration files with real-time validation.
-
File selector: Combo listing
config.yaml,config.example.yaml, andgamescope.example.yaml. Switching the combo loads the selected file into the editor. -
View Template: Toggles between the active file and its
.example.yamlcounterpart. The editor's previous content is cached inview_states["global"]and restored on toggle-back. Saving is disabled while in template view. -
Beautify:
beautify_yaml()runs the text through the ruamel.yaml round-trip parser, converting tabs to two spaces and fixing indentation while preserving comments and quoting. -
Save:
_atomic_save()validates the YAML and delegates persistence towrite_atomic()(C-Core) โ the sametmp + fdatasync + renameprotocol used for the session state file. - Error highlighting: On a YAML parse error, the offending line gets a red background and the preceding line an orange background, helping identify root causes like unclosed quotes. The highlight clears on any user edit.
Per-game YAML profile editor backed by journal-based game discovery.
-
Scan History:
refresh_detected_games()runsjournalctl --since "24 hours ago" --no-hostname --no-pager(no entry limit) in a background thread.filter_game_journal_lines()(fromjournal.py) then extracts game-related lines (matchingchdir,gameID, orAppIDpatterns, noise-filtered), keeping at most the last 2000 matching lines.parse_game_logs()(fromjournal.py) then builds the{name: appid}dict by callingextract_game_metadata()per line. -
On-disk merge: After scanning,
_merge_on_disk_profiles()adds any existinggames.d/*.yamlfile not already in the detection results, so profiles are always accessible even if the game wasn't recently launched. -
Combo display: Games are shown as
"Name (AppID)"when an AppID is known, otherwise just"Name". The combo is editable; the profile is loaded (or scaffolded) on selection โ typing a new name does not reload until you confirm it. -
Profile scaffold: If no profile exists for the selected game,
_scaffold_game_profile()generates a YAML stub includingSDY_IDandSTEAM_APPIDheaders when an AppID is available. -
Unsaved edits:
view_states["games"]caches unsaved editor content while switching between games or toggling templates. -
Save:
save_game_profile()writes togames.d/<Name>.yamlvia_atomic_save().
| Tab | Action | Method | Logic |
|---|---|---|---|
| 0 | Load Logs | load_logs() |
get_journal_cmd(tag) โ journalctl -t (12h, 300 entries); ALL/STEAM also merge gamescope logs (last 1h, short-iso) |
| 0 | Export Log | export_support_log() |
QFileDialog + Path.write_text
|
| 1 | Clean Logs | cleanup_logs_privileged() |
pkexec journalctl --rotate --vacuum-time=1s (single invocation) |
| 1 | Backup | run_backup() |
pkexec python3 backup.py in threading.Thread
|
| 1 | Restore | run_restore() |
QFileDialog + pkexec python3 restore.py <path> in threading.Thread
|
| 2 | Save Config | _atomic_save() |
YAML validation โ write_atomic() (C-Core, fdatasync + rename) |
| 2 | Beautify | beautify_yaml() |
ruamel.yaml round-trip (tabs โ spaces, indent fix, comments preserved) |
| 2 | View Template | toggle_template("global") |
Loads/restores .example.yaml; disables save while active |
| 3 | Scan Games | refresh_detected_games() |
journalctl --since "24 hours ago" โ filter โ last 2000 game lines |
| 3 | Save Profile | save_game_profile() |
_atomic_save() โ games.d/<Name>.yaml
|
Defined in
editors.pyโ zero dependency on project modules, purely self-contained Qt widgets.
Custom editor used in both the Global Options and Game Overrides tabs.
-
Line number gutter (
LineNumberArea): A side widget rendered viaQPainter. Width scales dynamically with line count. Background#2c3e50, numbers#95a5a6. -
Auto-indent: On
Enter/Return, the previous line's leading whitespace is replicated on the new line, preserving YAML indentation without manual spacing. -
No word wrap:
LineWrapMode.NoWrapkeeps long flag lines readable.
Rule-based highlighter applied to both editors. Rules are evaluated per visible block by Qt.
| Element | Pattern | Colour | Style |
|---|---|---|---|
| Comments | #... |
Grey #7f8c8d
|
Normal |
| Keys | word: |
Blue #3498db
|
Bold |
| Strings |
"..." / '...'
|
Yellow #f1c40f
|
Normal |
| List items | - ... |
Green #27ae60
|
Normal |
| Numbers | \d+ |
Orange #e67e22
|
Normal |
| Colons & dashes |
: / -
|
Red #e74c3c
|
Bold |
| Error line | โ | Red #e74c3c ฮฑ50 |
Background |
| Preceding line | โ | Orange #f39c12 ฮฑ50 |
Background |
| Tag | Icon | Colour |
|---|---|---|
CORE: |
๐ต | Blue #3498db
|
STEAM: |
๐ฎ | Green #2ecc71
|
SYSTEM: |
โ๏ธ | Orange #f39c12
|
DEBUG: |
๐ | Grey #95a5a6
|
ERROR: |
๐ซ | Red #e74c3c
|
| Tag | Icon | Colour |
|---|---|---|
[gamescope] |
โ | Teal #1abc9c
|
[Error] |
โ | Red #ff4444
|
[Warn] |
Amber #ffbb33
|
|
[Info] |
โน๏ธ | Blue #33b5e5
|
LAUNCH_ARGS |
๐ | Green #2ecc71 bold
|
The module-level yaml_parser instance is shared across all save and beautify operations:
yaml_parser = YAML()
yaml_parser.preserve_quotes = True
yaml_parser.indent(mapping=2, sequence=4, offset=2)
yaml_parser.width = 4096_atomic_save() validates content through yaml_parser.load() before writing. A parse error surfaces the problem line in the editor without touching the file on disk.
| Goal | Recommended Action |
|---|---|
| Debug a Crash | Open Diagnostics, filter by ALL or STEAM, look for ERROR: tags or [Error] gamescope lines. |
| Verify Configuration | Use Global Options; the editor highlights the error line (red) and its predecessor (orange) on invalid YAML. |
| System Recovery | If shims or symlinks are broken, use Restore from Archive in Maintenance. |
| Game Specific Profile | Use Game Overrides โ Scan History โ select the game โ edit and save. |
If you love this project, feel free to join and help me make it better!