2025.01.16 23:22:14

This commit is contained in:
Christian Moser 2025-01-16 23:22:14 +01:00
parent 391b42f3bb
commit 352b421479
Failed to extract signature
4 changed files with 157 additions and 111 deletions

View File

@ -143,6 +143,20 @@ class SavegameType(StrEnum):
return st.UNSET
SAVEGAME_TYPE_ICONS = {
SavegameType.UNSET : None,
SavegameType.WINDOWS: 'windows-svgrepo-com-symbolic',
SavegameType.LINUX: 'linux-svgrepo-com-symbolic.svg',
SavegameType.MACOS: 'apple-svgrepo-com-symbolic.svg',
SavegameType.STEAM_LINUX: 'steam-svgrepo-com-symbolic',
SavegameType.STEAM_MACOS: 'steam-svgrepo-com-symbolic',
SavegameType.STEAM_WINDOWS: 'steam-svgrepo-com-symbolic',
SavegameType.EPIC_LINUX: 'epic-games-svgrepo-com-symbolic',
SavegameType.EPIC_WINDOWS: 'epic-games-svgrepo-com-symbolic',
SavegameType.GOG_LINUX: 'gog-com-svgrepo-com-symbolic',
SavegameType.GOG_WINDOWS: 'gog-com-svgrepo-com-symbolic',
}
class GameFileType(StrEnum):
"""
@ -619,10 +633,24 @@ class WindowsGame(GameData):
def game_registry_keys(self)->list:
return self.__game_registry_keys
@game_registry_keys.setter
def game_registry_keys(self,keys:list[str]|tuple[str]|None):
self.__game_registry_keys = []
if keys:
for rk in keys:
self.__game_registry_keys.append(str(rk))
@Property
def installdir_registry_keys(self)->list:
return self.__installdir_registry_keys
@installdir_registry_keys.setter
def installdir_registry_keys(self,keys:list[str]|tuple[str]|None):
self.__installdir_registry_keys = []
if keys:
for rk in keys:
self.__installdir_registry_keys.append(str(rk))
@Property
def is_installed(self)->bool|None:
if not PLATFORM_WIN32 or not self.game_registry_keys:
@ -666,6 +694,7 @@ class WindowsGame(GameData):
def get_variables(self):
variables = super().get_variables()
variables["INSTALLDIR"] = self.installdir if self.installdir else ""
return variables
def serialize(self):
ret = super().serialize()
@ -900,8 +929,8 @@ class Game(GObject):
_logger = logger.getChild("Game.new_from_dict()")
def get_file_match(conf:dict):
conf_fm = conf['file_match'] if 'file_match' in conf else None
conf_im = conf['ignore_match'] if 'ignore_match' in conf else None
conf_fm = conf['file_match'] if 'file_match' in conf else []
conf_im = conf['ignore_match'] if 'ignore_match' in conf else []
if (conf_fm):
file_match = []
@ -933,11 +962,11 @@ class Game(GObject):
return (file_match,ignore_match)
def new_steam_game(conf,cls:SteamGame):
appid = conf['appid'] if 'appid' in conf else None
sgroot = conf['savegame_root'] if 'savegame_root' in conf else None
sgdir = conf['savegame_dir'] if 'savegame_dir' in conf else None
vars = conf['variables'] if 'variables' in conf else None
installdir = conf['installdir'] if 'installdir' in conf else None
appid = conf['appid'] if 'appid' in conf else ""
sgroot = conf['savegame_root'] if 'savegame_root' in conf else ""
sgdir = conf['savegame_dir'] if 'savegame_dir' in conf else ""
vars = conf['variables'] if 'variables' in conf else {}
installdir = conf['installdir'] if 'installdir' in conf else ""
file_match,ignore_match = get_file_match(conf)
if appid is not None and sgroot and sgdir:
@ -966,40 +995,37 @@ class Game(GObject):
winconf = config['windows']
sgroot = winconf['savegame_root'] if 'savegame_root' in winconf else None
sgdir = winconf['savegame_dir'] if 'savegame_dir' in winconf else None
vars = winconf['variables'] if 'variables' in winconf else None
vars = winconf['variables'] if 'variables' in winconf else {}
installdir = winconf['installdir'] if 'installdir' in winconf else None
game_regkeys = winconf['game_registry_keys'] if 'game_registry_keys' in winconf else None
installdir_regkeys = winconf['installdir_registry_keys'] if 'installdir_registry_keys' in winconf else None
game_regkeys = winconf['game_registry_keys'] if 'game_registry_keys' in winconf else []
installdir_regkeys = winconf['installdir_registry_keys'] if 'installdir_registry_keys' in winconf else []
file_match,ignore_match = get_file_match(winconf)
if (sgroot and sgdir):
game.windows = WindowsGame(sgroot,
sgdir,
vars,
installdir,
game_regkeys,
installdir_regkeys,
file_match,
ignore_match)
game.windows = WindowsGame(sgroot,
sgdir,
vars,
installdir,
game_regkeys,
installdir_regkeys,
file_match,
ignore_match)
if 'linux' in config:
linconf = config['linux']
sgroot = linconf['savegame_root'] if 'savegame_root' in linconf else None
sgdir = linconf['savegame_dir'] if 'savegame_dir' in linconf else None
vars = linconf['variables'] if 'variables' in linconf else None
vars = linconf['variables'] if 'variables' in linconf else {}
binary = linconf['binary'] if 'binary' in linconf else None
file_match,ignore_match = get_file_match(linconf)
if (sgroot and sgdir):
game.linux = LinuxGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
game.linux = LinuxGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
if 'macos' in config:
macconf = config['macos']
sgroot = macconf['savegame_root'] if 'savegame_root' in macconf else None
sgdir = macconf['savegame_dir'] if 'savegame_dir' in macconf else None
vars = macconf['variables'] if 'variables' in macconf else None
vars = macconf['variables'] if 'variables' in macconf else {}
binary = macconf['binary'] if 'binary' in macconf else None
file_match,ignore_match = get_file_match(macconf)
if (sgroot and sgdir):
game.macos = MacOSGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
game.macos = MacOSGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
if 'steam_windows' in config:
game.steam_windows = new_steam_game(config['steam_windows'],SteamWindowsGame)
@ -1099,9 +1125,9 @@ class Game(GObject):
@Property
def filename(self)->str|None:
if not self.__filename:
if not self.__key:
if not self.key:
return None
os.path.join(settings.gameconf_dir,'.'.join((self.key,'gameconf')))
return os.path.join(settings.gameconf_dir,'.'.join((self.key,'gameconf')))
return self.__filename
@ -1428,9 +1454,8 @@ class GameManager(GObject):
if not game:
self.logger.warn("Not loaded game \"{game}\"!".format(
game=(game.name if game is not None else "UNKNOWN GAME")))
print(game.serialize())
continue
except Exception as ex:
except GLib.Error as ex: #Exception as ex:
self.logger.error("Unable to load gameconf {gameconf}! ({what})".format(
gameconf = os.path.basename(gcf),
what = str(ex)))

View File

@ -16,7 +16,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
###############################################################################
from gi.repository import Gtk,Gio,Gdk
from gi.repository import Gtk,Gio,Gdk,GLib
from gi.repository.GObject import GObject,Signal,Property,SignalFlags,BindingFlags
import logging; logger=logging.getLogger(__name__)
@ -28,7 +28,7 @@ from pathlib import Path
from ..settings import settings
from ._settingsdialog import SettingsDialog
from ._gamedialog import GameDialog
from ..game import Game,GameManager
from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS
from ._steam import SteamLibrariesDialog,NewSteamAppsDialog
__gtype_name__ = __name__
@ -52,6 +52,11 @@ class GameView(Gtk.ScrolledWindow):
pass
self.__liststore.append(g)
factory_icon = Gtk.SignalListItemFactory.new()
factory_icon.connect('setup',self._on_icon_column_setup)
factory_icon.connect('bind',self._on_icon_column_bind)
column_icon = Gtk.ColumnViewColumn.new("",factory_icon)
factory_key = Gtk.SignalListItemFactory.new()
factory_key.connect('setup',self._on_key_column_setup)
factory_key.connect('bind',self._on_key_column_bind)
@ -75,14 +80,22 @@ class GameView(Gtk.ScrolledWindow):
factory_live.connect('unbind',self._on_live_column_unbind)
column_live = Gtk.ColumnViewColumn.new("Live",factory_live)
factory_actions = Gtk.SignalListItemFactory.new()
factory_actions.connect('setup',self._on_actions_column_setup)
factory_actions.connect('bind',self._on_actions_column_bind)
column_actions = Gtk.ColumnViewColumn.new("",factory_actions)
selection = Gtk.SingleSelection.new(self._liststore)
self.__columnview = Gtk.ColumnView.new(selection)
self.columnview.append_column(column_icon)
self.columnview.append_column(column_key)
self.columnview.append_column(column_name)
self.columnview.append_column(column_active)
self.columnview.append_column(column_live)
self.columnview.append_column(column_actions)
self.columnview.set_single_click_activate(True)
self.set_child(self.columnview)
self.refresh()
@ -115,6 +128,22 @@ class GameView(Gtk.ScrolledWindow):
for game in GameManager.get_global().games.values():
self.__liststore.append(game)
def _on_icon_column_setup(self,factory,item):
item.set_child(Gtk.Image())
def _on_icon_column_bind(self,factory,item):
def transform_to_icon_name(_bidning,sgtype):
icon_name = SAVEGAME_TYPE_ICONS[sgtype] if sgtype in SAVEGAME_TYPE_ICONS else None
if icon_name:
return icon_name
return ""
icon = item.get_child()
game = item.get_item()
game.bind_property('savegame_type',icon,'icon_name',BindingFlags.SYNC_CREATE,transform_to_icon_name)
def _on_key_column_setup(self,factory,item):
item.set_child(Gtk.Label())
@ -171,9 +200,9 @@ class GameView(Gtk.ScrolledWindow):
dialog.hide()
dialog.destroy()
game.is_live = state
game.is_live = switch.get_active()
game.save()
if not state:
if not game.is_live:
dialog = Gtk.MessageDialog()
dialog.set_transient_for(self.get_root())
dialog.props.buttons = Gtk.ButtonsType.YES_NO
@ -184,21 +213,61 @@ class GameView(Gtk.ScrolledWindow):
dialog.connect('response',on_dialog_response)
dialog.present()
@property
def current_game(self)->Game|None:
"""
current_game Get the currently selected `Game`
def _on_actions_column_setup(self,action,item):
child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,2)
icon = Gtk.Image.new_from_icon_name('document-edit-symbolic')
child.edit_button = Gtk.Button()
child.edit_button.set_child(icon)
child.append(child.edit_button)
If no `Game` is selected this property resolves to `Null`
:type: Game|None
"""
selection = self._columnview.get_model()
pos = selection.get_selected()
if pos == Gtk.INVALID_LIST_POSITION:
return None
return selection.get_model().get_item(pos)
icon = Gtk.Image.new_from_icon_name('list-remove-symbolic')
child.remove_button = Gtk.Button()
child.remove_button.set_child(icon)
child.append(child.remove_button)
item.set_child(child)
def _on_actions_column_bind(self,action,item):
child = item.get_child()
game = item.get_item()
child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item)
child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item)
def _on_columnview_edit_button_clicked(self,button,item):
def on_dialog_response(dialog,response):
if response == Gtk.ResponseType.APPLY:
self.refresh()
game = item.get_item()
dialog = GameDialog(self.get_root(),game)
dialog.connect('response',on_dialog_response)
dialog.present()
def _on_columnview_remove_button_clicked(self,button,item):
def on_dialog_response(dialog,response,game:Game):
if response == Gtk.ResponseType.YES:
if os.path.isfile(game.filename):
os.unlink(game.filename)
for i in range(self._liststore.get_n_items()):
item = self._liststore.get_item(i)
if item.key == game.key:
self._liststore.remove_item(i)
return
dialog.hide()
dialog.destroy()
game = item.get_item()
dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO,
text="Do you really want to remove the game <span weight='bold'>{game}</span>?".format(
game=game.name),
use_markup=True,
secondary_text="Removing games cannot be undone!!!")
dialog.set_transient_for(self.get_root())
dialog.connect('response',on_dialog_response,game)
dialog.present()
# GameView class
class BackupViewData(GObject):
@ -278,7 +347,7 @@ class BackupViewData(GObject):
def _on_selection_changed(self,selection):
pass
class BackupView(Gtk.ScrolledWindow):
class BackupView(Gtk.Box):
"""
BackupView This view displays the backup for the selected `Game`.
"""
@ -290,7 +359,9 @@ class BackupView(Gtk.ScrolledWindow):
:param gameview: The `GameView` to connect this class to.
:type gameview: GameView
"""
Gtk.ScrolledWindow.__init__(self)
Gtk.Box.__init__(self,orientation=Gtk.Orientation.VERTICAL)
self._title_label = Gtk.Label()
scrolled = Gtk.ScrolledWindow()
self.__gameview = gameview
self.__liststore = Gio.ListStore()
@ -316,11 +387,14 @@ class BackupView(Gtk.ScrolledWindow):
self.__columnview.append_column(live_column)
self.__columnview.append_column(sgname_column)
self.__columnview.append_column(timestamp_column)
self.__columnview.set_vexpand(True)
self._on_gameview_selection_changed(selection)
self.gameview.columnview.get_model().connect('selection-changed',self._on_gameview_selection_changed)
self.gameview.columnview.connect('activate',self._on_gameview_columnview_activate)
self.set_child(self.__columnview)
scrolled.set_child(self.__columnview)
self.append(self._title_label)
self.append(scrolled)
@property
def gameview(self)->GameView:
@ -383,10 +457,12 @@ class BackupView(Gtk.ScrolledWindow):
display_size = str(size) + " B"
label.set_text(display_size)
def _on_gameview_selection_changed(self,model):
game = model.get_selected_item()
if game is None:
return
def _on_gameview_columnview_activate(self,columnview,position):
model = columnview.get_model().get_model()
game = model.get_item(position)
self._title_label.set_markup("<span size='large' weight='bold'>{}</span>".format(GLib.markup_escape_text(game.name)))
class AppWindow(Gtk.ApplicationWindow):

View File

@ -831,6 +831,7 @@ class GameDialog(Gtk.Dialog):
self.__active_switch.set_active(self.__game.is_active if self.has_game else True)
self.__live_switch.set_active(self.__game.is_live if self.has_game else True)
self.__name_entry.set_text(self.__game.name if self.has_game else "")
self.__key_entry.set_text(self.__game.key if self.has_game else "")
self.__sgname_entry.set_text(self.__game.savegame_name if self.has_game else "")
set_variables(self.__game_variables,self.__game.variables if self.has_game else None)

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
height="800px"
width="800px"
version="1.1"
id="Layer_1"
viewBox="0 0 305 305"
xml:space="preserve"
sodipodi:docname="windows-svgrepo-com.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="1.46625"
inkscape:cx="400"
inkscape:cy="400"
inkscape:window-width="3440"
inkscape:window-height="1369"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<g
id="XMLID_108_"
style="fill-rule:evenodd;image-rendering:auto;fill:#2e3434;fill-opacity:1">
<path
id="XMLID_109_"
d="M139.999,25.775v116.724c0,1.381,1.119,2.5,2.5,2.5H302.46c1.381,0,2.5-1.119,2.5-2.5V2.5 c0-0.726-0.315-1.416-0.864-1.891c-0.548-0.475-1.275-0.687-1.996-0.583L142.139,23.301 C140.91,23.48,139.999,24.534,139.999,25.775z"
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
<path
id="XMLID_110_"
d="M122.501,279.948c0.601,0,1.186-0.216,1.644-0.616c0.544-0.475,0.856-1.162,0.856-1.884V162.5 c0-1.381-1.119-2.5-2.5-2.5H2.592c-0.663,0-1.299,0.263-1.768,0.732c-0.469,0.469-0.732,1.105-0.732,1.768l0.006,98.515 c0,1.25,0.923,2.307,2.16,2.477l119.903,16.434C122.274,279.94,122.388,279.948,122.501,279.948z"
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
<path
id="XMLID_138_"
d="M2.609,144.999h119.892c1.381,0,2.5-1.119,2.5-2.5V28.681c0-0.722-0.312-1.408-0.855-1.883 c-0.543-0.475-1.261-0.693-1.981-0.594L2.164,42.5C0.923,42.669-0.001,43.728,0,44.98l0.109,97.521 C0.111,143.881,1.23,144.999,2.609,144.999z"
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
<path
id="XMLID_169_"
d="M302.46,305c0.599,0,1.182-0.215,1.64-0.613c0.546-0.475,0.86-1.163,0.86-1.887l0.04-140 c0-0.663-0.263-1.299-0.732-1.768c-0.469-0.469-1.105-0.732-1.768-0.732H142.499c-1.381,0-2.5,1.119-2.5,2.5v117.496 c0,1.246,0.918,2.302,2.151,2.476l159.961,22.504C302.228,304.992,302.344,305,302.46,305z"
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB