- Replace .map().unwrap_or(false) with .is_some_and()/.is_ok_and()
- Use path.display() instead of {:?} for user-facing messages
- Replace Option<Option<Vec<String>>> with GamescopeUpdate enum
- Replace manual parent-walking loops with .ancestors() iterators
- Simplify kill()/kill_all() signatures to return () instead of Result
- Use tokio::task::spawn_blocking instead of hand-rolled thread+oneshot
- Read /proc/self/status for UID instead of spawning id subprocess
- Build Exec= line directly in render_desktop instead of string-replace
- Bump PKGBUILD pkgrel to 6
SKIP_DIRS contained parent directories that also contain game installs,
which prevented those games from ever being scanned:
- "epic games" → all Epic games live inside this dir
- "ubisoft" → Ubisoft games at Ubisoft/Ubisoft Game Launcher/games/
- "rockstar games" → Rockstar games live directly inside this dir
- "electronic arts" → some EA games live here
SKIP_EXES already filters the individual launcher executables, so the
directory-level blocks are redundant and harmful. Trim SKIP_DIRS to only
directories that genuinely contain no game executables (battle.net,
ea desktop, gog galaxy, Wine infrastructure).
Add missing launcher path patterns to name_from_launcher_path:
- "ea games" → EA Games/<GameName>/
- "ubisoft game launcher" → Ubisoft Game Launcher/games/<GameName>/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add stage 2: read Legendary and Heroic's installed.json files from all
known locations (native + Flatpak) to build a install-path → title map.
This covers Epic games (via Legendary) and GOG games (via Heroic's GOG
store) before any fallback is needed.
Remove CamelCase/digit-boundary splitting from the fallback entirely.
If stages 2-4 all miss, nearest_dir_name() returns the closest
non-generic parent directory name, or the raw exe stem as-is. No names
are fabricated from the exe filename.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the hardcoded EXE_OVERRIDES lookup table and the unreliable PE
byte scanner. Game names are already present in the install directory —
we just need to read them from the right place.
Resolution pipeline (first hit wins):
1. Explicit name supplied by the caller
2. Manifest walk: traverse up from the exe to the game root looking for
GOG goggame-*.info (gameName) and Epic .egstore/*.item (DisplayName)
3. Launcher path: read the game name from known directory conventions
laid down by the launcher itself:
- Epic Games/<GameName>/…
- GOG Games/<GameName>/…
- steamapps/common/<GameName>/…
- Rockstar Games/<GameName>/…
4. Heuristic: nearest non-generic parent directory name, or CamelCase
stem split (unchanged, for truly custom/manual installs)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce resolve_game_name() as the single entry point for deriving a
display name from a game executable. Resolution order:
1. Explicit name (caller-supplied)
2. Static override table for known bad stems (FactoryGame, bg3, etc.)
3. GOG goggame-*.info and Epic .egstore/*.item manifest JSON files
4. PE VERSIONINFO scan (ProductName / FileDescription, first 8 MB)
5. Heuristic fallback: parent directory name or CamelCase stem split
Remove prettify_game_name and humanise_stem; expose prettify_exe_name
as the public heuristic-only fallback. Resolved names are cached in a
process-wide LazyLock<Mutex<HashMap>> so repeated scans are free.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- detect::scan_games_in_prefix() walks drive_c/Program Files and
Program Files (x86) up to depth 4, skipping Windows system dirs,
the launcher's own exe, and already-configured games
- Empty game list now shows "No games added yet." with two actions:
· Scan for games — async scan of the Wine prefix, results appear
as a clickable list with + buttons to add each found exe
· Browse exe… — native file picker (zenity/kdialog) opening in
drive_c/, computes the relative path automatically
- Add-game form gains a Browse… button to pick exe from the prefix
- util::pick_file() added alongside pick_folder()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add #![forbid(unsafe_code)] to main.rs (issue #3)
- Replace raw ANSI escape codes with owo-colors crate (issue #2)
- Replace manual HOME path construction with dirs::home_dir() (issue #5)
- Ship umutray.service as a static file; service::install() substitutes
the binary path at install time instead of generating the unit at runtime
- Add packaging/PKGBUILD following Arch Rust package guidelines
- Add CLAUDE.md tracking refactor tasks
- setup.rs: clean up downloaded temp files on abort/back, save launcher
to config only after successful install, auto-start download when a
preset has an installer_url
- util.rs: add pick_folder() using zenity/kdialog subprocesses (no rfd)
- config.rs: populate installer_url for all 6 built-in presets with
official download URLs
- Document the Option<Option<Vec<String>>> gamescope pattern at main.rs:307
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scans common Wine prefix locations (~/Games, ~/.wine, Lutris, Bottles,
Heroic) plus any user-supplied --dir paths for each configured
launcher's exe. Reports matches with four markers:
✓ already configured at that prefix
→ detected at a different prefix (--apply to update)
⚠ multiple prefixes match (ambiguous)
· not found
--apply writes the new prefix_dir back to config.toml for unambiguous
cases; ambiguous ones are skipped with a note to resolve via
`config edit`. The Setup doc comment is also refreshed since the iced
wizard landed in an earlier commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>