diff --git a/.gitignore b/.gitignore index 1ead167..4b0947a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /subprojects/blueprint-compiler /.flatpak +/.flatpak-builder /.vscode \ No newline at end of file diff --git a/src/utils/bottles_parser.py b/src/utils/bottles_parser.py index 85fd3c1..a2426ab 100644 --- a/src/utils/bottles_parser.py +++ b/src/utils/bottles_parser.py @@ -95,7 +95,6 @@ def bottles_parser(parent_widget, action): with open(os.path.join(bottles_dir, "library.yml"), "r") as open_file: data = open_file.read() - open_file.close() library = yaml.load(data, Loader=yaml.Loader) @@ -112,9 +111,10 @@ def bottles_parser(parent_widget, action): continue values["name"] = game["name"] - values["executable"] = "xdg-open " + shlex.quote( + values["executable"] = [ + "xdg-open", f'bottles:run/{game["bottle"]["name"]}/{game["name"]}' - ) + ] values["hidden"] = False values["source"] = "bottles" values["added"] = current_time diff --git a/src/utils/create_details_window.py b/src/utils/create_details_window.py index 0ca5137..96b71df 100644 --- a/src/utils/create_details_window.py +++ b/src/utils/create_details_window.py @@ -19,6 +19,7 @@ import json import os +import shlex import time from gi.repository import Adw, GdkPixbuf, Gio, GLib, GObject, Gtk @@ -52,7 +53,7 @@ def create_details_window(parent_widget, game_id=None): ) name = Gtk.Entry.new_with_buffer(Gtk.EntryBuffer.new(games[game_id].name, -1)) executable = Gtk.Entry.new_with_buffer( - Gtk.EntryBuffer.new((games[game_id].executable), -1) + Gtk.EntryBuffer.new(shlex.join(games[game_id].executable), -1) ) apply_button = Gtk.Button.new_with_label(_("Apply")) @@ -201,6 +202,21 @@ def create_details_window(parent_widget, game_id=None): final_developer = developer.get_buffer().get_text() final_executable = executable.get_buffer().get_text() + try: + # Attempt to parse using shell parsing rules (doesn't verify executable existence). + final_executable_split = shlex.split( + final_executable, comments=False, posix=True + ) + except Exception as e: + create_dialog( + window, + _("Couldn't Add Game") + if game_id is None + else _("Couldn't Apply Preferences"), + f'{_("Executable")}: {e}.', # e = Shell parsing error message. Not translatable. + ) + return + if game_id is None: if final_name == "": create_dialog( @@ -252,7 +268,7 @@ def create_details_window(parent_widget, game_id=None): values["name"] = final_name values["developer"] = final_developer or None - values["executable"] = final_executable + values["executable"] = final_executable_split path = os.path.join( os.path.join( @@ -267,7 +283,6 @@ def create_details_window(parent_widget, game_id=None): if os.path.exists(path): with open(path, "r") as open_file: data = json.loads(open_file.read()) - open_file.close() data.update(values) save_games({game_id: data}) else: diff --git a/src/utils/get_games.py b/src/utils/get_games.py index d2e0423..dd40ed8 100644 --- a/src/utils/get_games.py +++ b/src/utils/get_games.py @@ -41,6 +41,6 @@ def get_games(game_ids=None): for game in game_files: with open(os.path.join(games_dir, game), "r") as open_file: data = json.loads(open_file.read()) - open_file.close() - games[data["game_id"]] = data + games[data["game_id"]] = data + return games diff --git a/src/utils/heroic_parser.py b/src/utils/heroic_parser.py index 494f6c4..8597f7f 100644 --- a/src/utils/heroic_parser.py +++ b/src/utils/heroic_parser.py @@ -107,7 +107,6 @@ def heroic_parser(parent_widget, action): os.path.join(heroic_dir, "lib-cache", "library.json"), "r" ) as open_file: data = open_file.read() - open_file.close() library = json.loads(data) try: @@ -129,9 +128,9 @@ def heroic_parser(parent_widget, action): values["name"] = game["title"] values["developer"] = game["developer"] values["executable"] = ( - f"start heroic://launch/{app_name}" + ["start", f"heroic://launch/{app_name}"] if os.name == "nt" - else f"xdg-open heroic://launch/{app_name}" + else ["xdg-open", f"heroic://launch/{app_name}"] ) values["hidden"] = False values["source"] = "heroic_epic" @@ -160,7 +159,6 @@ def heroic_parser(parent_widget, action): os.path.join(heroic_dir, "gog_store", "installed.json"), "r" ) as open_file: data = open_file.read() - open_file.close() installed = json.loads(data) for item in installed["installed"]: values = {} @@ -179,7 +177,6 @@ def heroic_parser(parent_widget, action): os.path.join(heroic_dir, "gog_store", "library.json"), "r" ) as open_file: data = open_file.read() - open_file.close() library = json.loads(data) for game in library["games"]: if game["app_name"] == app_name: @@ -195,9 +192,9 @@ def heroic_parser(parent_widget, action): break values["executable"] = ( - f"start heroic://launch/{app_name}" + ["start", f"heroic://launch/{app_name}"] if os.name == "nt" - else f"xdg-open heroic://launch/{app_name}" + else ["xdg-open", f"heroic://launch/{app_name}"] ) values["hidden"] = False values["source"] = "heroic_gog" @@ -214,7 +211,6 @@ def heroic_parser(parent_widget, action): os.path.join(heroic_dir, "sideload_apps", "library.json"), "r" ) as open_file: data = open_file.read() - open_file.close() library = json.loads(data) for item in library["games"]: values = {} @@ -230,9 +226,9 @@ def heroic_parser(parent_widget, action): values["name"] = item["title"] values["executable"] = ( - f"start heroic://launch/{app_name}" + ["start", f"heroic://launch/{app_name}"] if os.name == "nt" - else f"xdg-open heroic://launch/{app_name}" + else ["xdg-open", f"heroic://launch/{app_name}"] ) values["hidden"] = False values["source"] = "heroic_sideload" diff --git a/src/utils/run_command.py b/src/utils/run_command.py index 1400128..78b1d0f 100644 --- a/src/utils/run_command.py +++ b/src/utils/run_command.py @@ -18,6 +18,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import os +import shlex import subprocess import sys @@ -25,13 +26,14 @@ from gi.repository import Gio def run_command(executable): + # The host environment vars are automatically passed through by Popen. subprocess.Popen( - [f"flatpak-spawn --host {executable}"] + ["flatpak-spawn", "--host", *executable] # Flatpak if os.getenv("FLATPAK_ID") == "hu.kramo.Cartridges" - else executable.split() + else executable # Windows if os.name == "nt" - else [executable], - shell=True, + else executable, # Linux/Others + shell=False, # If true, the extra arguments would incorrectly be given to the shell instead. start_new_session=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == "nt" else 0, ) diff --git a/src/utils/save_games.py b/src/utils/save_games.py index 858f08c..854c140 100644 --- a/src/utils/save_games.py +++ b/src/utils/save_games.py @@ -35,4 +35,3 @@ def save_games(games): for game in games: with open(os.path.join(games_dir, f"{game}.json"), "w") as open_file: open_file.write(json.dumps(games[game], indent=4, sort_keys=True)) - open_file.close() diff --git a/src/utils/steam_parser.py b/src/utils/steam_parser.py index bfcc1ef..0f65860 100644 --- a/src/utils/steam_parser.py +++ b/src/utils/steam_parser.py @@ -49,7 +49,6 @@ def get_game(task, datatypes, current_time, parent_widget, appmanifest, steam_di with open(appmanifest, "r") as open_file: data = open_file.read() - open_file.close() for datatype in datatypes: value = re.findall(f'"{datatype}"\t\t"(.*)"\n', data) values[datatype] = value[0] @@ -64,9 +63,9 @@ def get_game(task, datatypes, current_time, parent_widget, appmanifest, steam_di return values["executable"] = ( - f'start steam://rungameid/{values["appid"]}' + ["start", f'steam://rungameid/{values["appid"]}'] if os.name == "nt" - else f'xdg-open steam://rungameid/{values["appid"]}' + else ["xdg-open", f'steam://rungameid/{values["appid"]}'] ) values["hidden"] = False values["source"] = "steam" diff --git a/src/utils/toggle_hidden.py b/src/utils/toggle_hidden.py index 10aa284..21dc9b1 100644 --- a/src/utils/toggle_hidden.py +++ b/src/utils/toggle_hidden.py @@ -36,7 +36,6 @@ def toggle_hidden(game): with open(os.path.join(games_dir, f"{game}.json"), "r") as open_file: data = json.loads(open_file.read()) - open_file.close() data["hidden"] = not data["hidden"]