2025.03.10 21:27:56 (desktop)

This commit is contained in:
Christian Moser 2025-03-10 21:27:56 +01:00
parent 09555bd3ec
commit 5a36b4f358
Failed to extract signature
7 changed files with 582 additions and 90 deletions

View File

@ -85,8 +85,7 @@ EOF
cat > "${venv_bin}/gsgbackup.cmd" << EOF
@ECHO OFF
cd $Env:userprofile
"$wvenv_bin\\gsgbackup.ps1" %*
powershell -File "$wvenv_bin\\gsgbackup.ps1" %*
EOF
unix2dos "${bindir}/gsgbackup.cmd"

193
sgbackup/epic.py Normal file
View File

@ -0,0 +1,193 @@
###############################################################################
# sgbackup - The SaveGame Backup tool #
# Copyright (C) 2024,2025 Christian Moser #
# #
# 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 <https://www.gnu.org/licenses/>. #
###############################################################################
import sys,os
from gi.repository import GLib
from gi.repository.GObject import GObject,Signal,SignalFlags,Property
from .settings import settings
import json
import logging
from i18n import gettext as _
logger = logging.getLogger(__name__)
PLATFORM_WINDOWS = sys.platform.lower().startswith('win')
class EpicGameInfo(GObject):
def __init__(self,
name:str,
installdir:str,
appname:str,
main_appname:str):
GObject.__init__(self)
self.__name = name
self.__installdir = installdir
self.__appname = appname
self.__main_appname = main_appname
@Property(type=str)
def name(self)->str:
return self.__name
@Property(type=str)
def installdir(self)->str:
return self.__installdir
@Property(type=str)
def appname(self)->str:
return self.__appname
@Property(type=str)
def main_appname(self)->str:
return self.__main_appname
@Property(type=bool,default=False)
def is_main(self)->bool:
return (self.appname == self.main_appname)
class EpicIgnoredApp(GObject):
def __init__(self,appname:str,name:str,reason:str):
GObject.__init__(self)
self.__appname = appname
self.__name = name
self.__reason = reason
@Property(type=str)
def appname(self)->str:
return self.__appname
@Property(type=str)
def name(self)->str:
return self.__name
@Property(type=str)
def reason(self)->str:
return self.__reason
def serialize(self):
return {
'appname':self.appname,
'name':self.name,
'reason':self.reason,
}
class Epic(GObject):
_logger = logger.getChild('Epic')
def __init__(self):
GObject.__init__(self)
self.__ignored_apps=self.__parse_ignore_file()
@Property(type=str)
def ignore_file(self)->str:
return os.path.join(settings.config_dir,'epic.ignore')
def __parse_ignore_file(self)->dict[str:EpicIgnoredApp]:
ret = {}
if os.path.isfile(self.ignore_file):
with open(self.ignore_file,'r',encoding="urf-8") as ifile:
data = json.loads(ifile.read())
for i in data:
ret[i['appname']] = EpicIgnoredApp(i['appname'],i['name'],i['reason'])
return ret
def __write_ignore_file(self):
with open(self.ignore_file,'w',encoding="utf-8") as ofile:
ofile.write(json.dumps(
[v.serialize() for v in self.__ignored_apps.values()],
ensure_ascii=False,
indent=4))
def add_ignored_app(self,app:EpicIgnoredApp):
if not isinstance(app,EpicIgnoredApp):
raise TypeError('app is not an EpicIgnoredApp instance!')
self.__ignored_apps[app.appname] = app
self.__write_ignore_file()
def remove_ignored_apps(self,app:str|EpicIgnoredApp):
if isinstance(app,str):
appname = app
elif isinstance(app,EpicIgnoredApp):
appname = app.appname
else:
raise TypeError("app is not a string and not an EpicIgnoredApp instance!")
if appname in self.__ignored_apps:
del self.__ignored_apps[appname]
self.__write_ignore_file()
@Property
def ignored_apps(self)->dict[str:EpicIgnoredApp]:
return self.__ignored_apps
@Property(type=str)
def datadir(self):
return settings.epic_datadir if settings.epic_datadir is not None else ""
def parse_manifest(self,filename)->EpicGameInfo|None:
if not os.path.exists(filename):
return None
if not filename.endswith('.item'):
return None
try:
with open(filename,'r',encoding="utf-8") as ifile:
data = json.loads(ifile.read())
except Exception as ex:
self._logger.error(_("Unable ot load Epic manifest \"{manifest}\"! ({error})").format(
manifest=filename,
error=str(ex)
))
return None
if data['FormatVersion'] == 0:
return EpicGameInfo(
name=data['DisplayName'],
installdir=data['InstallLocation'],
appname=data['AppName'],
main_appname=data['MainGameAppName']
)
return None
def parse_all_manifests(self)->list[EpicGameInfo]:
manifest_dir=os.path.join(settings.epic_datadir,'Manifests')
ret = []
for item in [ i for i in os.listdir(manifest_dir) if i.endswith('.item') ]:
manifest_file = os.path.join(manifest_dir,item)
info = self.parse_manifest(manifest_file)
if info is not None:
ret += info
return ret
def get_apps(self)->list[EpicGameInfo]:
return [i for i in self.parse_all_manifests() if i.appname == i.main_appname]
def get_new_apps(self)->list[EpicGameInfo]:
return []

View File

@ -385,6 +385,11 @@ class GameData(GObject):
raise TypeError("\"ignore_match\" needs to be \"None\" or a list of \"GameFileMatcher\" instances!")
self.__ignorematchers = list(im)
@Property(type=bool,default=False)
def is_valid(self)->bool:
return (bool(self.__savegame_root) and bool(self.__savegame_dir) and (self.__savegame_type != SavegameType.UNSET))
def has_variable(self,name:str)->bool:
"""
has_variable Check if variable exists.
@ -1036,6 +1041,7 @@ class SteamMacOSData(SteamPlatformData):
installdir=installdir,
librarydir=librarydir)
class SteamGameData(GObject):
def __init__(self,appid:int,
windows:SteamWindowsData|None=None,
@ -1095,24 +1101,24 @@ class SteamGameData(GObject):
def linux_game(self)->SteamLinuxGame|None:
if self.linux:
return SteamLinuxGame(appid=self.appid,
savegame_root=self.windows.savegame_root,
savegame_dir=self.windows.savegame_dir,
variables=self.windows.variables,
installdir=self.windows.installdir,
file_match=self.windows.file_matchers,
ignore_match=self.windows.ignore_matchers)
savegame_root=self.linux.savegame_root,
savegame_dir=self.linux.savegame_dir,
variables=self.linux.variables,
installdir=self.linux.installdir,
file_match=self.linux.file_matchers,
ignore_match=self.linux.ignore_matchers)
return None
@property
def macos_game(self)->SteamLinuxGame|None:
if self.macos:
return SteamMacOSGame(appid=self.appid,
savegame_root=self.windows.savegame_root,
savegame_dir=self.windows.savegame_dir,
variables=self.windows.variables,
installdir=self.windows.installdir,
file_match=self.windows.file_matchers,
ignore_match=self.windows.ignore_matchers)
savegame_root=self.macos.savegame_root,
savegame_dir=self.macos.savegame_dir,
variables=self.macos.variables,
installdir=self.macos.installdir,
file_match=self.macos.file_matchers,
ignore_match=self.macos.ignore_matchers)
return None
def serialize(self)->dict:
@ -1127,7 +1133,99 @@ class SteamGameData(GObject):
data['macos'] = self.macos.serialize()
return data
class EpicPlatformData(GameData):
def __init__(self,
savegame_type:SavegameType,
savegame_root:str,
savegame_dir:str,
variables:dict[str:str],
file_match:list[GameFileMatcher],
ignore_match:list[GameFileMatcher],
installdir:str|None):
if savegame_type not in (SavegameType.EPIC_WINDOWS,SavegameType.EPIC_LINUX):
raise ValueError("Savegame type needs to be EPIC_WINDOWS or EPIC_LINUX!")
GameData.__init__(self,
savegame_type=savegame_type,
savegame_root=savegame_root,
savegame_dir=savegame_dir,
variables=variables,
file_match=file_match,
ignore_match=ignore_match)
self.__installdir=installdir
@Property
def installdir(self)->str:
return self.__installdir if self.__installdir else ""
@installdir.setter
def installdir(self,directory:str|None):
self.__installdir = directory
def serialize(self):
data = super().serialize()
if self.__installdir:
data['installdir'] = self.__installdir
return data
class EpicWindowsData(EpicPlatformData):
def __init__(self,
savegame_root:str,
savegame_dir:str,
variables:dict[str:str],
file_match:list[GameFileMatcher],
ignore_match:list[GameFileMatcher],
installdir:str|None):
GameData.__init__(self,
savegame_type=SavegameType.EPIC_WINDOWS,
savegame_root=savegame_root,
savegame_dir=savegame_dir,
variables=variables,
file_match=file_match,
ignore_match=ignore_match,
installdir=installdir)
class EpicGameData(GObject):
def __init__(self,appname:str,windows:EpicWindowsData|None):
GObject.__init__(self)
self.__appname = appname
self.windows = windows
@Property(type=str)
def appname(self)->str:
return self.__appname
@appname.setter
def appname(self,appname:str):
self.__appname = appname
@Property
def windows(self)->EpicWindowsData|None:
return self.__windows
@windows.setter
def windows(self,data:EpicWindowsData|None):
self.__windows = data
@Property(type=bool,default=False)
def is_valid(self)->bool:
if (self.windows and self.windows.is_valid):
return True
def serialize(self):
ret = {
"appname": self.appname
}
if self.windows and self.windows.is_valid:
ret["windows"] = self.windows.serialize()
return ret
class Game(GObject):
__gtype_name__ = "Game"
@ -1212,7 +1310,36 @@ class Game(GObject):
linux=linux,
macos=macos)
# new_steamdata()
def new_epic_data(conf:dict):
def new_epic_platform_data(data,cls):
if not 'savegame_root' in data or not 'savegame_dir' in data:
return None
file_match,ignore_match = get_file_match(data)
return cls(
savegame_root=data['savegame_root'],
savegame_dir=data['savegame_dir'],
variables=dict(((v['name'],v['value']) for v in data['variables'])) if ('variables' in data and config['variables']) else None,
file_match=file_match,
ignore_match=ignore_match,
installdir=data['installdir'] if ('installdir' in data and data['installdir']) else None,
librarydir=data['librarydir'] if ('librarydir' in data and data['librarydir']) else None
)
if not "epic" in conf or not "appname" in conf["epic"]:
return None
if ("windows" in conf['epic']):
windows = new_epic_platform_data(conf['windows'],EpicWindowsData)
else:
windows = None
return EpicGameData(conf['epic']['appname'],
windows=windows)
# new_epic_data()
if not 'key' in config or not 'name' in config:
return None
@ -1267,6 +1394,7 @@ class Game(GObject):
game.macos = MacOSGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
game.steam = new_steamdata(config)
game.epic = new_epic_data(config)
return game
@ -1300,13 +1428,7 @@ class Game(GObject):
self.__steam = None
self.__epic = None
self.__gog = None
self.__steam_windows = None
self.__steam_linux = None
self.__steam_macos = None
self.__gog_windows = None
self.__gog_linux = None
self.__epic_windows = None
self.__epic_linux = None
self.__epic = None
@Property(type=str)
def dbid(self)->str:
@ -1405,19 +1527,23 @@ class Game(GObject):
elif (sgtype == SavegameType.MACOS):
return self.macos
elif (sgtype == SavegameType.STEAM_WINDOWS):
return self.steam.windows_game
if self.steam:
return self.steam.windows_game
elif (sgtype == SavegameType.STEAM_LINUX):
return self.steam.linux_game
if self.steam:
return self.steam.linux_game
elif (sgtype == SavegameType.STEAM_MACOS):
return self.steam.macos_game
elif (sgtype == SavegameType.GOG_WINDOWS):
return self.__gog_windows
elif (sgtype == SavegameType.GOG_LINUX):
return self.__gog_linux
if self.steam:
return self.steam.macos_game
#elif (sgtype == SavegameType.GOG_WINDOWS):
# return self.__gog_windows
#elif (sgtype == SavegameType.GOG_LINUX):
# return self.__gog_linux
elif (sgtype == SavegameType.EPIC_WINDOWS):
return self.__epic_windows
if self.epic:
return self.__epic.windows
elif (sgtype == SavegameType.EPIC_LINUX):
return self.__epic_linux
return None
return None
@Property
@ -1504,6 +1630,14 @@ class Game(GObject):
t = Template(self.game_data.savegame_dir)
return t.safe_substitute(self.get_variables())
@Property
def epic(self)->EpicGameData|None:
return self.__epic
@epic.setter
def epic(self,epic:EpicGameData|None):
self.__epic = epic
def add_variable(self,name:str,value:str):
self.__variables[str(name)] = str(value)
@ -1537,23 +1671,20 @@ class Game(GObject):
if self.dbid:
ret['dbid'] = self.dbid
if (self.windows):
if (self.windows and self.windows.is_valid):
ret['windows'] = self.windows.serialize()
if (self.linux):
if (self.linux and self.linux.is_valid):
ret['linux'] = self.linux.serialize()
if (self.macos):
if (self.macos and self.macos.is_valid):
ret['macos'] = self.macos.serialize()
if (self.steam):
ret['steam'] = self.steam.serialize()
if (self.epic and self.epic.is_valid):
ret['epic'] = self.epic.serialize()
#if self.gog_windows:
# ret['gog_windows'] = self.gog_windows.serialize()
#if self.gog_linux:
# ret['gog_linux'] = self.gog_linux.serialize()
#if self.epic_windows:
# ret['epic_windows'] = self.epic_windows.serialize()
#if self.epic_linux:
# ret['epic_linux'] = self.epic_linux.serialize()
return ret

17
sgbackup/gog.py Normal file
View File

@ -0,0 +1,17 @@
###############################################################################
# sgbackup - The SaveGame Backup tool #
# Copyright (C) 2024,2025 Christian Moser #
# #
# 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 <https://www.gnu.org/licenses/>. #
###############################################################################

View File

@ -21,6 +21,8 @@ from .. import _import_gtk
from gi.repository import Gio,GLib,Gtk,Pango
from gi.repository.GObject import Property,Signal,GObject,BindingFlags,SignalFlags
from ..i18n import gettext as _, gettext_noop as N_
from ..game import (
Game,
GameData,
@ -30,13 +32,12 @@ from ..game import (
WindowsGame,
LinuxGame,
MacOSGame,
SteamLinuxGame,
SteamWindowsGame,
SteamMacOSGame,
SteamGameData,
SteamLinuxData,
SteamMacOSData,
SteamWindowsData,
EpicGameData,
EpicWindowsData,
GameManager,
)
@ -244,14 +245,16 @@ class GameVariableDialog(Gtk.Dialog):
self.__variable = None
grid = Gtk.Grid()
label = Gtk.Label.new("Name:")
label = Gtk.Label.new(_("Name:"))
label.set_xalign(0.0)
self.__name_entry = Gtk.Entry()
self.__name_entry.set_hexpand(True)
self.__name_entry.connect("changed",self._on_name_entry_changed)
grid.attach(label,0,0,1,1)
grid.attach(self.__name_entry,1,0,1,1)
label = Gtk.Label.new("Value")
label = Gtk.Label.new(_("Value:"))
label.set_xalign(0.0)
self.__value_entry = Gtk.Entry()
self.__value_entry.set_hexpand(True)
@ -326,9 +329,9 @@ class GameDialog(Gtk.Dialog):
self.set_default_size(800,600)
self.__filematch_dropdown_model = Gio.ListStore.new(GameFileTypeData)
self.__filematch_dropdown_model.append(GameFileTypeData(GameFileType.FILENAME,"Filename"))
self.__filematch_dropdown_model.append(GameFileTypeData(GameFileType.GLOB,"Glob"))
self.__filematch_dropdown_model.append(GameFileTypeData(GameFileType.REGEX,"Regular expression"))
self.__filematch_dropdown_model.append(GameFileTypeData(GameFileType.FILENAME,_("Filename")))
self.__filematch_dropdown_model.append(GameFileTypeData(GameFileType.GLOB,_("Glob")))
self.__filematch_dropdown_model.append(GameFileTypeData(GameFileType.REGEX,_("Regular expression")))
paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
paned.set_position(200)
@ -360,6 +363,7 @@ class GameDialog(Gtk.Dialog):
self.__linux = self.__create_linux_page()
self.__macos = self.__create_macos_page()
self.__steam = self.__create_steam_page()
self.__epic = self.__create_epic_page()
for stack_page in self.__stack.get_pages():
@ -396,18 +400,18 @@ class GameDialog(Gtk.Dialog):
sgtype_data = (
(SavegameType.UNSET,"Not set","process-stop-symbolic"),
(SavegameType.WINDOWS,"Windows native","windows-svgrepo-com-symbolic"),
(SavegameType.STEAM_WINDOWS,"Steam Windows","steam-svgrepo-com-symbolic"),
#(SavegameType.EPIC_WINDOWS,"Epic Windows","object-select-symbolic"),
(SavegameType.WINDOWS,N_("Windows"),"windows-svgrepo-com-symbolic"),
(SavegameType.STEAM_WINDOWS,N_("Steam Windows"),"steam-svgrepo-com-symbolic"),
(SavegameType.EPIC_WINDOWS,N_("Epic Windows"),"epic-games-svgrepo-com-symbolic"),
#(SavegameType.GOG_WINDOWS,"GoG Windows","object-select-symbolic"),
(SavegameType.LINUX,"Linux native","linux-svgrepo-com-symbolic"),
(SavegameType.STEAM_LINUX,"Steam Linux","steam-svgrepo-com-symbolic"),
#(SavegameType.EPIC_LINUX,"Epic Linux","object-select-symbolic"),
(SavegameType.LINUX,N_("Linux native"),"linux-svgrepo-com-symbolic"),
(SavegameType.STEAM_LINUX,_("Steam Linux"),"steam-svgrepo-com-symbolic"),
#(SavegameType.EPIC_LINUX,_("Epic Linux"),"epic-games-svgrepo-com-symbolic"),
#(SavegameType.GOG_LINUX,"GoG Linux","object-select-symbolic"),
(SavegameType.MACOS,"MacOS native","apple-svgrepo-com-symbolic"),
(SavegameType.STEAM_MACOS,"Steam MacOS","steam-svgrepo-com-symbolic"),
(SavegameType.MACOS,_("MacOS"),"apple-svgrepo-com-symbolic"),
(SavegameType.STEAM_MACOS,_("Steam MacOS"),"steam-svgrepo-com-symbolic"),
)
sgtype_model = Gio.ListStore.new(SavegameTypeData)
for data in sgtype_data:
@ -415,14 +419,14 @@ class GameDialog(Gtk.Dialog):
grid = Gtk.Grid.new()
label = Gtk.Label.new("Is active?")
label = self.__create_label(_("Is active?"))
self.__active_switch = Gtk.Switch()
entry_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,5)
entry_hbox.append(self.__active_switch)
entry_hbox.append(label)
vbox.append(entry_hbox)
label = Gtk.Label.new("Is live?")
label = self.__create_label(_("Is live?"))
self.__live_switch = Gtk.Switch()
entry_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,5)
@ -430,13 +434,13 @@ class GameDialog(Gtk.Dialog):
entry_hbox.append(label)
vbox.append(entry_hbox)
label = Gtk.Label.new("ID:")
label = self.__create_label(_("ID:"))
self.__id_label = Gtk.Label()
self.__id_label.set_hexpand(True)
grid.attach(label,0,0,1,1)
grid.attach(self.__id_label,1,0,1,1)
label = Gtk.Label.new("Key:")
label = self.__create_label(_("Key:"))
self.__key_entry = Gtk.Entry()
self.__key_entry.set_hexpand(True)
self.__key_entry.connect('changed',lambda w: self._on_savegame_type_changed())
@ -451,18 +455,18 @@ class GameDialog(Gtk.Dialog):
self.__savegame_type_dropdown.set_selected(0)
self.__savegame_type_dropdown.set_hexpand(True)
self.__savegame_type_dropdown.connect('notify::selected-item',lambda w,d: self._on_savegame_type_changed())
label = Gtk.Label.new("Savegame Type:")
label = self.__create_label(_("Savegame Type:"))
grid.attach(label,0,2,1,1)
grid.attach(self.__savegame_type_dropdown,1,2,1,1)
label = Gtk.Label.new("Game name:")
label = self.__create_label(_("Game name:"))
self.__name_entry = Gtk.Entry()
self.__name_entry.set_hexpand(True)
self.__name_entry.connect('changed',lambda w: self._on_savegame_type_changed())
grid.attach(label,0,3,1,1)
grid.attach(self.__name_entry,1,3,1,1)
label = Gtk.Label.new("Savegame name:")
label = self.__create_label(_("Savegame name:"))
self.__sgname_entry = Gtk.Entry()
self.__sgname_entry.set_hexpand(True)
self.__sgname_entry.connect('changed',lambda w: self._on_savegame_type_changed())
@ -475,7 +479,7 @@ class GameDialog(Gtk.Dialog):
vbox.append(self.__game_variables)
page.set_child(vbox)
self.__stack.add_titled(page,"main","Game")
self.__stack.add_titled(page,"main",_("Game"))
stack_page = self.__stack.get_page(page)
stack_page.set_icon_name('org.sgbackup.sgbackup-symbolic')
@ -487,44 +491,44 @@ class GameDialog(Gtk.Dialog):
grid = Gtk.Grid()
label = Gtk.Label.new("Root directory:")
label = self.__create_label(_("Root directory:"))
page.sgroot_entry = Gtk.Entry()
page.sgroot_entry.set_hexpand(True)
page.sgroot_entry.connect('changed',lambda w: self._on_savegame_type_changed())
grid.attach(label,0,0,1,1)
grid.attach(page.sgroot_entry,1,0,1,1)
label = Gtk.Label.new("Backup directory:")
label = self.__create_label(_("Backup directory:"))
page.sgdir_entry = Gtk.Entry()
page.sgdir_entry.set_hexpand(True)
page.sgdir_entry.connect('changed',lambda w: self._on_savegame_type_changed())
grid.attach(label,0,1,1,1)
grid.attach(page.sgdir_entry,1,1,1,1)
label = Gtk.Label.new("Installation directory:")
label = self.__create_label(("Installation directory:"))
page.installdir_entry = Gtk.Entry()
page.installdir_entry.set_hexpand(True)
grid.attach(label,0,2,1,1)
grid.attach(page.installdir_entry,1,2,1,1)
vbox.append(grid)
page.filematch = self.__create_filematch_widget('Match Files')
page.filematch = self.__create_filematch_widget(_('Match Files'))
vbox.append(page.filematch)
page.ignorematch = self.__create_filematch_widget('Ignore Files')
page.ignorematch = self.__create_filematch_widget(_('Ignore Files'))
vbox.append(page.ignorematch)
page.lookup_regkeys = self.__create_registry_key_widget("Lookup Registry keys")
page.lookup_regkeys = self.__create_registry_key_widget(_("Lookup Registry keys"))
vbox.append(page.lookup_regkeys)
page.installdir_regkeys = self.__create_registry_key_widget("Installations directory Registry keys")
page.installdir_regkeys = self.__create_registry_key_widget(_("Installations directory Registry keys"))
vbox.append(page.installdir_regkeys)
page.variables = self.__create_variables_widget()
vbox.append(page.variables)
page.set_child(vbox)
self.__stack.add_titled(page,"windows","Windows")
self.__stack.add_titled(page,"windows",_("Windows"))
stack_page = self.__stack.get_page(page)
stack_page.set_icon_name("windows-svgrepo-com-symbolic")
@ -537,21 +541,21 @@ class GameDialog(Gtk.Dialog):
self.__set_widget_margin(vbox,5)
grid = Gtk.Grid()
label = Gtk.Label.new("Root directory:")
label = self.__create_label(_("Root directory:"))
page.sgroot_entry = Gtk.Entry()
page.sgroot_entry.set_hexpand(True)
page.sgroot_entry.connect('changed',lambda w: self._on_savegame_type_changed())
grid.attach(label,0,0,1,1)
grid.attach(page.sgroot_entry,1,0,1,1)
label = Gtk.Label.new("Backup directory:")
label = self.__create_label(_("Backup directory:"))
page.sgdir_entry = Gtk.Entry()
page.sgdir_entry.set_hexpand(True)
page.sgdir_entry.connect('changed',lambda w: self._on_savegame_type_changed())
grid.attach(label,0,1,1,1)
grid.attach(page.sgdir_entry,1,1,1,1)
label = Gtk.Label.new("Executable")
label = self.__create_label(_("Executable:"))
page.binary_entry = Gtk.Entry()
page.binary_entry.set_hexpand(True)
grid.attach(label,0,2,1,1)
@ -568,7 +572,7 @@ class GameDialog(Gtk.Dialog):
vbox.append(page.variables)
page.set_child(vbox)
self.__stack.add_titled(page,"linux","Linux")
self.__stack.add_titled(page,"linux",_("Linux"))
stack_page = self.__stack.get_page(page)
stack_page.set_icon_name("linux-svgrepo-com-symbolic")
@ -580,21 +584,21 @@ class GameDialog(Gtk.Dialog):
self.__set_widget_margin(vbox,5)
grid = Gtk.Grid()
label = Gtk.Label.new("Root directory:")
label = self.__create_label(_("Root directory:"))
page.sgroot_entry = Gtk.Entry()
page.sgroot_entry.set_hexpand(True)
page.sgroot_entry.connect('changed',lambda w: self._on_savegame_type_changed())
grid.attach(label,0,0,1,1)
grid.attach(page.sgroot_entry,1,0,1,1)
label = Gtk.Label.new("Backup directory:")
label = self.__create_label(_("Backup directory:"))
page.sgdir_entry = Gtk.Entry()
page.sgdir_entry.set_hexpand(True)
page.sgdir_entry.connect('changed',lambda w: self._on_savegame_type_changed())
grid.attach(label,0,1,1,1)
grid.attach(page.sgdir_entry,1,1,1,1)
label = Gtk.Label.new("Executable")
label = self.__create_label(_("Executable"))
page.binary_entry = Gtk.Entry()
page.binary_entry.set_hexpand(True)
grid.attach(label,0,2,1,1)
@ -611,7 +615,7 @@ class GameDialog(Gtk.Dialog):
vbox.append(page.variables)
page.set_child(vbox)
self.__stack.add_titled(page,"macos","MacOS")
self.__stack.add_titled(page,"macos",_("MacOS"))
stack_page = self.__stack.get_page(page)
stack_page.set_icon_name("apple-svgrepo-com-symbolic")
@ -631,21 +635,21 @@ class GameDialog(Gtk.Dialog):
nbgrid = Gtk.Grid()
self.__set_widget_margin(nbgrid,5)
label = Gtk.Label.new("Root directory:")
label = self.__create_label(_("Root directory:"))
nbpage.sgroot_entry = Gtk.Entry()
nbpage.sgroot_entry.set_hexpand(True)
nbpage.sgroot_entry.connect('changed',lambda w: self._on_savegame_type_changed())
nbgrid.attach(label,0,0,1,1)
nbgrid.attach(nbpage.sgroot_entry,1,0,1,1)
label = Gtk.Label.new("Backup directory:")
label = self.__create_label(_("Backup directory:"))
nbpage.sgdir_entry = Gtk.Entry()
nbpage.sgdir_entry.set_hexpand(True)
nbpage.sgdir_entry.connect('changed',lambda w: self._on_savegame_type_changed())
nbgrid.attach(label,0,1,1,1)
nbgrid.attach(nbpage.sgdir_entry,1,1,1,1)
label = Gtk.Label.new("Installation directory:")
label = self.__create_label(_("Installation directory:"))
nbpage.installdir_entry = Gtk.Entry()
nbpage.installdir_entry.set_hexpand(True)
nbgrid.attach(label,0,3,1,1)
@ -681,13 +685,13 @@ class GameDialog(Gtk.Dialog):
page.notebook = Gtk.Notebook()
page.notebook.set_hexpand(True)
page.notebook.set_vexpand(True)
page.windows,nb_label = create_notebook_page('Windows','windows-svgrepo-com-symbolic')
page.windows,nb_label = create_notebook_page(_('Windows'),'windows-svgrepo-com-symbolic')
page.notebook.append_page(page.windows,nb_label)
page.linux,nb_label = create_notebook_page("Linux",'linux-svgrepo-com-symbolic')
page.linux,nb_label = create_notebook_page(_("Linux"),'linux-svgrepo-com-symbolic')
page.notebook.append_page(page.linux,nb_label)
page.macos,nb_label = create_notebook_page("Mac OS",'apple-svgrepo-com-symbolic')
page.macos,nb_label = create_notebook_page(_("Mac OS"),'apple-svgrepo-com-symbolic')
page.notebook.append_page(page.macos,nb_label)
page.append(page.notebook)
@ -697,6 +701,80 @@ class GameDialog(Gtk.Dialog):
return page
def __create_epic_page(self):
def create_notebook_page(title,icon_name):
label_widget = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,2)
label_icon = Gtk.Image.new_from_icon_name(icon_name)
label_icon.set_pixel_size(12)
label=Gtk.Label.new(title)
label_widget.append(label_icon)
label_widget.append(label)
nbpage = Gtk.ScrolledWindow()
nbvbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,5)
nbgrid = Gtk.Grid()
self.__set_widget_margin(nbgrid,5)
label = self.__create_label(_("Root directory:"))
nbpage.sgroot_entry = Gtk.Entry()
nbpage.sgroot_entry.set_hexpand(True)
nbpage.sgroot_entry.connect('changed',lambda w: self._on_savegame_type_changed())
nbgrid.attach(label,0,0,1,1)
nbgrid.attach(nbpage.sgroot_entry,1,0,1,1)
label = self.__create_label(_("Backup directory:"))
nbpage.sgdir_entry = Gtk.Entry()
nbpage.sgdir_entry.set_hexpand(True)
nbpage.sgdir_entry.connect('changed',lambda w: self._on_savegame_type_changed())
nbgrid.attach(label,0,1,1,1)
nbgrid.attach(nbpage.sgdir_entry,1,1,1,1)
label = self.__create_label(_("Installation directory:"))
nbpage.installdir_entry = Gtk.Entry()
nbpage.installdir_entry.set_hexpand(True)
nbgrid.attach(label,0,3,1,1)
nbgrid.attach(nbpage.installdir_entry,1,3,1,1)
nbvbox.append(nbgrid)
nbpage.filematch = self.__create_filematch_widget('Match Files')
nbvbox.append(nbpage.filematch)
nbpage.ignorematch = self.__create_filematch_widget('Ignore Files')
nbvbox.append(nbpage.ignorematch)
nbpage.variables = self.__create_variables_widget()
nbvbox.append(nbpage.variables)
nbpage.set_child(nbvbox)
return nbpage,label_widget
# create_notebook_page()
page = Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
grid = Gtk.Grid()
label = self.__create_label(_("AppName:"))
page.appname_entry = Gtk.Entry()
page.appname_entry.set_hexpand(True)
grid.attach(label,0,0,1,1)
grid.attach(page.appname_entry,1,0,1,1)
page.append(grid)
page.notebook = Gtk.Notebook()
page.notebook.set_hexpand(True)
page.notebook.set_vexpand(True)
page.windows,nb_label = create_notebook_page(_("Windows"),'windows-svgrepo-com-symbolic')
page.notebook.append_page(page.windows,nb_label)
page.append(page.notebook)
self.__stack.add_titled(page,'epic','Epic Games')
stack_page = self.__stack.get_page(page)
stack_page.set_icon_name("epic-games-svgrepo-com-symbolic")
return page
def __set_widget_margin(self,widget,margin):
widget.set_margin_start(margin)
widget.set_margin_end(margin)
@ -758,6 +836,11 @@ class GameDialog(Gtk.Dialog):
widget.set_child(vbox)
return widget
def __create_label(self,text:str):
label = Gtk.Label.new(text)
label.set_xalign(0.0)
return label
def __create_filematch_dropdown(self,item,widget):
factory = Gtk.SignalListItemFactory()
factory.connect('setup',self._on_filematch_type_dropdown_setup)
@ -928,6 +1011,17 @@ class GameDialog(Gtk.Dialog):
and self.__game.steam.macos
and self.__game.steam.macos.installdir
else "")
# Epic Games
set_game_widget_data(self.__epic.windows,self.__game.epic.windows if self.has_game and self.__game.epic else None)
self.__epic.appname_entry.set_text(self.__game.epic.appname if self.has_game and self.__game.epic else "")
self.__epic.windows.installdir_entry.set_text(self.__game.epic.windows.installdir
if self.has_game
and self.__game.epic
and self.__game.epic.windows
and self.__game.epic.windows.installdir
else "")
# reset()
def save(self):
@ -970,6 +1064,9 @@ class GameDialog(Gtk.Dialog):
})
return conf
def get_epic_data(widget):
conf = get_game_data(widget)
conf.update({'installdir':widget.installdir_entry.get_text()})
if not self.get_is_valid():
return
@ -1145,7 +1242,32 @@ class GameDialog(Gtk.Dialog):
ignore_match=data['ignorematch'])
elif self.__game.steam.macos:
self.__game.steam.macos = None
# bEND: steam
# END: steam
# BEGIN: epic
if self.get_is_valid_savegame_type(SavegameType.EPIC_WINDOWS):
data = get_epic_data(self.__epic.windows)
if self.__game.epic:
self.__game.epic.appname = self.__epic.appname_entry.get_text()
else:
self.__game.epic = EpicGameData(appname=self.__epic.appname_entry.get_text())
if self.__game.epic.windows:
self.__game.epic.windows.savegame_root = data['sgroot']
self.__game.epic.windows.savegame_dir = data['sgdir']
self.__game.epic.windows.variables = data['variables']
self.__game.epic.windows.file_matchers = data['filematch']
self.__game.epic.windows.ignore_matchers = data['ignorematch']
self.__game.epic.windows.installdir = data['installdir']
else:
self.__game.epic.windows = EpicWindowsData(savegame_root=data['sgroot'],
savegame_dir=data['sgdir'],
variables=data['variables'],
file_match=data['filematch'],
ignore_match=data['ignorematch'],
installdir=data['installdir'])
# END: epic
self.__game.save()
GameManager.get_global().add_game(self.__game)
@ -1189,8 +1311,8 @@ class GameDialog(Gtk.Dialog):
return check_is_valid(self.__steam.linux)
elif sgtype == SavegameType.STEAM_MACOS:
return check_is_valid(self.__steam.macos)
#elif sgtype == SavegameType.EPIC_WINDOWS:
# return check_is_valid(self.__epic_windows)
elif sgtype == SavegameType.EPIC_WINDOWS:
return check_is_valid(self.__epic.windows)
#elif sgtype == SavegameType.EPIC_LINUX:
# return check_is_valid(self.__epic_linux)
#elif sgtype == SavegameType.GOG_WINDOWS:

View File

@ -27,4 +27,5 @@ gettext = lambda s: _gettext.dgettext(TEXTDOMAIN,s)
pgettext = lambda context,s: _gettext.dpgettext(TEXTDOMAIN,context,s)
ngettext = lambda singular,plural,n: _gettext.dngettext(TEXTDOMAIN,singular,plural,n)
npgettext = lambda context,singular,plural,n: _gettext.dnpgettext(TEXTDOMAIN,context,singular,plural,n)
noop = lambda s: s
def gettext_noop(msgid:str): return msgid
def noop(msgid:str): return msgid

View File

@ -405,7 +405,6 @@ class Settings(GObject.GObject):
skey = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE,i)
svalue = winreg.QueryValueEx(skey,'InstallPath')[0]
if svalue:
self.set_string('steam','installpath',svalue)
return svalue
except:
continue
@ -418,6 +417,36 @@ class Settings(GObject.GObject):
def steam_installpath(self,path:str):
self.set_string('steam','installpath',path)
@GObject.Property
def epic_datadir(self)->str|None:
datadir = self.get_string('epic',"dataDir",None)
if datadir is None and PLATFORM_WINDOWS:
for i in ("SOFTWARE\\WOW6432Node\\Epic Games\\EpicGamesLauncher","SOFTWARE\\Epic Games\\EpicGamesLauncher"):
try:
skey = None
skey = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE,i)
svalue = winreg.QueryValueEx(skey,'AppDataPath')[0]
if svalue:
datadir = svalue
break
except:
continue
finally:
if skey:
skey.Close()
return datadir
@epic_datadir.setter
def epic_datadir(self,datadir:str|None):
if not datadir:
if self.has_key('epic',"dataDir"):
self.remove_key('epic','dataDir')
return
if not os.path.isabs(datadir):
raise ValueError("\"epic_datadir\" is not an absolute path!")
self.set_string('epic','dataDir',datadir)
@GObject.Property
def cli_pager(self)->str:
pager = self.get_string('cli','pager',None)