Support for Heroic import
This commit is contained in:
@@ -3,14 +3,14 @@ using Adw 1;
|
||||
|
||||
template PreferencesWindow : Adw.PreferencesWindow {
|
||||
search-enabled: false;
|
||||
default-height: 400;
|
||||
default-height: 500;
|
||||
|
||||
Adw.PreferencesPage {
|
||||
Adw.PreferencesGroup {
|
||||
title: _("General");
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Exit after launching a game");
|
||||
title: _("Exit After Launching Games");
|
||||
|
||||
Switch exit_after_launch_switch {
|
||||
valign: center;
|
||||
@@ -22,7 +22,7 @@ template PreferencesWindow : Adw.PreferencesWindow {
|
||||
title: "Steam";
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Steam install location");
|
||||
title: _("Steam Install Location");
|
||||
subtitle: _("Directory to use when importing games");
|
||||
|
||||
Button steam_file_chooser_button {
|
||||
@@ -31,5 +31,43 @@ template PreferencesWindow : Adw.PreferencesWindow {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
title: "Heroic";
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Heroic Install Location");
|
||||
subtitle: _("Directory to use when importing games");
|
||||
|
||||
Button heroic_file_chooser_button {
|
||||
icon-name: "folder-symbolic";
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Import Epic Games");
|
||||
|
||||
Switch import_epic_games_switch {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Import GOG Games");
|
||||
|
||||
Switch import_gog_games_switch {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Import Sideloaded Games");
|
||||
|
||||
Switch import_sideload_games_switch {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/main.py
11
src/main.py
@@ -30,6 +30,7 @@ from .toggle_hidden import toggle_hidden
|
||||
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 .create_details_window import create_details_window
|
||||
|
||||
class GameShelfApplication(Adw.Application):
|
||||
@@ -39,6 +40,7 @@ class GameShelfApplication(Adw.Application):
|
||||
self.create_action("about", self.on_about_action)
|
||||
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("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)
|
||||
@@ -71,7 +73,7 @@ class GameShelfApplication(Adw.Application):
|
||||
application_name="Game Shelf",
|
||||
application_icon="hu.kramo.GameShelf",
|
||||
developer_name="kramo",
|
||||
version="0.1.0",
|
||||
version="0.1.1",
|
||||
developers=["kramo"],
|
||||
copyright="© 2022 kramo",
|
||||
license_type=Gtk.License.GPL_3_0)
|
||||
@@ -85,6 +87,11 @@ class GameShelfApplication(Adw.Application):
|
||||
save_games(games)
|
||||
self.props.active_window.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)
|
||||
save_games(games)
|
||||
self.props.active_window.update_games(games.keys())
|
||||
|
||||
def on_launch_game_action(self, widget, callback=None):
|
||||
|
||||
# Launch the game and update the last played value
|
||||
@@ -123,7 +130,7 @@ class GameShelfApplication(Adw.Application):
|
||||
self.props.active_window.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.props.active_window.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.set_priority(Adw.ToastPriority.HIGH)
|
||||
|
||||
@@ -44,6 +44,7 @@ gameshelf_sources = [
|
||||
'preferences.py',
|
||||
'game.py',
|
||||
'utils/steam_parser.py',
|
||||
'utils/heroic_parser.py',
|
||||
'utils/run_command.py',
|
||||
'utils/get_games.py',
|
||||
'utils/get_cover.py',
|
||||
|
||||
@@ -24,7 +24,12 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
||||
__gtype_name__ = 'PreferencesWindow'
|
||||
|
||||
exit_after_launch_switch = Gtk.Template.Child()
|
||||
import_epic_games_switch = Gtk.Template.Child()
|
||||
import_gog_games_switch = Gtk.Template.Child()
|
||||
import_sideload_games_switch = Gtk.Template.Child()
|
||||
|
||||
steam_file_chooser_button = Gtk.Template.Child()
|
||||
heroic_file_chooser_button = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, parent_widget, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -32,6 +37,9 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
||||
self.set_transient_for(parent_widget)
|
||||
schema = parent_widget.schema
|
||||
schema.bind("exit-after-launch", self.exit_after_launch_switch, "active", Gio.SettingsBindFlags.DEFAULT)
|
||||
schema.bind("heroic-import-epic", self.import_epic_games_switch, "active", Gio.SettingsBindFlags.DEFAULT)
|
||||
schema.bind("heroic-import-gog", self.import_gog_games_switch, "active", Gio.SettingsBindFlags.DEFAULT)
|
||||
schema.bind("heroic-import-sideload", self.import_sideload_games_switch, "active", Gio.SettingsBindFlags.DEFAULT)
|
||||
|
||||
filechooser = Gtk.FileDialog()
|
||||
|
||||
@@ -41,7 +49,14 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
||||
except GLib.GError:
|
||||
pass
|
||||
|
||||
def choose_folder(widget):
|
||||
filechooser.select_folder(parent_widget, None, None, set_steam_dir, None)
|
||||
def set_heroic_dir(source, result, user_data):
|
||||
try:
|
||||
schema.set_string("heroic-location", filechooser.select_folder_finish(result).get_path())
|
||||
except GLib.GError:
|
||||
pass
|
||||
|
||||
self.steam_file_chooser_button.connect("clicked", choose_folder)
|
||||
def choose_folder(widget, function):
|
||||
filechooser.select_folder(parent_widget, None, None, function, None)
|
||||
|
||||
self.steam_file_chooser_button.connect("clicked", choose_folder, set_steam_dir)
|
||||
self.heroic_file_chooser_button.connect("clicked", choose_folder, set_heroic_dir)
|
||||
|
||||
158
src/utils/heroic_parser.py
Normal file
158
src/utils/heroic_parser.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# heroic_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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
def heroic_parser(parent_widget, action):
|
||||
import os, json, time
|
||||
|
||||
from gi.repository import Gtk, GLib
|
||||
|
||||
from .create_dialog import create_dialog
|
||||
from .save_cover import save_cover
|
||||
|
||||
schema = parent_widget.schema
|
||||
heroic_dir = os.path.expanduser(os.path.join(schema.get_string("heroic-location")))
|
||||
|
||||
def heroic_not_found():
|
||||
filechooser = Gtk.FileDialog.new()
|
||||
|
||||
def set_heroic_dir(source, result, _):
|
||||
try:
|
||||
schema.set_string("heroic-location", filechooser.select_folder_finish(result).get_path())
|
||||
heroic_dir = heroic_dir = os.path.join(schema.get_string("heroic-location"))
|
||||
action(None, None)
|
||||
except GLib.GError:
|
||||
return
|
||||
|
||||
def choose_folder(widget):
|
||||
filechooser.select_folder(parent_widget, None, set_heroic_dir, None)
|
||||
|
||||
def response(widget, response):
|
||||
if response == "choose_folder":
|
||||
choose_folder(widget)
|
||||
|
||||
create_dialog(parent_widget, _("Couldn't Import Games"), _("Heroic directory cannot be found."), "choose_folder", _("Set Heroic Location")).connect("response", response)
|
||||
|
||||
if os.path.exists(os.path.join(heroic_dir, "config.json")) == True:
|
||||
pass
|
||||
else:
|
||||
heroic_not_found()
|
||||
return {}
|
||||
|
||||
heroic_games = {}
|
||||
current_time = int(time.time())
|
||||
|
||||
# Import Epic games
|
||||
if schema.get_boolean("heroic-import-epic") == False:
|
||||
pass
|
||||
elif os.path.exists(os.path.join(heroic_dir, "lib-cache", "installInfo.json")) == True:
|
||||
open_file = open(os.path.join(heroic_dir, "lib-cache", "installInfo.json"), "r")
|
||||
data = open_file.read()
|
||||
open_file.close()
|
||||
installInfo = json.loads(data)
|
||||
for item in installInfo:
|
||||
if installInfo[item]["install"] != None:
|
||||
values = {}
|
||||
app_name = installInfo[item]["game"]["app_name"]
|
||||
|
||||
values["game_id"] = "heroic_epic_" + app_name
|
||||
|
||||
if values["game_id"] in parent_widget.games and "removed" not in parent_widget.games[values["game_id"]].keys():
|
||||
continue
|
||||
|
||||
values["name"] = installInfo[item]["game"]["title"]
|
||||
values["executable"] = "xdg-open heroic://launch/" + app_name
|
||||
values["hidden"] = False
|
||||
values["source"] = "heroic_epic"
|
||||
values["added"] = current_time
|
||||
values["last_played"] = 0
|
||||
if os.path.isfile(os.path.join(heroic_dir, "icons", app_name + ".jpg")) == True:
|
||||
values["pixbuf_options"] = save_cover(values, parent_widget, os.path.join(os.path.join(heroic_dir, "icons", app_name + ".jpg")))
|
||||
|
||||
heroic_games[values["game_id"]] = values
|
||||
|
||||
# Import GOG games
|
||||
if schema.get_boolean("heroic-import-gog") == False:
|
||||
pass
|
||||
elif os.path.exists(os.path.join(heroic_dir, "gog_store", "installed.json")) == True:
|
||||
open_file = open(os.path.join(heroic_dir, "gog_store", "installed.json"), "r")
|
||||
data = open_file.read()
|
||||
open_file.close()
|
||||
installed = json.loads(data)
|
||||
for item in installed["installed"]:
|
||||
values = {}
|
||||
app_name = item["appName"]
|
||||
|
||||
values["game_id"] = "heroic_gog_" + app_name
|
||||
|
||||
if values["game_id"] in parent_widget.games and "removed" not in parent_widget.games[values["game_id"]].keys():
|
||||
continue
|
||||
|
||||
# Get game title from library.json as it's not present in installed.json
|
||||
open_file = open(os.path.join(heroic_dir, "gog_store", "library.json"), "r")
|
||||
data = open_file.read()
|
||||
open_file.close()
|
||||
library = json.loads(data)
|
||||
for game in library["games"]:
|
||||
if game["app_name"] == app_name:
|
||||
values["name"] = game["title"]
|
||||
break
|
||||
|
||||
values["executable"] = "xdg-open heroic://launch/" + app_name
|
||||
values["hidden"] = False
|
||||
values["source"] = "heroic_gog"
|
||||
values["added"] = current_time
|
||||
values["last_played"] = 0
|
||||
if os.path.isfile(os.path.join(heroic_dir, "icons", app_name + ".jpg")) == True:
|
||||
values["pixbuf_options"] = save_cover(values, parent_widget, os.path.join(os.path.join(heroic_dir, "icons", app_name + ".jpg")))
|
||||
heroic_games[values["game_id"]] = values
|
||||
|
||||
# Import sideloaded games
|
||||
if schema.get_boolean("heroic-import-sideload") == False:
|
||||
pass
|
||||
elif os.path.exists(os.path.join(heroic_dir, "sideload_apps", "library.json")) == True:
|
||||
open_file = open(os.path.join(heroic_dir, "sideload_apps", "library.json"), "r")
|
||||
data = open_file.read()
|
||||
open_file.close()
|
||||
library = json.loads(data)
|
||||
for item in library["games"]:
|
||||
values = {}
|
||||
app_name = item["app_name"]
|
||||
|
||||
values["game_id"] = "heroic_sideload_" + app_name
|
||||
|
||||
if values["game_id"] in parent_widget.games and "removed" not in parent_widget.games[values["game_id"]].keys():
|
||||
continue
|
||||
|
||||
values["name"] = item["title"]
|
||||
values["executable"] = "xdg-open heroic://launch/" + app_name
|
||||
values["hidden"] = False
|
||||
values["source"] = "heroic_sideload"
|
||||
values["added"] = current_time
|
||||
values["last_played"] = 0
|
||||
if os.path.isfile(os.path.join(heroic_dir, "icons", app_name + ".jpg")) == True:
|
||||
values["pixbuf_options"] = save_cover(values, parent_widget, os.path.join(os.path.join(heroic_dir, "icons", app_name + ".jpg")))
|
||||
heroic_games[values["game_id"]] = values
|
||||
|
||||
if len(heroic_games) == 0:
|
||||
create_dialog(parent_widget, _("No Games Found"), _("No new games found in Heroic library."))
|
||||
elif len(heroic_games) == 1:
|
||||
create_dialog(parent_widget, _("Heroic Games Imported"), _("Successfully imported 1 game."))
|
||||
elif len(heroic_games) > 1:
|
||||
create_dialog(parent_widget, _("Heroic Games Imported"), _("Successfully imported") + " " + str(len(heroic_games)) + " " + _("games."))
|
||||
return heroic_games
|
||||
@@ -58,23 +58,23 @@ def steam_parser(parent_widget, action):
|
||||
steam_not_found()
|
||||
return {}
|
||||
|
||||
steam_dir = os.path.join(schema.get_string("steam-location"))
|
||||
steam_dir = schema.get_string("steam-location")
|
||||
|
||||
appmanifests = []
|
||||
datatypes = ["appid", "name"]
|
||||
steam_games = {}
|
||||
current_time = int(time.time())
|
||||
|
||||
for file in os.listdir(os.path.join(steam_dir, "steamapps")):
|
||||
path = os.path.join(steam_dir, "steamapps", file)
|
||||
if os.path.isfile(path) and "appmanifest" in file:
|
||||
for open_file in os.listdir(os.path.join(steam_dir, "steamapps")):
|
||||
path = os.path.join(steam_dir, "steamapps", open_file)
|
||||
if os.path.isfile(path) and "appmanifest" in open_file:
|
||||
appmanifests.append(path)
|
||||
|
||||
for appmanifest in appmanifests:
|
||||
values = {}
|
||||
file = open(appmanifest, "r")
|
||||
data = file.read()
|
||||
file.close()
|
||||
open_file = open(appmanifest, "r")
|
||||
data = open_file.read()
|
||||
open_file.close()
|
||||
for datatype in datatypes:
|
||||
value = re.findall("\"" + datatype + "\"\t\t\"(.*)\"\n", data)
|
||||
values [datatype] = value[0]
|
||||
@@ -99,5 +99,5 @@ def steam_parser(parent_widget, action):
|
||||
elif len(steam_games) == 1:
|
||||
create_dialog(parent_widget, _("Steam Games Imported"), _("Successfully imported 1 game."))
|
||||
elif len(steam_games) > 1:
|
||||
create_dialog(parent_widget, _("Steam Games Imported"), _("Successfully imported ") + str(len(steam_games)) + _(" games."))
|
||||
create_dialog(parent_widget, _("Steam Games Imported"), _("Successfully imported") + " " + str(len(steam_games)) + " " + _("games."))
|
||||
return steam_games
|
||||
|
||||
@@ -365,6 +365,11 @@ menu add_games {
|
||||
label: _("Import From Steam");
|
||||
action: "app.steam_import";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Import From Heroic");
|
||||
action: "app.heroic_import";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user