The Problem

Desktop Linux users with multiple game launchers (Steam, Lutris, Heroic, etc.) must open each client to view or launch a game, and there is no unified, offline‑first view that respects the Omarchy visual language. Maintaining separate libraries also fragments achievement tracking and metadata.

What This Does

omakade aggregates locally‑installed games from six launchers into a single Qt‑based UI that matches the Omarchy theme. The discovery logic lives in the src/sources/ folder (e.g., SteamScanner.cpp, LutrisScanner.cpp) and populates unified model objects such as UnifiedGameModel.cpp. UI components are QML files under qml/ (Main.qml, GameCard.qml). The application entry point is src/app/main.cpp, which boots the Qt engine, loads settings from src/app/AppSettings.cpp, and instantiates the QML UI.

Key runtime pieces:

  • Scanning – each *Scanner implements a scan() method that reads launcher config files or databases and emits GameInfo structs.
  • Model layer – src/library/UnifiedGameModel.cpp merges results from the individual scanners and presents them through Qt’s QAbstractListModel.
  • Launching – src/launch/GameLauncher.cpp dispatches a request to the appropriate concrete launcher (SteamLauncher.cpp, RetroArchLauncher.cpp, etc.).
  • Achievements & metadata – optional enrichment via src/achievements/SteamAchievementApi.cpp and src/metadata/IgdbApi.cpp.

All assets (icons, demo video) are stored in docs/assets/ and referenced by the QML UI.

How It Is Wired

  1. Entry point – src/app/main.cpp creates a QGuiApplication, checks for a single instance via SingleInstance.cpp, loads persistent settings (AppSettings.cpp), and calls QQuickView::setSource(QUrl(QStringLiteral("qrc:/Main.qml"))).
  2. App initialization – AppSettings reads/writes ~/.config/omakade/settings.json (file handling is encapsulated in AppSettings.cpp).
  3. Data population – UnifiedGameModel (in src/library/UnifiedGameModel.cpp) constructs a list of GameModel objects. It invokes each scanner (SteamScanner, LutrisScanner, HeroicScanner, FaugusScanner, RetroArchScanner) which read launcher‑specific files (e.g., Steam’s libraryfolders.vdf, Lutris’ lutris.db). The scanners expose a std::vector<GameInfo> that UnifiedGameModel converts into Qt roles.
  4. UI binding – QML files under qml/ import the model via context->setContextProperty("libraryModel", &unifiedModel). UI components (GameCard.qml, LibraryView.qml) bind to model roles (title, coverPath, source).
  5. User actions – Clicking “Play” triggers a QML signal that calls GameLauncher::launch(const GameInfo&) (implemented in src/launch/GameLauncher.cpp). The launcher selects the concrete implementation (SteamLauncher, RetroArchLauncher, …) based on the source field and executes the platform’s launch command (e.g., steam://run/<appid> or retroarch -L <core> <rom>).
  6. Optional enrichment – If the user enables IGDB or Steam achievement panels, IgdbApi.cpp and SteamAchievementApi.cpp perform HTTP requests (Qt Network) and populate additional model roles.

The most “blast‑radius” module is UnifiedGameModel.cpp; changes here affect every UI component and launcher because it is the single source of truth for the game list. The scanner classes are relatively isolated – each reads only its own launcher files, making them safe to modify independently.

No circular dependencies are visible; the call graph is a clear tree from main.cpp → settings → model → scanners/launchers → UI.

How To Use It

# Clone the upstream repository
git clone https://github.com/moses-y/omakade.git
cd omakade

# Configure with the provided dev preset (requires CMake ≥3.24, Ninja, Qt 6.8+, SDL 3, libsecret)
cmake --preset dev
cmake --build --preset dev   # builds ./build/dev/omakade
ctest --preset dev           # runs the two unit tests in tests/CoreTests.cpp
./build/dev/omakade          # launches the application

Configuration files are stored under ~/.config/omakade/. No environment variables are required for a default install. To run the demo library without scanning real launchers, start the binary with --demo (documented in README.md).

Real‑World Use

A user on an Arch‑based desktop installs Omakade alongside their existing Steam, Lutris, and Heroic installations. After the first launch, Omakade scans each launcher’s local database, presents a single, Omarchy‑styled grid of covers, and launches games via the original client when the user selects a title. Achievements from Steam appear inline, and IGDB ratings are fetched on demand, all without contacting the launchers’ cloud services.

Code Health & Issues

  • Low – No test coverage for UI layer – only tests/CoreTests.cpp exists; UI components are untested.
  • Low – Hard‑coded asset paths – QML references assets via relative paths (docs/assets/…) which may break if the install layout changes.
  • Low – Limited platform abstraction – launch commands are embedded in each concrete launcher (SteamLauncher.cpp, RetroArchLauncher.cpp), making addition of a new platform a moderate effort but not a blocker.

Static analysis (CI workflow ci.yml) reports no missing licenses, lockfiles, or structural warnings.

The Bottom Line

omakade delivers a functional, Qt‑based aggregator for Linux game launchers with a clean Omarchy UI and a modular codebase that isolates launcher‑specific logic. It is ready for developers who need a local, offline‑first game library and are comfortable extending the scanner/launcher interfaces to add new sources. Missing UI tests and some hard‑coded paths suggest modest maintenance effort before production deployment.