diff --git a/data/gtk/preferences.blp b/data/gtk/preferences.blp
index bdd4c70..85dfa1e 100644
--- a/data/gtk/preferences.blp
+++ b/data/gtk/preferences.blp
@@ -108,6 +108,30 @@ template PreferencesWindow : Adw.PreferencesWindow {
}
}
+ Adw.ExpanderRow lutris_expander_row {
+ title: _("Lutris");
+ show-enable-switch: true;
+
+ Adw.ActionRow {
+ title: _("Lutris Install Location");
+ subtitle: _("Directory to use when importing games");
+
+ Button lutris_file_chooser_button {
+ icon-name: "folder-symbolic";
+ valign: center;
+ }
+ }
+ Adw.ActionRow {
+ title: _("Lutris Cache Location");
+ subtitle: _("Directory to use when importing game covers");
+
+ Button lutris_cache_file_chooser_button {
+ icon-name: "folder-symbolic";
+ valign: center;
+ }
+ }
+ }
+
Adw.ExpanderRow heroic_expander_row {
title: _("Heroic");
show-enable-switch: true;
diff --git a/data/hu.kramo.Cartridges.gschema.xml b/data/hu.kramo.Cartridges.gschema.xml
index b0304d0..23d1ae6 100644
--- a/data/hu.kramo.Cartridges.gschema.xml
+++ b/data/hu.kramo.Cartridges.gschema.xml
@@ -19,6 +19,15 @@
[]
+
+ true
+
+
+ "~/.var/app/net.lutris.Lutris/data/lutris/"
+
+
+ "~/.var/app/net.lutris.Lutris/cache/lutris"
+
true
diff --git a/hu.kramo.Cartridges.json b/hu.kramo.Cartridges.json
index 9c6624a..6dc752a 100644
--- a/hu.kramo.Cartridges.json
+++ b/hu.kramo.Cartridges.json
@@ -13,9 +13,12 @@
"--talk-name=org.gtk.vfs.*",
"--filesystem=xdg-run/gvfsd",
"--filesystem=~/.steam/steam/:ro",
+ "--filesystem=xdg-data/lutris/:ro",
+ "--filesystem=xdg-cache/lutris/:ro",
"--filesystem=xdg-config/heroic/:ro",
"--filesystem=xdg-data/bottles/:ro",
"--filesystem=~/.var/app/com.valvesoftware.Steam/data/Steam/:ro",
+ "--filesystem=~/.var/app/net.lutris.Lutris/:ro",
"--filesystem=~/.var/app/com.heroicgameslauncher.hgl/config/heroic/:ro",
"--filesystem=~/.var/app/com.usebottles.bottles/data/bottles/:ro"
],
diff --git a/src/main.py b/src/main.py
index 6f0baac..b23675a 100644
--- a/src/main.py
+++ b/src/main.py
@@ -33,6 +33,7 @@ from .create_details_window import create_details_window
from .get_games import get_games
from .heroic_parser import heroic_parser
from .importer import Importer
+from .lutris_parser import lutris_parser
from .preferences import PreferencesWindow
from .save_game import save_game
from .steam_parser import steam_parser
@@ -168,6 +169,9 @@ class CartridgesApplication(Adw.Application):
if self.win.schema.get_boolean("steam"):
steam_parser(self.win)
+ if self.win.schema.get_boolean("lutris"):
+ lutris_parser(self.win)
+
if self.win.schema.get_boolean("heroic"):
heroic_parser(self.win)
diff --git a/src/meson.build b/src/meson.build
index f2cd13c..5bbaae6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -26,6 +26,7 @@ cartridges_sources = [
'utils/steam_parser.py',
'utils/heroic_parser.py',
'utils/bottles_parser.py',
+ 'utils/lutris_parser.py',
'utils/get_games.py',
'utils/save_game.py',
'utils/save_cover.py',
diff --git a/src/preferences.py b/src/preferences.py
index 20a9841..8af5263 100644
--- a/src/preferences.py
+++ b/src/preferences.py
@@ -98,6 +98,10 @@ class PreferencesWindow(Adw.PreferencesWindow):
steam_clear_button_revealer = Gtk.Template.Child()
steam_clear_button = Gtk.Template.Child()
+ lutris_expander_row = Gtk.Template.Child()
+ lutris_file_chooser_button = Gtk.Template.Child()
+ lutris_cache_file_chooser_button = Gtk.Template.Child()
+
heroic_expander_row = Gtk.Template.Child()
heroic_file_chooser_button = Gtk.Template.Child()
heroic_epic_switch = Gtk.Template.Child()
@@ -191,6 +195,45 @@ class PreferencesWindow(Adw.PreferencesWindow):
)
self.steam_clear_button.connect("clicked", clear_steam_dirs)
+ # Lutris
+ ImportPreferences(
+ self,
+ "lutris",
+ "Lutris",
+ "lutris-location",
+ ["pga.db"],
+ self.lutris_expander_row,
+ self.lutris_file_chooser_button,
+ )
+
+ def set_cache_dir(_source, result, _unused):
+ try:
+ path = self.file_chooser.select_folder_finish(result).get_path()
+
+ def response(widget, response):
+ if response == "choose_folder":
+ self.choose_folder(widget, set_cache_dir)
+
+ if not os.path.exists(os.path.join(path, "coverart")):
+ create_dialog(
+ self.parent_widget,
+ _("Cache Not Found"),
+ _("Select the Lutris cache directory."),
+ "choose_folder",
+ _("Set Location"),
+ ).connect("response", response)
+ else:
+ self.schema.set_string(
+ "lutris-cache-location",
+ path,
+ )
+ except GLib.GError:
+ pass
+
+ self.lutris_cache_file_chooser_button.connect(
+ "clicked", self.choose_folder, set_cache_dir
+ )
+
# Heroic
ImportPreferences(
self,
diff --git a/src/utils/bottles_parser.py b/src/utils/bottles_parser.py
index 9beee8d..35bccb4 100644
--- a/src/utils/bottles_parser.py
+++ b/src/utils/bottles_parser.py
@@ -18,7 +18,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import os
-import time
+from time import time
import yaml
@@ -53,7 +53,7 @@ def bottles_parser(parent_widget):
return
bottles_dir = os.path.expanduser(schema.get_string("bottles-location"))
- current_time = int(time.time())
+ current_time = int(time())
with open(os.path.join(bottles_dir, "library.yml"), "r") as open_file:
data = open_file.read()
diff --git a/src/utils/heroic_parser.py b/src/utils/heroic_parser.py
index 6671e55..e78998a 100644
--- a/src/utils/heroic_parser.py
+++ b/src/utils/heroic_parser.py
@@ -20,7 +20,7 @@
import hashlib
import json
import os
-import time
+from time import time
def heroic_parser(parent_widget):
@@ -60,7 +60,7 @@ def heroic_parser(parent_widget):
return
heroic_dir = os.path.expanduser(schema.get_string("heroic-location"))
- current_time = int(time.time())
+ current_time = int(time())
importer = parent_widget.importer
diff --git a/src/utils/lutris_parser.py b/src/utils/lutris_parser.py
new file mode 100644
index 0000000..beb58b9
--- /dev/null
+++ b/src/utils/lutris_parser.py
@@ -0,0 +1,149 @@
+# lutris_parser.py
+#
+# Copyright 2022-2023 kramo
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import os
+import shutil
+from sqlite3 import connect
+from time import time
+
+
+def lutris_parser(parent_widget):
+
+ schema = parent_widget.schema
+
+ database_path = os.path.join(
+ os.path.expanduser(schema.get_string("lutris-location")), "pga.db"
+ )
+ if not os.path.isfile(database_path):
+ if os.path.exists(
+ os.path.expanduser("~/.var/app/net.lutris.Lutris/data/lutris/")
+ ):
+ schema.set_string(
+ "lutris-location", "~/.var/app/net.lutris.Lutris/data/lutris/"
+ )
+ elif os.path.exists(
+ os.path.join(
+ os.getenv("XDG_DATA_HOME")
+ or os.path.expanduser(os.path.join("~", ".local", "share")),
+ "lutris",
+ )
+ ):
+ schema.set_string(
+ "lutris-location",
+ os.path.join(
+ os.getenv("XDG_DATA_HOME")
+ or os.path.expanduser(os.path.join("~", ".local", "share")),
+ "lutris",
+ ),
+ )
+ else:
+ return
+
+ cache_dir = os.path.expanduser(schema.get_string("lutris-cache-location"))
+ if not os.path.exists(cache_dir):
+ if os.path.exists(
+ os.path.expanduser("~/.var/app/net.lutris.Lutris/cache/lutris/")
+ ):
+ schema.set_string(
+ "lutris-cache-location", "~/.var/app/net.lutris.Lutris/cache/lutris/"
+ )
+ elif os.path.exists(
+ os.path.join(
+ os.getenv("XDG_CACHE_HOME")
+ or os.path.expanduser(os.path.join("~", ".cache")),
+ "lutris",
+ )
+ ):
+ schema.set_string(
+ "lutris-cache-location",
+ os.path.join(
+ os.getenv("XDG_CACHE_HOME")
+ or os.path.expanduser(os.path.join("~", ".cache")),
+ "lutris",
+ ),
+ )
+ else:
+ return
+
+ database_path = os.path.join(
+ os.path.expanduser(schema.get_string("lutris-location")), "pga.db"
+ )
+ cache_dir = os.path.expanduser(schema.get_string("lutris-cache-location"))
+
+ db_cache_dir = os.path.join(
+ os.getenv("XDG_CACHE_HOME") or os.path.expanduser(os.path.join("~", ".cache")),
+ "cartridges",
+ "lutris",
+ )
+ os.makedirs(db_cache_dir, exist_ok=True)
+
+ shutil.copyfile(database_path, os.path.join(db_cache_dir, "pga.db"))
+
+ db_request = """
+ SELECT
+ id, name, slug, runner, hidden
+ FROM
+ 'games'
+ WHERE
+ name IS NOT NULL
+ AND slug IS NOT NULL
+ AND configPath IS NOT NULL
+ AND installed IS TRUE
+ ;
+ """
+
+ connection = connect(os.path.join(db_cache_dir, "pga.db"))
+ cursor = connection.execute(db_request)
+ rows = cursor.fetchall()
+ connection.close()
+
+ if schema.get_boolean("steam"):
+ rows = [row for row in rows if not row[3] == "steam"]
+
+ current_time = int(time())
+
+ importer = parent_widget.importer
+ importer.total_queue += len(rows)
+ importer.queue += len(rows)
+
+ for row in rows:
+ values = {}
+
+ values["game_id"] = f"lutris_{row[3]}_{row[0]}"
+
+ if (
+ values["game_id"] in parent_widget.games
+ and not parent_widget.games[values["game_id"]].removed
+ ):
+ importer.save_game()
+ continue
+
+ values["added"] = current_time
+ values["executable"] = ["xdg-open", f"lutris:rungameid/{row[0]}"]
+ values["hidden"] = row[4] == 1
+ values["last_played"] = 0
+ values["name"] = row[1]
+ values["source"] = f"lutris_{row[3]}"
+
+ if os.path.isfile(os.path.join(cache_dir, "coverart", f"{row[2]}.jpg")):
+ importer.save_cover(
+ values["game_id"], os.path.join(cache_dir, "coverart", f"{row[2]}.jpg")
+ )
+
+ importer.save_game(values)
diff --git a/src/utils/save_cover.py b/src/utils/save_cover.py
index 9f868ad..a4c9711 100644
--- a/src/utils/save_cover.py
+++ b/src/utils/save_cover.py
@@ -30,8 +30,7 @@ def save_cover(parent_widget, game_id, cover_path, pixbuf=None):
"covers",
)
- if not os.path.exists(covers_dir):
- os.makedirs(covers_dir)
+ os.makedirs(covers_dir, exist_ok=True)
if pixbuf is None:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(cover_path, 400, 600, False)
diff --git a/src/utils/save_game.py b/src/utils/save_game.py
index b93b28d..4ecda93 100644
--- a/src/utils/save_game.py
+++ b/src/utils/save_game.py
@@ -29,8 +29,7 @@ def save_game(game):
"games",
)
- if not os.path.exists(games_dir):
- os.makedirs(games_dir)
+ os.makedirs(games_dir, exist_ok=True)
with open(os.path.join(games_dir, f'{game["game_id"]}.json'), "w") as open_file:
open_file.write(json.dumps(game, indent=4, sort_keys=True))
diff --git a/src/utils/steam_parser.py b/src/utils/steam_parser.py
index e10f954..23fcf37 100644
--- a/src/utils/steam_parser.py
+++ b/src/utils/steam_parser.py
@@ -20,8 +20,8 @@
import json
import os
import re
-import time
import urllib.request
+from time import time
from gi.repository import Gio, GLib
@@ -113,7 +113,7 @@ def get_game(
def get_games_async(parent_widget, appmanifests, steam_dir, importer):
datatypes = ["appid", "name"]
- current_time = int(time.time())
+ current_time = int(time())
# Wrap the function in another one as Gio.Task.run_in_thread does not allow for passing args
def create_func(datatypes, current_time, parent_widget, appmanifest, steam_dir):