Game Library Management
Relevant source files
The following files were used as context for generating this wiki page:The Game Library Management system is the core of the SOFL application, responsible for storing, displaying, and managing a user's collection of games. It provides a unified interface for games aggregated from various sources, including manual additions, Online-Fix installations, and imports from platforms like Steam, Lutris, and Heroic. The system is built around a central data store, a main window for presentation, and various dialogs for user interaction.
The architecture separates game data (GameData) from its visual representation (Game), allowing for efficient UI updates and data persistence. The main window (SOFLWindow) orchestrates the display, filtering, sorting, and interaction with the game library, while specialized dialogs handle tasks like adding, editing, and installing games.
Architecture Overview
The library management system is composed of several key classes that work together to manage game data and the user interface. The SOFLApplication class initializes the main components, including the central Store and the SOFLWindow. The SOFLWindow is responsible for all visual aspects of the library, such as the game grid, sidebar, and details page.
Each game in the library is represented by a Game widget, which is a Gtk.Box. The underlying data for each game is held in a GameData object. This separation allows the data to be manipulated independently of the UI, with changes being communicated through GObject signals.
Sources: sofl/main.py, sofl/window.py, sofl/game.py, sofl/game_data.py
Core Component Relationships
The following diagram illustrates the relationship between the main classes involved in game library management.
This diagram shows that SOFLApplication holds the SOFLWindow and the Store. The SOFLWindow displays multiple Game widgets, each of which is backed by a GameData object. The DetailsDialog is used to create or modify Game objects.
Sources: sofl/main.py:80, sofl/window.py:25, sofl/game.py:41, sofl/game_data.py:25, sofl/details_dialog.py:72
Game Data Model
The foundation of the game library is the GameData class, which encapsulates all properties and non-UI logic for a single game. This GObject.Object stores attributes like the game's name, executable path, source, and user-managed state (e.g., hidden, last played).
Sources: sofl/game_data.py
GameData Attributes
The key attributes of the GameData class are summarized below. These properties are loaded from and saved to individual JSON files in the games directory.
| Attribute | Type | Description | Source File |
|---|---|---|---|
game_id | str | A unique identifier for the game (e.g., steam_440, online-fix_1). | sofl/game_data.py:40 |
name | str | The display name of the game. | sofl/game_data.py:44 |
developer | Optional[str] | The developer of the game. | sofl/game_data.py:45 |
executable | str | The command or path to launch the game. | sofl/game_data.py:39 |
source | str | The origin of the game (e.g., steam, lutris, online-fix, imported). | sofl/game_data.py:41 |
base_source | str | The root source, derived from source (e.g., steam from steam_440). | sofl/game_data.py:60 |
hidden | bool | True if the game is hidden from the main library view. | sofl/game_data.py:42 |
removed | bool | True if the game is marked for removal. | sofl/game_data.py:46 |
last_played | int | A Unix timestamp of when the game was last launched. | sofl/game_data.py:43 |
added | int | A Unix timestamp of when the game was added to the library. | sofl/game_data.py:38 |
Data-UI Communication
The GameData object communicates with its corresponding Game widget using GObject signals. This decouples the data logic from the UI implementation.
update-ready: Emitted when the UI needs to be refreshed with the latest data (e.g., after a name change).save-ready: Emitted when the game's data needs to be persisted to its JSON file.toast: Emitted to request that the main window show a notification to the user (e.g., "Game launched").
Sources: sofl/game_data.py:30-32, sofl/game.py:58-66
Library Display and Interaction
The SOFLWindow class manages the main user interface for the library. It uses a Gtk.FlowBox to display Game widgets in a grid.
Sources: sofl/window.py
Main Views
- Library View: The primary grid of game covers, managed by
self.library(Gtk.FlowBox). - Hidden Library View: A separate view for games marked as hidden, managed by
self.hidden_library. - Details View: A dedicated page (
self.details_page) that shows detailed information for a selected game, including a blurred background, developer info, and action buttons. - Sidebar: A
Gtk.ListBox(self.sidebar) that allows filtering the library by game source (e.g., "All Games", "Added", "Steam").
Sources: sofl/window.py:41-78
Sorting and Filtering
The library view can be sorted and filtered dynamically.
- Filtering: The
filter_funcinSOFLWindowdetermines which games are visible. It checks against the search term entered in theGtk.SearchEntryand the source selected in the sidebar. The search is case-insensitive and matches against the game's name and developer. - Sorting: The
sort_funcorders the games based on the current sort mode (sort_state), which can belast_played,newest,oldest, ora-z. The sort mode is persisted in GSettings.
Sources: sofl/window.py:308-333, 396-417
Game Details Page
When a user selects a game, the show_details_page method is called. This populates the details view with the selected game's information, including its cover, title, developer, and last played/added dates. The background of this view is a blurred version of the game's cover, with opacity adjusted based on the system theme (light/dark) to ensure text readability.
Sources: sofl/window.py:338-381
Adding and Editing Games
Games can be added to the library manually or through an installation process. The DetailsDialog is the primary interface for both adding a new game and editing an existing one.
Sources: sofl/details_dialog.py
Add/Edit Flow
The apply_preferences method in DetailsDialog contains the core logic for saving changes.
- Input Validation: It first checks that the game
nameandexecutablefields are not empty. - Game Creation (Add Mode): If adding a new game (
self.gameisNone), it determines thesourcebased on the "Game Type" dropdown ("Online-Fix" or "Regular Game"). It then generates a new, uniquegame_idby finding the highest existing number for that source and incrementing it. A newGameobject is created usingGameFactory. - Game Update (Edit Mode): If editing an existing game, it updates the game's properties. It also handles changing a game's type, which involves moving it between source groups in the central store.
- Save and Update: The method saves the cover image if it was changed, adds/updates the game in the
Store, saves the game's data to its JSON file, and updates the UI.
The following diagram shows the logic flow for adding or editing a game.
Game Lifecycle and Actions
The system manages the full lifecycle of a game within the library, from addition to launch and removal.
Game Launch and Process Tracking
When a user initiates a launch, the GameData.launch() method is called. This method updates the last_played timestamp and then calls run_executable_with_tracking to start the game process.
The SOFLWindow is notified of the new process via on_game_launched. It stores the process object and game data in its running_processes dictionary. A periodic timer (process_check_timer) polls the status of running processes. When a process terminates, it is removed from the dictionary, and the status bar is updated. The status bar provides visual feedback on whether a game is running and includes a "Stop" button to terminate it.
Sources: sofl/game_data.py:101-114, sofl/window.py:539-612
The sequence diagram below shows the game launch and tracking process.
Hiding and Removing Games
- Hiding: The
toggle_hidden()method onGameDataflips thehiddenboolean flag. The game is then moved to or from the "Hidden" view in the UI. A toast notification confirms the action. - Removing: The
remove_game()method sets theremovedflag toTrue. Games with this flag are not loaded into the UI but their data files are kept, allowing for potential "undo" functionality. TheImporteralso uses this flag to mark games that are no longer found in their source location.
Sources: sofl/game_data.py:116-129, sofl/importer/importer.py:165-183
Game Covers
Game covers are managed by the GameCover class. Each Game widget and the DetailsDialog maintain a GameCover instance. This class handles loading the cover image (.gif or .tiff) and providing it to Gtk.Picture widgets. It also handles creating a blurred version for the details page background. Covers can be set manually through the DetailsDialog or fetched automatically from SteamGridDB if a game is missing one.