diff --git a/data/hu.kramo.Cartridges.gschema.xml b/data/hu.kramo.Cartridges.gschema.xml
index 3d112f4..b35be61 100644
--- a/data/hu.kramo.Cartridges.gschema.xml
+++ b/data/hu.kramo.Cartridges.gschema.xml
@@ -18,6 +18,9 @@
"~/.var/app/com.heroicgameslauncher.hgl/config/heroic/"
+
+
+ "~/.var/app/com.usebottles.bottles/data/bottles/"
diff --git a/hu.kramo.Cartridges.json b/hu.kramo.Cartridges.json
index 51185ab..8cd34f1 100644
--- a/hu.kramo.Cartridges.json
+++ b/hu.kramo.Cartridges.json
@@ -11,7 +11,8 @@
"--socket=wayland",
"--talk-name=org.freedesktop.Flatpak",
"--filesystem=~/.steam/steam/",
- "--filesystem=~/.var/app/com.heroicgameslauncher.hgl/config/heroic/"
+ "--filesystem=~/.var/app/com.heroicgameslauncher.hgl/config/heroic/",
+ "--filesystem=~/.var/app/com.usebottles.bottles/data/bottles/"
],
"cleanup" : [
"/include",
@@ -25,6 +26,7 @@
"*.a"
],
"modules" : [
+ "python3-PyYAML.json",
{
"name" : "cartridges",
"builddir" : true,
diff --git a/python3-PyYAML.json b/python3-PyYAML.json
new file mode 100644
index 0000000..8393db4
--- /dev/null
+++ b/python3-PyYAML.json
@@ -0,0 +1,14 @@
+{
+ "name": "python3-PyYAML",
+ "buildsystem": "simple",
+ "build-commands": [
+ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"PyYAML>=6.0\" --no-build-isolation"
+ ],
+ "sources": [
+ {
+ "type": "file",
+ "url": "https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz",
+ "sha256": "68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index c08e061..32eaedf 100644
--- a/src/main.py
+++ b/src/main.py
@@ -31,6 +31,7 @@ from .save_games import save_games
from .run_command import run_command
from .steam_parser import steam_parser
from .heroic_parser import heroic_parser
+from .bottles_parser import bottles_parser
from .create_details_window import create_details_window
class CartridgesApplication(Adw.Application):
@@ -41,6 +42,7 @@ class CartridgesApplication(Adw.Application):
self.create_action("preferences", self.on_preferences_action)
self.create_action("steam_import", self.on_steam_import_action)
self.create_action("heroic_import", self.on_heroic_import_action)
+ self.create_action("bottles_import", self.on_bottles_import_action)
self.create_action("launch_game", self.on_launch_game_action)
self.create_action("hide_game", self.on_hide_game_action)
self.create_action("edit_details", self.on_edit_details_action)
@@ -50,26 +52,26 @@ class CartridgesApplication(Adw.Application):
def do_activate(self):
# Create the main window
- win = self.props.active_window
- if not win:
- win = CartridgesWindow(application=self)
+ self.win = self.props.active_window
+ if not self.win:
+ self.win = CartridgesWindow(application=self)
- win.present()
+ self.win.present()
# Create actions for the main window
- self.create_action("show_hidden", win.on_show_hidden_action, None, win)
- self.create_action("go_back", win.on_go_back_action, ["Left"], win)
- self.create_action("go_to_parent", win.on_go_to_parent_action, ["Up"], win)
- self.create_action("toggle_search", win.on_toggle_search_action, ["f"], win)
- self.create_action("escape", win.on_escape_action, ["Escape"], win)
- self.create_action("undo_remove", win.on_undo_remove_action, ["z"], win)
- win.sort = Gio.SimpleAction.new_stateful("sort_by", GLib.VariantType.new("s"), GLib.Variant("s", "a-z"))
- win.add_action(win.sort)
- win.sort.connect("activate", win.on_sort_action)
- win.on_sort_action(win.sort, win.schema.get_value("sort-mode"))
+ self.create_action("show_hidden", self.win.on_show_hidden_action, None, self.win)
+ self.create_action("go_back", self.win.on_go_back_action, ["Left"], self.win)
+ self.create_action("go_to_parent", self.win.on_go_to_parent_action, ["Up"], self.win)
+ self.create_action("toggle_search", self.win.on_toggle_search_action, ["f"], self.win)
+ self.create_action("escape", self.win.on_escape_action, ["Escape"], self.win)
+ self.create_action("undo_remove", self.win.on_undo_remove_action, ["z"], self.win)
+ self.win.sort = Gio.SimpleAction.new_stateful("sort_by", GLib.VariantType.new("s"), GLib.Variant("s", "a-z"))
+ self.win.add_action(self.win.sort)
+ self.win.sort.connect("activate", self.win.on_sort_action)
+ self.win.on_sort_action(self.win.sort, self.win.schema.get_value("sort-mode"))
def on_about_action(self, widget, callback=None):
- about = Adw.AboutWindow(transient_for=self.props.active_window,
+ about = Adw.AboutWindow(transient_for=self.win,
application_name="Cartridges",
application_icon="hu.kramo.Cartridges",
developer_name="kramo",
@@ -83,62 +85,67 @@ class CartridgesApplication(Adw.Application):
about.present()
def on_preferences_action(self, widget, callback=None):
- PreferencesWindow(self.props.active_window).present()
+ PreferencesWindow(self.win).present()
def on_steam_import_action(self, widget, callback=None):
- games = steam_parser(self.props.active_window, self.on_steam_import_action)
+ games = steam_parser(self.win, self.on_steam_import_action)
save_games(games)
- self.props.active_window.update_games(games.keys())
+ self.win.update_games(games.keys())
def on_heroic_import_action(self, widget, callback=None):
- games = heroic_parser(self.props.active_window, self.on_heroic_import_action)
+ games = heroic_parser(self.win, self.on_heroic_import_action)
save_games(games)
- self.props.active_window.update_games(games.keys())
+ self.win.update_games(games.keys())
+
+ def on_bottles_import_action(self, widget, callback=None):
+ games = bottles_parser(self.win, self.on_bottles_import_action)
+ save_games(games)
+ self.win.update_games(games.keys())
def on_launch_game_action(self, widget, callback=None):
# Launch the game and update the last played value
- self.props.active_window.games[self.props.active_window.active_game_id]["last_played"] = int(time.time())
- save_games({self.props.active_window.active_game_id : self.props.active_window.games[self.props.active_window.active_game_id]})
- self.props.active_window.update_games([self.props.active_window.active_game_id])
- run_command(self.props.active_window, self.props.active_window.games[self.props.active_window.active_game_id]["executable"])
+ self.win.games[self.win.active_game_id]["last_played"] = int(time.time())
+ save_games({self.win.active_game_id : self.win.games[self.win.active_game_id]})
+ self.win.update_games([self.win.active_game_id])
+ run_command(self.win, self.win.games[self.win.active_game_id]["executable"])
- if self.props.active_window.stack.get_visible_child() == self.props.active_window.overview:
- self.props.active_window.show_overview(None, self.props.active_window.active_game_id)
+ if self.win.stack.get_visible_child() == self.win.overview:
+ self.win.show_overview(None, self.win.active_game_id)
def on_hide_game_action(self, widget, callback=None):
- if self.props.active_window.stack.get_visible_child() == self.props.active_window.overview:
- self.props.active_window.on_go_back_action(None, None)
- toggle_hidden(self.props.active_window.active_game_id)
- self.props.active_window.update_games([self.props.active_window.active_game_id])
+ if self.win.stack.get_visible_child() == self.win.overview:
+ self.win.on_go_back_action(None, None)
+ toggle_hidden(self.win.active_game_id)
+ self.win.update_games([self.win.active_game_id])
def on_edit_details_action(self, widget, callback=None):
- create_details_window(self.props.active_window, self.props.active_window.active_game_id)
+ create_details_window(self.win, self.win.active_game_id)
def on_add_game_action(self, widget, callback=None):
- create_details_window(self.props.active_window)
+ create_details_window(self.win)
def on_remove_game_action(self, widget, callback=None):
# Add "removed=True" to the game properties so it can be deleted on next init
- game_id = self.props.active_window.active_game_id
+ game_id = self.win.active_game_id
open_file = open(os.path.join(os.path.join(os.environ.get("XDG_DATA_HOME"), "cartridges", "games", game_id + ".json")), "r")
data = json.loads(open_file.read())
open_file.close()
data["removed"] = True
save_games({game_id : data})
- self.props.active_window.update_games([game_id])
- if self.props.active_window.stack.get_visible_child() == self.props.active_window.overview:
- self.props.active_window.on_go_back_action(None, None)
+ self.win.update_games([game_id])
+ if self.win.stack.get_visible_child() == self.win.overview:
+ self.win.on_go_back_action(None, None)
# Create toast for undoing the remove action
- toast = Adw.Toast.new(self.props.active_window.games[game_id]["name"] + " " + (_("removed")))
+ toast = Adw.Toast.new(self.win.games[game_id]["name"] + " " + (_("removed")))
toast.set_button_label(_("Undo"))
- toast.connect("button-clicked", self.props.active_window.on_undo_remove_action, game_id)
+ toast.connect("button-clicked", self.win.on_undo_remove_action, game_id)
toast.set_priority(Adw.ToastPriority.HIGH)
- self.props.active_window.toasts[game_id] = toast
- self.props.active_window.toast_overlay.add_toast(toast)
+ self.win.toasts[game_id] = toast
+ self.win.toast_overlay.add_toast(toast)
def on_quit_action(self, widget, callback=None):
self.quit()
diff --git a/src/meson.build b/src/meson.build
index c49ca9a..be9faba 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -45,6 +45,7 @@ cartridges_sources = [
'game.py',
'utils/steam_parser.py',
'utils/heroic_parser.py',
+ 'utils/bottles_parser.py',
'utils/run_command.py',
'utils/get_games.py',
'utils/get_cover.py',
diff --git a/src/utils/bottles_parser.py b/src/utils/bottles_parser.py
new file mode 100644
index 0000000..73c8f00
--- /dev/null
+++ b/src/utils/bottles_parser.py
@@ -0,0 +1,90 @@
+# bottles_parser.py
+#
+# Copyright 2022 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
+
+def bottles_parser(parent_widget, action):
+ import os, yaml, time
+
+ from gi.repository import Gtk, GLib
+
+ from .create_dialog import create_dialog
+ from .save_cover import save_cover
+
+ schema = parent_widget.schema
+ bottles_dir = os.path.expanduser(os.path.join(schema.get_string("bottles-location")))
+
+ def bottles_not_found():
+ filechooser = Gtk.FileDialog.new()
+
+ def set_bottles_dir(source, result, _):
+ try:
+ schema.set_string("bottles-location", filechooser.select_folder_finish(result).get_path())
+ bottles_dir = bottles_dir = os.path.join(schema.get_string("bottles-location"))
+ action(None, None)
+ except GLib.GError:
+ return
+
+ def choose_folder(widget):
+ filechooser.select_folder(parent_widget, None, set_bottles_dir, None)
+
+ def response(widget, response):
+ if response == "choose_folder":
+ choose_folder(widget)
+
+ create_dialog(parent_widget, _("Couldn't Import Games"), _("Bottles directory cannot be found."), "choose_folder", _("Set Bottles Location")).connect("response", response)
+
+ if os.path.isfile(os.path.join(bottles_dir, "library.yml")):
+ pass
+ else:
+ bottles_not_found()
+ return {}
+
+ datatypes = ["path", "id", "name", "thumbnail"]
+ bottles_games = {}
+ current_time = int(time.time())
+
+ open_file = open(os.path.join(bottles_dir, "library.yml"), "r")
+ data = open_file.read()
+ open_file.close()
+
+ library = yaml.load(data, Loader=yaml.Loader)
+
+ for game in library:
+ game = library[game]
+ values = {}
+
+ values["game_id"] = "bottles_" + game["id"]
+ values["name"] = game["name"]
+ values["executable"] = "xdg-open bottles:run/" + game["bottle"]["name"] + "/" + game["name"]
+ values["hidden"] = False
+ values["source"] = "bottles"
+ values["added"] = current_time
+ values["last_played"] = 0
+
+ if game["thumbnail"]:
+ values["pixbuf_options"] = save_cover(values, parent_widget, os.path.join(bottles_dir, "bottles", game["bottle"]["path"], "grids", game["thumbnail"].replace("grid:", "")))
+
+ bottles_games[values["game_id"]] = values
+
+ if len(bottles_games) == 0:
+ create_dialog(parent_widget, _("No Games Found"), _("No new games found in Bottles library."))
+ elif len(bottles_games) == 1:
+ create_dialog(parent_widget, _("bottles Games Imported"), _("Successfully imported 1 game."))
+ elif len(bottles_games) > 1:
+ create_dialog(parent_widget, _("bottles Games Imported"), _("Successfully imported") + " " + str(len(bottles_games)) + " " + _("games."))
+ return bottles_games
diff --git a/src/utils/heroic_parser.py b/src/utils/heroic_parser.py
index 73e6e62..ec44fc2 100644
--- a/src/utils/heroic_parser.py
+++ b/src/utils/heroic_parser.py
@@ -99,6 +99,7 @@ def heroic_parser(parent_widget, action):
values["pixbuf_options"] = save_cover(values, parent_widget, image_path)
break
+
heroic_games[values["game_id"]] = values
# Import GOG games
@@ -138,6 +139,7 @@ def heroic_parser(parent_widget, action):
values["source"] = "heroic_gog"
values["added"] = current_time
values["last_played"] = 0
+
heroic_games[values["game_id"]] = values
# Import sideloaded games
@@ -168,6 +170,7 @@ def heroic_parser(parent_widget, action):
hashlib.sha256(item["art_square"].encode()).hexdigest())
if os.path.exists(image_path):
values["pixbuf_options"] = save_cover(values, parent_widget, image_path)
+
heroic_games[values["game_id"]] = values
if len(heroic_games) == 0:
diff --git a/src/utils/steam_parser.py b/src/utils/steam_parser.py
index e59f3ad..88300fb 100644
--- a/src/utils/steam_parser.py
+++ b/src/utils/steam_parser.py
@@ -77,7 +77,7 @@ def steam_parser(parent_widget, action):
open_file.close()
for datatype in datatypes:
value = re.findall("\"" + datatype + "\"\t\t\"(.*)\"\n", data)
- values [datatype] = value[0]
+ values[datatype] = value[0]
values["game_id"] = "steam_" + values["appid"]
@@ -92,6 +92,7 @@ def steam_parser(parent_widget, action):
if os.path.isfile(os.path.join(steam_dir, "appcache", "librarycache", values["appid"] + "_library_600x900.jpg")):
values["pixbuf_options"] = save_cover(values, parent_widget, os.path.join(steam_dir, "appcache", "librarycache", values["appid"] + "_library_600x900.jpg"))
+
steam_games[values["game_id"]] = values
if len(steam_games) == 0:
diff --git a/src/window.blp b/src/window.blp
index 47240c1..1af916b 100644
--- a/src/window.blp
+++ b/src/window.blp
@@ -360,15 +360,24 @@ menu add_games {
label: _("Add Game");
action: "app.add_game";
}
+ }
+ section {
+ submenu {
+ label: _("Import from");
+ item {
+ label: _("Steam");
+ action: "app.steam_import";
+ }
- item {
- label: _("Import From Steam");
- action: "app.steam_import";
- }
+ item {
+ label: _("Heroic");
+ action: "app.heroic_import";
+ }
- item {
- label: _("Import From Heroic");
- action: "app.heroic_import";
+ item {
+ label: _("Bottles");
+ action: "app.bottles_import";
+ }
}
}
}