Fix/gamelauncher nixos build (#225)

* game-launcher: bundle self-contained SQLite reader, drop libsqlite3 dependency

Fixes building gamelauncher.c on NixOS where the runtime env lacks
sqlite3.h/libsqlite3. scan_lutris now reads Lutris' pga.db through a
bundled read-only SQLite file parser instead of linking sqlite3.

* game-launcher: rework /g provider to reuse panel scan results

The launcher provider now reads the games the panel already scanned via
state/cache, trims the query, and surfaces status/error rows instead of
duplicating the scan.

---------

Co-authored-by: Ahmed5Emad <ahmed5emad@users.noreply.github.com>
This commit is contained in:
Ahmed Emad
2026-08-02 23:12:29 -04:00
committed by GitHub
co-authored by Ahmed5Emad
parent cf1bda889a
commit b5098f8eed
7 changed files with 557 additions and 134 deletions
+99 -67
View File
@@ -5,7 +5,7 @@
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sqlite3.h>
#include "sqlite_reader.h"
#include <libgen.h>
#include <ctype.h>
#include <time.h>
@@ -393,6 +393,70 @@ void scan_steam() {
free(steamapps_paths);
}
typedef struct {
SqliteTable *t;
int col_id;
int col_name;
int col_slug;
int col_runner;
int col_installed;
} LutrisCtx;
static int lutris_row_cb(void *ctxp, long long rowid, const unsigned char *rec, int reclen) {
LutrisCtx *c = ctxp;
SqliteRecHeader h;
if (!sq_parse_header(rec, reclen, &h)) return 0;
char id[MAX_STR] = "0";
char name[MAX_STR] = "";
char slug[MAX_STR] = "";
char runner[MAX_STR] = "linux";
if (c->t->id_is_rowid)
snprintf(id, sizeof(id), "%lld", rowid);
else if (c->col_id >= 0)
sq_col_text(&h, rec, reclen, c->col_id, id, sizeof(id));
int has_name = sq_col_text(&h, rec, reclen, c->col_name, name, sizeof(name));
int has_slug = sq_col_text(&h, rec, reclen, c->col_slug, slug, sizeof(slug));
if (!has_name || !has_slug) return 0;
if (game_exists(name)) return 0;
int has_runner = sq_col_text(&h, rec, reclen, c->col_runner, runner, sizeof(runner));
if (!has_runner) strncpy(runner, "linux", sizeof(runner) - 1);
char cover[MAX_PATH] = "";
const char *cover_dirs[] = {
"/.local/share/lutris/coverart",
"/.var/app/net.lutris.Lutris/data/lutris/coverart",
"/.cache/lutris/coverart",
NULL
};
for (int cd = 0; cover_dirs[cd]; cd++) {
char cover_dir[MAX_PATH];
snprintf(cover_dir, sizeof(cover_dir), "%s%s", get_home(), cover_dirs[cd]);
const char *exts[] = {".jpg", ".png", ".jpeg", NULL};
for (int e = 0; exts[e]; e++) {
char fp[MAX_PATH];
snprintf(fp, sizeof(fp), "%s/%s%s", cover_dir, slug, exts[e]);
if (file_exists(fp)) {
strncpy(cover, fp, MAX_PATH - 1);
break;
}
}
if (cover[0]) break;
}
char run_command[MAX_RUN_CMD];
snprintf(run_command, sizeof(run_command), "lutris:rungame/%s", slug);
add_game(id, name, "lutris", cover, "", run_command, slug);
return 0;
}
void scan_lutris() {
char db_paths[8][MAX_PATH];
int num_dbs = 0;
@@ -474,80 +538,48 @@ void scan_lutris() {
if (!chosen_db) return;
sqlite3 *db;
int rc = sqlite3_open_v2(chosen_db, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) return;
SqliteDb db;
if (!sq_open(&db, chosen_db)) return;
sqlite3_stmt *stmt;
struct {
const char *name;
int require_installed;
int filter_installed;
} cands[] = {
{"games", 1, 1},
{"installed_game", 0, 0},
{"game", 1, 1},
{NULL, 0, 0}
};
const char *query =
"SELECT id, name, slug, runner "
"FROM games WHERE installed = 1";
SqliteTable t;
rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
for (int i = 0; cands[i].name; i++) {
memset(&t, 0, sizeof(t));
if (!sq_find_table(&db, cands[i].name, &t)) continue;
if (rc != SQLITE_OK) {
const char *alt_queries[] = {
"SELECT id, name, slug, runner FROM installed_game",
"SELECT id, name, slug, runner FROM game WHERE installed = 1",
NULL
};
int col_id = sq_column_index(&t, "id");
int col_name = sq_column_index(&t, "name");
int col_slug = sq_column_index(&t, "slug");
int col_runner = sq_column_index(&t, "runner");
if (col_id < 0 || col_name < 0 || col_slug < 0 || col_runner < 0) continue;
for (int q = 0; alt_queries[q]; q++) {
rc = sqlite3_prepare_v2(db, alt_queries[q], -1, &stmt, NULL);
if (rc == SQLITE_OK) break;
}
int col_installed = sq_column_index(&t, "installed");
if (cands[i].require_installed && col_installed < 0) continue;
if (rc != SQLITE_OK) {
sqlite3_close(db);
return;
}
LutrisCtx ctx;
ctx.t = &t;
ctx.col_id = col_id;
ctx.col_name = col_name;
ctx.col_slug = col_slug;
ctx.col_runner = col_runner;
ctx.col_installed = cands[i].filter_installed ? col_installed : -1;
sq_walk_table(&db, t.rootpage, lutris_row_cb, &ctx);
break;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *id = (const char *)sqlite3_column_text(stmt, 0);
const char *name = (const char *)sqlite3_column_text(stmt, 1);
const char *slug = (const char *)sqlite3_column_text(stmt, 2);
const char *runner = (const char *)sqlite3_column_text(stmt, 3);
if (!name || !slug) continue;
if (game_exists(name)) continue;
char cover[MAX_PATH] = "";
const char *cover_dirs[] = {
"/.local/share/lutris/coverart",
"/.var/app/net.lutris.Lutris/data/lutris/coverart",
"/.cache/lutris/coverart",
NULL
};
for (int c = 0; cover_dirs[c]; c++) {
char cd[MAX_PATH];
snprintf(cd, sizeof(cd), "%s%s", get_home(), cover_dirs[c]);
const char *exts[] = {".jpg", ".png", ".jpeg", NULL};
for (int e = 0; exts[e]; e++) {
char fp[MAX_PATH];
snprintf(fp, sizeof(fp), "%s/%s%s", cd, slug, exts[e]);
if (file_exists(fp)) {
strncpy(cover, fp, MAX_PATH - 1);
break;
}
}
if (cover[0]) break;
}
char run_command[MAX_RUN_CMD];
snprintf(run_command, sizeof(run_command), "lutris:rungame/%s", slug);
if (!id) id = "0";
if (!runner) runner = "linux";
add_game(id, name, "lutris", cover, "", run_command, slug ? slug : "");
}
sqlite3_finalize(stmt);
sqlite3_close(db);
sq_close(&db);
}
void scan_lutris_manual_files() {