Various WIP
* Use `vars()` instead of directly accessing `__dict__` * Remove `auto_update` from RareGame's metadata * Correct check for updating the Steam App ID (We want to keep any changes from the user) * Collect both Wine and Proton prefixes when removing overlay registry keys. * Add few convenience functions in config_helper and paths.
This commit is contained in:
parent
7a5bb0b732
commit
af6d7c5055
|
@ -16,6 +16,7 @@ from PyQt5.QtWidgets import (
|
|||
QHBoxLayout,
|
||||
)
|
||||
|
||||
from rare.models.options import options
|
||||
from rare.components.tabs import MainTabWidget
|
||||
from rare.components.tray_icon import TrayIcon
|
||||
from rare.shared import RareCore
|
||||
|
@ -93,8 +94,8 @@ class MainWindow(QMainWindow):
|
|||
# self.status_timer.start()
|
||||
|
||||
width, height = 1280, 720
|
||||
if self.settings.value("save_size", False, bool):
|
||||
width, height = self.settings.value("window_size", (width, height), tuple)
|
||||
if self.settings.value(*options.save_size):
|
||||
width, height = self.settings.value(*options.window_size)
|
||||
|
||||
self.resize(width, height)
|
||||
|
||||
|
@ -151,9 +152,9 @@ class MainWindow(QMainWindow):
|
|||
self._window_launched = True
|
||||
|
||||
def hide(self) -> None:
|
||||
if self.settings.value("save_size", False, bool):
|
||||
if self.settings.value(*options.save_size):
|
||||
size = self.size().width(), self.size().height()
|
||||
self.settings.setValue("window_size", size)
|
||||
self.settings.setValue(options.window_size.key, size)
|
||||
super(MainWindow, self).hide()
|
||||
|
||||
def toggle(self):
|
||||
|
@ -214,7 +215,7 @@ class MainWindow(QMainWindow):
|
|||
# lk: `accept_close` is set to `True` by the `close()` method, overrides exiting to tray in `closeEvent()`
|
||||
# lk: ensures exiting instead of hiding when `close()` is called programmatically
|
||||
if not self.__accept_close:
|
||||
if self.settings.value("sys_tray", True, bool):
|
||||
if self.settings.value(*options.sys_tray):
|
||||
self.hide()
|
||||
e.ignore()
|
||||
return
|
||||
|
|
|
@ -15,6 +15,7 @@ from rare.components.dialogs.uninstall_dialog import UninstallDialog
|
|||
from rare.lgndr.models.downloading import UIUpdate
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.install import InstallOptionsModel, InstallQueueItemModel, UninstallOptionsModel
|
||||
from rare.models.options import options
|
||||
from rare.shared import RareCore
|
||||
from rare.shared.workers.install_info import InstallInfoWorker
|
||||
from rare.shared.workers.uninstall import UninstallWorker
|
||||
|
@ -105,9 +106,13 @@ class DownloadsTab(QWidget):
|
|||
def __add_update(self, update: Union[str, RareGame]):
|
||||
if isinstance(update, str):
|
||||
update = self.rcore.get_game(update)
|
||||
if QSettings().value(
|
||||
f"{update.app_name}/auto_update", False, bool
|
||||
) or QSettings().value("auto_update", False, bool):
|
||||
|
||||
auto_update = QSettings(self).value(
|
||||
f"{update.app_name}/{options.auto_update.key}",
|
||||
QSettings(self).value(*options.auto_update),
|
||||
options.auto_update.dtype
|
||||
)
|
||||
if auto_update:
|
||||
self.__get_install_options(
|
||||
InstallOptionsModel(app_name=update.app_name, update=True, silent=True)
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@ from rare.shared import RareCore
|
|||
from rare.widgets.library_layout import LibraryLayout
|
||||
from rare.widgets.sliding_stack import SlidingStackedWidget
|
||||
from .game_info import GameInfoTabs
|
||||
from .game_widgets import LibraryWidgetController
|
||||
from .game_widgets import LibraryWidgetController, LibraryFilter, LibraryOrder
|
||||
from .game_widgets.icon_game_widget import IconGameWidget
|
||||
from .game_widgets.list_game_widget import ListGameWidget
|
||||
from .head_bar import GameListHeadBar
|
||||
|
@ -94,11 +94,11 @@ class GamesTab(QStackedWidget):
|
|||
self.head_bar.search_bar.textChanged.connect(self.scroll_to_top)
|
||||
self.head_bar.filterChanged.connect(self.filter_games)
|
||||
self.head_bar.filterChanged.connect(self.scroll_to_top)
|
||||
self.head_bar.refresh_list.clicked.connect(self.library_controller.update_list)
|
||||
self.head_bar.orderChanged.connect(self.order_games)
|
||||
self.head_bar.orderChanged.connect(self.scroll_to_top)
|
||||
self.head_bar.refresh_list.clicked.connect(self.library_controller.update_game_views)
|
||||
self.head_bar.view.toggled.connect(self.toggle_view)
|
||||
|
||||
self.active_filter: str = self.head_bar.filter.currentData(Qt.UserRole)
|
||||
|
||||
# signals
|
||||
self.signals.game.installed.connect(self.update_count_games_label)
|
||||
self.signals.game.uninstalled.connect(self.update_count_games_label)
|
||||
|
@ -157,7 +157,7 @@ class GamesTab(QStackedWidget):
|
|||
continue
|
||||
self.icon_view.layout().addWidget(icon_widget)
|
||||
self.list_view.layout().addWidget(list_widget)
|
||||
self.filter_games(self.active_filter)
|
||||
self.filter_games(self.head_bar.current_filter())
|
||||
self.update_count_games_label()
|
||||
|
||||
def add_library_widget(self, rgame: RareGame):
|
||||
|
@ -170,18 +170,26 @@ class GamesTab(QStackedWidget):
|
|||
list_widget.show_info.connect(self.show_game_info)
|
||||
return icon_widget, list_widget
|
||||
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot(str, str)
|
||||
def filter_games(self, filter_name="all", search_text: str = ""):
|
||||
@pyqtSlot(int)
|
||||
@pyqtSlot(int, str)
|
||||
def filter_games(self, library_filter: LibraryFilter = LibraryFilter.ALL, search_text: str = ""):
|
||||
if not search_text and (t := self.head_bar.search_bar.text()):
|
||||
search_text = t
|
||||
|
||||
if filter_name:
|
||||
self.active_filter = filter_name
|
||||
if not filter_name and (t := self.active_filter):
|
||||
filter_name = t
|
||||
# if library_filter:
|
||||
# self.active_filter = filter_type
|
||||
# if not library_filter and (t := self.active_filter):
|
||||
# library_filter = t
|
||||
|
||||
self.library_controller.filter_list(filter_name, search_text.lower())
|
||||
self.library_controller.filter_game_views(library_filter, search_text.lower())
|
||||
|
||||
@pyqtSlot(int)
|
||||
@pyqtSlot(int, str)
|
||||
def order_games(self, library_order: LibraryOrder = LibraryFilter.ALL, search_text: str = ""):
|
||||
if not search_text and (t := self.head_bar.search_bar.text()):
|
||||
search_text = t
|
||||
|
||||
self.library_controller.order_game_views(library_order, search_text.lower())
|
||||
|
||||
def toggle_view(self):
|
||||
self.settings.setValue("icon_view", not self.head_bar.view.isChecked())
|
||||
|
|
|
@ -3,7 +3,7 @@ import platform
|
|||
from logging import getLogger
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import QThreadPool, QSettings
|
||||
from PyQt5.QtCore import QThreadPool, QSettings, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QFileDialog,
|
||||
|
@ -19,10 +19,11 @@ from legendary.models.game import SaveGameStatus
|
|||
|
||||
from rare.models.game import RareGame
|
||||
from rare.shared import RareCore
|
||||
from rare.shared.workers.wine_resolver import WineResolver
|
||||
from rare.shared.workers.wine_resolver import WineSavePathResolver
|
||||
from rare.ui.components.tabs.games.game_info.cloud_settings_widget import Ui_CloudSettingsWidget
|
||||
from rare.ui.components.tabs.games.game_info.cloud_sync_widget import Ui_CloudSyncWidget
|
||||
from rare.utils.misc import icon
|
||||
from rare.utils.metrics import timelogger
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
from rare.widgets.loading_widget import LoadingWidget
|
||||
from rare.widgets.side_tab import SideTabContents
|
||||
|
@ -114,13 +115,14 @@ class CloudSaves(QWidget, SideTabContents):
|
|||
def compute_save_path(self):
|
||||
if self.rgame.is_installed and self.rgame.game.supports_cloud_saves:
|
||||
try:
|
||||
with timelogger(logger, "Detecting save path"):
|
||||
new_path = self.core.get_save_path(self.rgame.app_name)
|
||||
if platform.system() != "Windows" and not os.path.exists(new_path):
|
||||
raise ValueError(f'Path "{new_path}" does not exist.')
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
resolver = WineResolver(self.core, self.rgame.raw_save_path, self.rgame.app_name)
|
||||
if not resolver.wine_env.get("WINEPREFIX"):
|
||||
resolver = WineSavePathResolver(self.core, self.rgame)
|
||||
if not resolver.environment.get("WINEPREFIX"):
|
||||
del resolver
|
||||
self.cloud_save_path_edit.setText("")
|
||||
QMessageBox.warning(self, "Warning", "No wine prefix selected. Please set it in settings")
|
||||
|
@ -129,14 +131,14 @@ class CloudSaves(QWidget, SideTabContents):
|
|||
self.cloud_save_path_edit.setDisabled(True)
|
||||
self.compute_save_path_button.setDisabled(True)
|
||||
|
||||
app_name = self.rgame.app_name
|
||||
resolver.signals.result_ready.connect(lambda x: self.wine_resolver_finished(x, app_name))
|
||||
resolver.signals.result_ready.connect(self.__on_wine_resolver_result)
|
||||
QThreadPool.globalInstance().start(resolver)
|
||||
return
|
||||
else:
|
||||
self.cloud_save_path_edit.setText(new_path)
|
||||
|
||||
def wine_resolver_finished(self, path, app_name):
|
||||
@pyqtSlot(str, str)
|
||||
def __on_wine_resolver_result(self, path, app_name):
|
||||
logger.info(f"Wine resolver finished for {app_name}. Computed save path: {path}")
|
||||
if app_name == self.rgame.app_name:
|
||||
self.cloud_save_path_edit.setDisabled(False)
|
||||
|
@ -158,8 +160,6 @@ class CloudSaves(QWidget, SideTabContents):
|
|||
self.cloud_save_path_edit.setText("")
|
||||
return
|
||||
self.cloud_save_path_edit.setText(path)
|
||||
elif path:
|
||||
self.rcore.get_game(app_name).save_path = path
|
||||
|
||||
def __update_widget(self):
|
||||
supports_saves = self.rgame.igame is not None and (
|
||||
|
|
|
@ -4,7 +4,7 @@ from logging import getLogger
|
|||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QLabel, QFileDialog
|
||||
from PyQt5.QtWidgets import QLabel, QFileDialog, QFormLayout
|
||||
from legendary.models.game import Game, InstalledGame
|
||||
|
||||
from rare.components.tabs.settings import DefaultGameSettings
|
||||
|
@ -20,10 +20,6 @@ logger = getLogger("GameSettings")
|
|||
class GameSettings(DefaultGameSettings, SideTabContents):
|
||||
def __init__(self, parent=None):
|
||||
super(GameSettings, self).__init__(False, parent=parent)
|
||||
self.pre_launch_settings = PreLaunchSettings()
|
||||
self.ui.launch_settings_group.layout().addRow(
|
||||
QLabel(self.tr("Pre-launch command")), self.pre_launch_settings
|
||||
)
|
||||
|
||||
self.ui.skip_update.currentIndexChanged.connect(
|
||||
lambda x: self.update_combobox("skip_update_check", x)
|
||||
|
@ -43,9 +39,17 @@ class GameSettings(DefaultGameSettings, SideTabContents):
|
|||
save_func=self.override_exe_save_callback,
|
||||
parent=self
|
||||
)
|
||||
self.ui.launch_settings_layout.insertRow(
|
||||
self.ui.launch_settings_layout.getWidgetPosition(self.ui.launch_params)[0] + 1,
|
||||
QLabel(self.tr("Override executable"), self), self.override_exe_edit
|
||||
self.ui.launch_settings_layout.setWidget(
|
||||
self.ui.launch_settings_layout.getWidgetPosition(self.ui.override_exe_label)[0],
|
||||
QFormLayout.FieldRole,
|
||||
self.override_exe_edit
|
||||
)
|
||||
|
||||
self.pre_launch_settings = PreLaunchSettings(parent=self)
|
||||
self.ui.launch_settings_layout.setWidget(
|
||||
self.ui.launch_settings_layout.getWidgetPosition(self.ui.pre_launch_label)[0],
|
||||
QFormLayout.FieldRole,
|
||||
self.pre_launch_settings
|
||||
)
|
||||
|
||||
self.ui.game_settings_layout.setAlignment(Qt.AlignTop)
|
||||
|
@ -126,9 +130,9 @@ class GameSettings(DefaultGameSettings, SideTabContents):
|
|||
self.set_title.emit(self.game.app_title)
|
||||
if platform.system() != "Windows":
|
||||
if self.igame and self.igame.platform == "Mac":
|
||||
self.ui.linux_settings_widget.setVisible(False)
|
||||
self.linux_settings.setVisible(False)
|
||||
else:
|
||||
self.ui.linux_settings_widget.setVisible(True)
|
||||
self.linux_settings.setVisible(True)
|
||||
|
||||
self.ui.launch_params.setText(self.core.lgd.config.get(self.game.app_name, "start_params", fallback=""))
|
||||
self.override_exe_edit.setText(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Tuple, List, Union, Optional
|
||||
from typing import Tuple, List, Union
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QWidget
|
|||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.signals import GlobalSignals
|
||||
from rare.models.library import LibraryFilter, LibraryOrder
|
||||
from rare.shared import RareCore
|
||||
from .icon_game_widget import IconGameWidget
|
||||
from .list_game_widget import ListGameWidget
|
||||
|
@ -20,8 +21,8 @@ class LibraryWidgetController(QObject):
|
|||
self.core: LegendaryCore = self.rcore.core()
|
||||
self.signals: GlobalSignals = self.rcore.signals()
|
||||
|
||||
self.signals.game.installed.connect(self.sort_list)
|
||||
self.signals.game.uninstalled.connect(self.sort_list)
|
||||
self.signals.game.installed.connect(self.order_game_views)
|
||||
self.signals.game.uninstalled.connect(self.order_game_views)
|
||||
|
||||
def add_game(self, rgame: RareGame):
|
||||
return self.add_widgets(rgame)
|
||||
|
@ -32,24 +33,26 @@ class LibraryWidgetController(QObject):
|
|||
return icon_widget, list_widget
|
||||
|
||||
@staticmethod
|
||||
def __visibility(widget: Union[IconGameWidget,ListGameWidget], filter_name, search_text) -> Tuple[bool, float]:
|
||||
if filter_name == "hidden":
|
||||
def __visibility(
|
||||
widget: Union[IconGameWidget, ListGameWidget], library_filter, search_text
|
||||
) -> Tuple[bool, float]:
|
||||
if library_filter == LibraryFilter.HIDDEN:
|
||||
visible = "hidden" in widget.rgame.metadata.tags
|
||||
elif "hidden" in widget.rgame.metadata.tags:
|
||||
visible = False
|
||||
elif filter_name == "installed":
|
||||
elif library_filter == LibraryFilter.INSTALLED:
|
||||
visible = widget.rgame.is_installed and not widget.rgame.is_unreal
|
||||
elif filter_name == "offline":
|
||||
elif library_filter == LibraryFilter.OFFLINE:
|
||||
visible = widget.rgame.can_run_offline and not widget.rgame.is_unreal
|
||||
elif filter_name == "32bit":
|
||||
elif library_filter == LibraryFilter.WIN32:
|
||||
visible = widget.rgame.is_win32 and not widget.rgame.is_unreal
|
||||
elif filter_name == "mac":
|
||||
elif library_filter == LibraryFilter.MAC:
|
||||
visible = widget.rgame.is_mac and not widget.rgame.is_unreal
|
||||
elif filter_name == "installable":
|
||||
elif library_filter == LibraryFilter.INSTALLABLE:
|
||||
visible = not widget.rgame.is_non_asset and not widget.rgame.is_unreal
|
||||
elif filter_name == "include_ue":
|
||||
elif library_filter == LibraryFilter.INCLUDE_UE:
|
||||
visible = True
|
||||
elif filter_name == "all":
|
||||
elif library_filter == LibraryFilter.ALL:
|
||||
visible = not widget.rgame.is_unreal
|
||||
else:
|
||||
visible = True
|
||||
|
@ -64,7 +67,7 @@ class LibraryWidgetController(QObject):
|
|||
|
||||
return visible, opacity
|
||||
|
||||
def filter_list(self, filter_name="all", search_text: str = ""):
|
||||
def filter_game_views(self, filter_name="all", search_text: str = ""):
|
||||
icon_widgets = self._icon_container.findChildren(IconGameWidget)
|
||||
list_widgets = self._list_container.findChildren(ListGameWidget)
|
||||
for iw in icon_widgets:
|
||||
|
@ -75,42 +78,52 @@ class LibraryWidgetController(QObject):
|
|||
visibility, opacity = self.__visibility(lw, filter_name, search_text)
|
||||
lw.setOpacity(opacity)
|
||||
lw.setVisible(visibility)
|
||||
self.sort_list(search_text)
|
||||
self.order_game_views(search_text=search_text)
|
||||
|
||||
@pyqtSlot()
|
||||
def sort_list(self, sort_by: str = ""):
|
||||
# lk: this is the existing sorting implemenation
|
||||
# lk: it sorts by installed then by title
|
||||
if sort_by:
|
||||
self._icon_container.layout().sort(lambda x: (sort_by not in x.widget().rgame.app_title.lower(),))
|
||||
else:
|
||||
self._icon_container.layout().sort(
|
||||
key=lambda x: (
|
||||
# Sort by grant date
|
||||
# x.widget().rgame.is_installed,
|
||||
# not x.widget().rgame.is_non_asset,
|
||||
# x.widget().rgame.grant_date(),
|
||||
# ), reverse=True
|
||||
not x.widget().rgame.is_installed,
|
||||
x.widget().rgame.is_non_asset,
|
||||
x.widget().rgame.app_title,
|
||||
)
|
||||
)
|
||||
def order_game_views(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""):
|
||||
list_widgets = self._list_container.findChildren(ListGameWidget)
|
||||
if sort_by:
|
||||
list_widgets.sort(key=lambda x: (sort_by not in x.rgame.app_title.lower(),))
|
||||
if search_text:
|
||||
self._icon_container.layout().sort(
|
||||
lambda x: (search_text not in x.widget().rgame.app_title.lower(),)
|
||||
)
|
||||
list_widgets.sort(key=lambda x: (search_text not in x.rgame.app_title.lower(),))
|
||||
else:
|
||||
list_widgets.sort(
|
||||
if (newest := order_by == LibraryOrder.NEWEST) or order_by == LibraryOrder.OLDEST:
|
||||
# Sort by grant date
|
||||
# key=lambda x: (x.rgame.is_installed, not x.rgame.is_non_asset, x.rgame.grant_date()), reverse=True
|
||||
self._icon_container.layout().sort(
|
||||
key=lambda x: (x.widget().rgame.is_installed, not x.widget().rgame.is_non_asset, x.widget().rgame.grant_date()),
|
||||
reverse=newest,
|
||||
)
|
||||
list_widgets.sort(
|
||||
key=lambda x: (x.rgame.is_installed, not x.rgame.is_non_asset, x.rgame.grant_date()),
|
||||
reverse=newest,
|
||||
)
|
||||
elif order_by == LibraryOrder.RECENT:
|
||||
# Sort by recently played
|
||||
self._icon_container.layout().sort(
|
||||
key=lambda x: (not x.widget().rgame.is_installed, x.widget().rgame.is_non_asset, x.widget().rgame.metadata.last_played),
|
||||
reverse=True,
|
||||
)
|
||||
list_widgets.sort(
|
||||
key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.metadata.last_played),
|
||||
reverse=True,
|
||||
)
|
||||
else:
|
||||
# Sort by title
|
||||
self._icon_container.layout().sort(
|
||||
key=lambda x: (not x.widget().rgame.is_installed, x.widget().rgame.is_non_asset, x.widget().rgame.app_title)
|
||||
)
|
||||
list_widgets.sort(
|
||||
key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.app_title)
|
||||
)
|
||||
|
||||
for idx, wl in enumerate(list_widgets):
|
||||
self._list_container.layout().insertWidget(idx, wl)
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(list)
|
||||
def update_list(self, app_names: List[str] = None):
|
||||
def update_game_views(self, app_names: List[str] = None):
|
||||
if not app_names:
|
||||
# lk: base it on icon widgets, the two lists should be identical
|
||||
icon_widgets = self._icon_container.findChildren(IconGameWidget)
|
||||
|
@ -129,7 +142,7 @@ class LibraryWidgetController(QObject):
|
|||
game = self.rcore.get_game(app_name)
|
||||
lw = ListGameWidget(game)
|
||||
self._list_container.layout().addWidget(lw)
|
||||
self.sort_list()
|
||||
self.order_game_views()
|
||||
|
||||
def __find_widget(self, app_name: str) -> Tuple[Union[IconGameWidget, None], Union[ListGameWidget, None]]:
|
||||
iw = self._icon_container.findChild(IconGameWidget, app_name)
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import platform as pf
|
||||
|
||||
from PyQt5.QtCore import QSettings, pyqtSignal, pyqtSlot, Qt
|
||||
from PyQt5.QtCore import QSettings, pyqtSignal, pyqtSlot, QSize, Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QWidget,
|
||||
QHBoxLayout,
|
||||
QComboBox, QToolButton, QMenu, QAction,
|
||||
QComboBox,
|
||||
QToolButton,
|
||||
QMenu,
|
||||
QAction,
|
||||
)
|
||||
from qtawesome import IconWidget
|
||||
|
||||
from rare.shared import RareCore
|
||||
from rare.models.options import options
|
||||
from rare.utils.extra_widgets import SelectViewWidget, ButtonLineEdit
|
||||
from rare.utils.misc import icon
|
||||
from .game_widgets import LibraryFilter, LibraryOrder
|
||||
|
||||
|
||||
class GameListHeadBar(QWidget):
|
||||
filterChanged: pyqtSignal = pyqtSignal(str)
|
||||
filterChanged: pyqtSignal = pyqtSignal(int)
|
||||
orderChanged: pyqtSignal = pyqtSignal(int)
|
||||
goto_import: pyqtSignal = pyqtSignal()
|
||||
goto_egl_sync: pyqtSignal = pyqtSignal()
|
||||
goto_eos_ubisoft: pyqtSignal = pyqtSignal()
|
||||
|
@ -27,43 +30,63 @@ class GameListHeadBar(QWidget):
|
|||
self.settings = QSettings(self)
|
||||
|
||||
self.filter = QComboBox(self)
|
||||
self.filter.addItem(self.tr("All games"), "all")
|
||||
self.filter.addItem(self.tr("Installed"), "installed")
|
||||
self.filter.addItem(self.tr("Offline"), "offline")
|
||||
# self.filter.addItem(self.tr("Hidden"), "hidden")
|
||||
self.filter.addItem(self.tr("All games"), LibraryFilter.ALL)
|
||||
self.filter.addItem(self.tr("Installed"), LibraryFilter.INSTALLED)
|
||||
self.filter.addItem(self.tr("Offline"), LibraryFilter.OFFLINE)
|
||||
# self.filter.addItem(self.tr("Hidden"), LibraryFilter.HIDDEN)
|
||||
if self.rcore.bit32_games:
|
||||
self.filter.addItem(self.tr("32bit games"), "32bit")
|
||||
self.filter.addItem(self.tr("32bit games"), LibraryFilter.WIN32)
|
||||
if self.rcore.mac_games:
|
||||
self.filter.addItem(self.tr("macOS games"), "mac")
|
||||
self.filter.addItem(self.tr("macOS games"), LibraryFilter.MAC)
|
||||
if self.rcore.origin_games:
|
||||
self.filter.addItem(self.tr("Exclude Origin"), "installable")
|
||||
self.filter.addItem(self.tr("Include Unreal"), "include_ue")
|
||||
|
||||
filter_default = "mac" if pf.system() == "Darwin" else "all"
|
||||
filter_index = i if (i := self.filter.findData(filter_default, Qt.UserRole)) >= 0 else 0
|
||||
self.filter.addItem(self.tr("Exclude Origin"), LibraryFilter.INSTALLABLE)
|
||||
self.filter.addItem(self.tr("Include Unreal"), LibraryFilter.INCLUDE_UE)
|
||||
try:
|
||||
self.filter.setCurrentIndex(self.settings.value("library_filter", filter_index, int))
|
||||
except TypeError:
|
||||
self.settings.setValue("library_filter", filter_index)
|
||||
self.filter.setCurrentIndex(filter_index)
|
||||
self.filter.currentIndexChanged.connect(self.filter_changed)
|
||||
self.filter.setCurrentIndex(self.filter.findData(
|
||||
LibraryFilter(self.settings.value(*options.library_filter))
|
||||
))
|
||||
except (TypeError, ValueError):
|
||||
self.settings.setValue(options.library_filter.key, options.library_filter.default)
|
||||
self.filter.setCurrentIndex(self.filter.findData(options.library_filter.default))
|
||||
self.filter.currentIndexChanged.connect(self.__filter_changed)
|
||||
|
||||
integrations_menu = QMenu(self)
|
||||
import_action = QAction(icon("mdi.import", "fa.arrow-down"), self.tr("Import Game"), integrations_menu)
|
||||
self.order = QComboBox(parent=self)
|
||||
sortings = {
|
||||
LibraryOrder.TITLE: self.tr("Title"),
|
||||
LibraryOrder.RECENT: self.tr("Recently played"),
|
||||
LibraryOrder.NEWEST: self.tr("Newest"),
|
||||
LibraryOrder.OLDEST: self.tr("Oldest"),
|
||||
}
|
||||
for data, text in sortings.items():
|
||||
self.order.addItem(text, data)
|
||||
try:
|
||||
self.order.setCurrentIndex(self.order.findData(
|
||||
LibraryOrder(self.settings.value(*options.library_order))
|
||||
))
|
||||
except (TypeError, ValueError):
|
||||
self.settings.setValue(options.library_order.key, options.library_order.default)
|
||||
self.order.setCurrentIndex(self.order.findData(options.library_order.default))
|
||||
self.order.currentIndexChanged.connect(self.__order_changed)
|
||||
|
||||
integrations_menu = QMenu(parent=self)
|
||||
import_action = QAction(
|
||||
icon("mdi.import", "fa.arrow-down"), self.tr("Import Game"), integrations_menu
|
||||
)
|
||||
|
||||
import_action.triggered.connect(self.goto_import)
|
||||
egl_sync_action = QAction(icon("mdi.sync", "fa.refresh"), self.tr("Sync with EGL"), integrations_menu)
|
||||
egl_sync_action.triggered.connect(self.goto_egl_sync)
|
||||
|
||||
eos_ubisoft_action = QAction(icon("mdi.rocket", "fa.rocket"), self.tr("Epic Overlay and Ubisoft"),
|
||||
integrations_menu)
|
||||
eos_ubisoft_action = QAction(
|
||||
icon("mdi.rocket", "fa.rocket"), self.tr("Epic Overlay and Ubisoft"), integrations_menu
|
||||
)
|
||||
eos_ubisoft_action.triggered.connect(self.goto_eos_ubisoft)
|
||||
|
||||
integrations_menu.addAction(import_action)
|
||||
integrations_menu.addAction(egl_sync_action)
|
||||
integrations_menu.addAction(eos_ubisoft_action)
|
||||
|
||||
integrations = QToolButton(self)
|
||||
integrations = QToolButton(parent=self)
|
||||
integrations.setText(self.tr("Integrations"))
|
||||
integrations.setMenu(integrations_menu)
|
||||
integrations.setPopupMode(QToolButton.InstantPopup)
|
||||
|
@ -76,8 +99,8 @@ class GameListHeadBar(QWidget):
|
|||
checked = QSettings().value("icon_view", True, bool)
|
||||
|
||||
installed_tooltip = self.tr("Installed games")
|
||||
self.installed_icon = IconWidget(parent=self)
|
||||
self.installed_icon.setIcon(icon("ph.floppy-disk-back-fill"))
|
||||
self.installed_icon = QLabel(parent=self)
|
||||
self.installed_icon.setPixmap(icon("ph.floppy-disk-back-fill").pixmap(QSize(16, 16)))
|
||||
self.installed_icon.setToolTip(installed_tooltip)
|
||||
self.installed_label = QLabel(parent=self)
|
||||
font = self.installed_label.font()
|
||||
|
@ -85,24 +108,25 @@ class GameListHeadBar(QWidget):
|
|||
self.installed_label.setFont(font)
|
||||
self.installed_label.setToolTip(installed_tooltip)
|
||||
available_tooltip = self.tr("Available games")
|
||||
self.available_icon = IconWidget(parent=self)
|
||||
self.available_icon.setIcon(icon("ph.floppy-disk-back-light"))
|
||||
self.available_icon = QLabel(parent=self)
|
||||
self.available_icon.setPixmap(icon("ph.floppy-disk-back-light").pixmap(QSize(16, 16)))
|
||||
self.available_icon.setToolTip(available_tooltip)
|
||||
self.available_label = QLabel(parent=self)
|
||||
self.available_label.setToolTip(available_tooltip)
|
||||
|
||||
self.view = SelectViewWidget(checked)
|
||||
|
||||
self.refresh_list = QPushButton()
|
||||
self.refresh_list = QPushButton(parent=self)
|
||||
self.refresh_list.setIcon(icon("fa.refresh")) # Reload icon
|
||||
self.refresh_list.clicked.connect(self.refresh_clicked)
|
||||
self.refresh_list.clicked.connect(self.__refresh_clicked)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 5, 0, 5)
|
||||
layout.addWidget(self.filter)
|
||||
layout.addWidget(self.order)
|
||||
layout.addStretch(0)
|
||||
layout.addWidget(integrations)
|
||||
layout.addStretch(5)
|
||||
layout.addStretch(2)
|
||||
layout.addWidget(self.search_bar)
|
||||
layout.addStretch(2)
|
||||
layout.addWidget(self.installed_icon)
|
||||
|
@ -113,17 +137,29 @@ class GameListHeadBar(QWidget):
|
|||
layout.addWidget(self.view)
|
||||
layout.addStretch(2)
|
||||
layout.addWidget(self.refresh_list)
|
||||
self.setLayout(layout)
|
||||
|
||||
def set_games_count(self, inst: int, avail: int) -> None:
|
||||
self.installed_label.setText(str(inst))
|
||||
self.available_label.setText(str(avail))
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_clicked(self):
|
||||
def __refresh_clicked(self):
|
||||
self.rcore.fetch()
|
||||
|
||||
def current_filter(self) -> int:
|
||||
return int(self.filter.currentData(Qt.UserRole))
|
||||
|
||||
@pyqtSlot(int)
|
||||
def filter_changed(self, index: int):
|
||||
self.filterChanged.emit(self.filter.itemData(index, Qt.UserRole))
|
||||
self.settings.setValue("library_filter", index)
|
||||
def __filter_changed(self, index: int):
|
||||
data = int(self.filter.itemData(index, Qt.UserRole))
|
||||
self.filterChanged.emit(data)
|
||||
self.settings.setValue(options.library_filter.key, data)
|
||||
|
||||
def current_order(self) -> int:
|
||||
return int(self.order.currentData(Qt.UserRole))
|
||||
|
||||
@pyqtSlot(int)
|
||||
def __order_changed(self, index: int):
|
||||
data = int(self.order.itemData(index, Qt.UserRole))
|
||||
self.orderChanged.emit(data)
|
||||
self.settings.setValue(options.library_order.key, data)
|
||||
|
|
|
@ -13,9 +13,10 @@ from legendary.models.game import InstalledGame
|
|||
from rare.lgndr.glue.exception import LgndrException
|
||||
from rare.models.pathspec import PathSpec
|
||||
from rare.shared import RareCore
|
||||
from rare.shared.workers.wine_resolver import WineResolver
|
||||
from rare.shared.workers.wine_resolver import WinePathResolver
|
||||
from rare.ui.components.tabs.games.integrations.egl_sync_group import Ui_EGLSyncGroup
|
||||
from rare.ui.components.tabs.games.integrations.egl_sync_list_group import Ui_EGLSyncListGroup
|
||||
from rare.utils import runners
|
||||
from rare.widgets.elide_label import ElideLabel
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
|
||||
|
@ -87,10 +88,10 @@ class EGLSyncGroup(QGroupBox):
|
|||
|
||||
def __run_wine_resolver(self):
|
||||
self.egl_path_info.setText(self.tr("Updating..."))
|
||||
wine_resolver = WineResolver(
|
||||
self.core,
|
||||
PathSpec.egl_programdata,
|
||||
"default"
|
||||
wine_resolver = WinePathResolver(
|
||||
self.core.get_app_launch_command("default"),
|
||||
runners.get_environment(self.core.get_app_environment("default")),
|
||||
PathSpec.egl_programdata()
|
||||
)
|
||||
wine_resolver.signals.result_ready.connect(self.__on_wine_resolver_result)
|
||||
QThreadPool.globalInstance().start(wine_resolver)
|
||||
|
@ -122,14 +123,8 @@ class EGLSyncGroup(QGroupBox):
|
|||
os.path.join(path, "dosdevices/c:")
|
||||
):
|
||||
# path is a wine prefix
|
||||
path = os.path.join(
|
||||
path,
|
||||
"dosdevices/c:",
|
||||
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests",
|
||||
)
|
||||
elif not path.rstrip("/").endswith(
|
||||
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests"
|
||||
):
|
||||
path = PathSpec.prefix_egl_programdata(path)
|
||||
elif not path.rstrip("/").endswith(PathSpec.wine_egl_programdata()):
|
||||
# lower() might or might not be needed in the check
|
||||
return False, path, IndicatorReasonsCommon.WRONG_FORMAT
|
||||
if os.path.exists(path):
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from rare.components.tabs.settings.widgets.linux import LinuxSettings
|
||||
from rare.components.tabs.settings.widgets.wine import LinuxSettings
|
||||
from rare.shared import ArgumentsSingleton
|
||||
from rare.widgets.side_tab import SideTabWidget
|
||||
from .about import About
|
||||
from .debug import DebugSettings
|
||||
from .game_settings import DefaultGameSettings
|
||||
from .game import DefaultGameSettings
|
||||
from .legendary import LegendarySettings
|
||||
from .rare import RareSettings
|
||||
|
||||
|
@ -13,9 +13,14 @@ class SettingsTab(SideTabWidget):
|
|||
super(SettingsTab, self).__init__(parent=parent)
|
||||
self.args = ArgumentsSingleton()
|
||||
|
||||
self.rare_index = self.addTab(RareSettings(self), "Rare")
|
||||
self.legendary_index = self.addTab(LegendarySettings(self), "Legendary")
|
||||
self.settings_index = self.addTab(DefaultGameSettings(True, self), self.tr("Default Settings"))
|
||||
rare_settings = RareSettings(self)
|
||||
self.rare_index = self.addTab(rare_settings, "Rare")
|
||||
|
||||
legendary_settings = LegendarySettings(self)
|
||||
self.legendary_index = self.addTab(legendary_settings, "Legendary")
|
||||
|
||||
game_settings = DefaultGameSettings(True, self)
|
||||
self.settings_index = self.addTab(game_settings, self.tr("Defaults"))
|
||||
|
||||
self.about = About(self)
|
||||
self.about_index = self.addTab(self.about, "About", "About")
|
||||
|
|
95
rare/components/tabs/settings/game.py
Normal file
95
rare/components/tabs/settings/game.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
import platform as pf
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import QSettings, Qt
|
||||
from PyQt5.QtGui import QShowEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QLabel, QFormLayout
|
||||
)
|
||||
|
||||
from components.tabs.settings.widgets.dxvk import DxvkSettings
|
||||
from components.tabs.settings.widgets.mangohud import MangoHudSettings
|
||||
from rare.components.tabs.settings.widgets.env_vars import EnvVars
|
||||
from rare.components.tabs.settings.widgets.wine import LinuxSettings
|
||||
from rare.components.tabs.settings.widgets.proton import ProtonSettings
|
||||
from rare.components.tabs.settings.widgets.wrapper import WrapperSettings
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.ui.components.tabs.settings.game import Ui_GameSettings
|
||||
|
||||
logger = getLogger("GameSettings")
|
||||
|
||||
|
||||
class DefaultGameSettings(QWidget):
|
||||
# variable to no update when changing game
|
||||
change = False
|
||||
app_name: str
|
||||
|
||||
def __init__(self, is_default, parent=None):
|
||||
super(DefaultGameSettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_GameSettings()
|
||||
self.ui.setupUi(self)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.settings = QSettings(self)
|
||||
|
||||
self.wrapper_settings = WrapperSettings(self)
|
||||
self.ui.launch_layout.setWidget(
|
||||
self.ui.launch_layout.getWidgetPosition(self.ui.wrapper_label)[0],
|
||||
QFormLayout.FieldRole,
|
||||
self.wrapper_settings
|
||||
)
|
||||
|
||||
self.env_vars = EnvVars(self)
|
||||
# dxvk
|
||||
self.dxvk = DxvkSettings(self)
|
||||
self.dxvk.environ_changed.connect(self.env_vars.reset_model)
|
||||
self.dxvk.load_settings(self.app_name)
|
||||
|
||||
self.mangohud = MangoHudSettings(self)
|
||||
self.mangohud.environ_changed.connect(self.environ_changed)
|
||||
self.mangohud.load_settings(self.name)
|
||||
|
||||
if pf.system() != "Windows":
|
||||
self.linux_settings = LinuxAppSettings(self)
|
||||
self.ui.game_settings_layout.addWidget(self.linux_settings)
|
||||
|
||||
self.linux_settings.mangohud.set_wrapper_activated.connect(
|
||||
lambda active: self.wrapper_settings.add_wrapper("mangohud")
|
||||
if active else self.wrapper_settings.delete_wrapper("mangohud"))
|
||||
self.linux_settings.environ_changed.connect(self.env_vars.reset_model)
|
||||
|
||||
if pf.system() != "Darwin":
|
||||
self.proton_settings = ProtonSettings(self.linux_settings, self.wrapper_settings)
|
||||
self.linux_settings.ui.linux_settings_layout.insertWidget(0, self.proton_settings)
|
||||
self.proton_settings.environ_changed.connect(self.env_vars.reset_model)
|
||||
|
||||
self.ui.game_settings_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.ui.main_layout.addWidget(self.dxvk)
|
||||
self.ui.main_layout.addWidget(self.mangohud)
|
||||
self.ui.main_layout.addWidget(self.env_vars)
|
||||
|
||||
if is_default:
|
||||
self.ui.launch_layout.removeRow(self.ui.skip_update_label)
|
||||
self.ui.launch_layout.removeRow(self.ui.offline_label)
|
||||
self.ui.launch_layout.removeRow(self.ui.launch_params_label)
|
||||
self.ui.launch_layout.removeRow(self.ui.override_exe_label)
|
||||
self.ui.launch_layout.removeRow(self.ui.pre_launch_label)
|
||||
self.load_settings("default")
|
||||
|
||||
def load_settings(self, app_name):
|
||||
self.app_name = app_name
|
||||
self.wrapper_settings.load_settings(app_name)
|
||||
|
||||
if pf.system() != "Windows":
|
||||
self.linux_settings.update_game(app_name)
|
||||
if pf.system() != "Darwin":
|
||||
proton = self.wrapper_settings.wrappers.get("proton", "")
|
||||
if proton:
|
||||
proton = proton.text
|
||||
self.proton_settings.load_settings(app_name, proton)
|
||||
else:
|
||||
proton = ""
|
||||
self.linux_settings.ui.wine_groupbox.setDisabled(bool(proton))
|
||||
|
||||
self.env_vars.update_game(app_name)
|
|
@ -1,106 +0,0 @@
|
|||
import platform
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import QSettings, Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QLabel
|
||||
)
|
||||
|
||||
from rare.components.tabs.settings.widgets.env_vars import EnvVars
|
||||
from rare.components.tabs.settings.widgets.wrapper import WrapperSettings
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.ui.components.tabs.settings.game_settings import Ui_GameSettings
|
||||
|
||||
if platform.system() != "Windows":
|
||||
from rare.components.tabs.settings.widgets.linux import LinuxSettings
|
||||
if platform.system() != "Darwin":
|
||||
from rare.components.tabs.settings.widgets.proton import ProtonSettings
|
||||
|
||||
logger = getLogger("GameSettings")
|
||||
|
||||
|
||||
class DefaultGameSettings(QWidget):
|
||||
# variable to no update when changing game
|
||||
change = False
|
||||
app_name: str
|
||||
|
||||
def __init__(self, is_default, parent=None):
|
||||
super(DefaultGameSettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_GameSettings()
|
||||
self.ui.setupUi(self)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.settings = QSettings()
|
||||
|
||||
self.wrapper_settings = WrapperSettings()
|
||||
|
||||
self.ui.launch_settings_group.layout().addRow(
|
||||
QLabel("Wrapper"), self.wrapper_settings
|
||||
)
|
||||
|
||||
self.env_vars = EnvVars(self)
|
||||
self.ui.game_settings_layout.addWidget(self.env_vars)
|
||||
|
||||
if platform.system() != "Windows":
|
||||
self.linux_settings = LinuxAppSettings()
|
||||
if platform.system() != "Darwin":
|
||||
self.proton_settings = ProtonSettings(self.linux_settings, self.wrapper_settings)
|
||||
self.ui.proton_layout.addWidget(self.proton_settings)
|
||||
self.proton_settings.environ_changed.connect(self.env_vars.reset_model)
|
||||
|
||||
# FIXME: Remove the spacerItem and margins from the linux settings
|
||||
# FIXME: This should be handled differently at soem point in the future
|
||||
# NOTE: specerItem has been removed
|
||||
self.linux_settings.layout().setContentsMargins(0, 0, 0, 0)
|
||||
# FIXME: End of FIXME
|
||||
self.ui.linux_settings_layout.addWidget(self.linux_settings)
|
||||
self.ui.linux_settings_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.ui.game_settings_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.linux_settings.mangohud.set_wrapper_activated.connect(
|
||||
lambda active: self.wrapper_settings.add_wrapper("mangohud")
|
||||
if active else self.wrapper_settings.delete_wrapper("mangohud"))
|
||||
self.linux_settings.environ_changed.connect(self.env_vars.reset_model)
|
||||
else:
|
||||
self.ui.linux_settings_widget.setVisible(False)
|
||||
|
||||
if is_default:
|
||||
self.ui.launch_settings_layout.removeRow(self.ui.skip_update)
|
||||
self.ui.launch_settings_layout.removeRow(self.ui.offline)
|
||||
self.ui.launch_settings_layout.removeRow(self.ui.launch_params)
|
||||
|
||||
self.load_settings("default")
|
||||
|
||||
def load_settings(self, app_name):
|
||||
self.app_name = app_name
|
||||
self.wrapper_settings.load_settings(app_name)
|
||||
if platform.system() != "Windows":
|
||||
self.linux_settings.update_game(app_name)
|
||||
proton = self.wrapper_settings.wrappers.get("proton", "")
|
||||
if proton:
|
||||
proton = proton.text
|
||||
if platform.system() != "Darwin":
|
||||
self.proton_settings.load_settings(app_name, proton)
|
||||
else:
|
||||
proton = ""
|
||||
if proton:
|
||||
self.linux_settings.ui.wine_groupbox.setEnabled(False)
|
||||
else:
|
||||
self.linux_settings.ui.wine_groupbox.setEnabled(True)
|
||||
self.env_vars.update_game(app_name)
|
||||
|
||||
|
||||
if platform.system() != "Windows":
|
||||
class LinuxAppSettings(LinuxSettings):
|
||||
def __init__(self):
|
||||
super(LinuxAppSettings, self).__init__()
|
||||
|
||||
def update_game(self, app_name):
|
||||
self.name = app_name
|
||||
self.wine_prefix.setText(self.load_prefix())
|
||||
self.wine_exec.setText(self.load_setting(self.name, "wine_executable"))
|
||||
|
||||
self.dxvk.load_settings(self.name)
|
||||
|
||||
self.mangohud.load_settings(self.name)
|
|
@ -6,6 +6,7 @@ from typing import Tuple, List
|
|||
from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSettings
|
||||
from PyQt5.QtWidgets import QSizePolicy, QWidget, QFileDialog, QMessageBox
|
||||
|
||||
from rare.models.options import options
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.shared.workers.worker import Worker
|
||||
from rare.ui.components.tabs.settings.legendary import Ui_LegendarySettings
|
||||
|
@ -100,33 +101,30 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
)
|
||||
self.locale_layout.addWidget(self.locale_edit)
|
||||
|
||||
self.fetch_win32_check.setChecked(self.settings.value("win32_meta", False, bool))
|
||||
self.fetch_win32_check.setChecked(self.settings.value(*options.win32_meta))
|
||||
self.fetch_win32_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("win32_meta", self.fetch_win32_check.isChecked())
|
||||
lambda: self.settings.setValue(options.win32_meta.key, self.fetch_win32_check.isChecked())
|
||||
)
|
||||
|
||||
self.fetch_macos_check.setChecked(self.settings.value("macos_meta", pf.system() == "Darwin", bool))
|
||||
self.fetch_macos_check.setChecked(self.settings.value(*options.macos_meta))
|
||||
self.fetch_macos_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("macos_meta", self.fetch_macos_check.isChecked())
|
||||
lambda: self.settings.setValue(options.macos_meta.key, self.fetch_macos_check.isChecked())
|
||||
)
|
||||
self.fetch_macos_check.setDisabled(pf.system() == "Darwin")
|
||||
|
||||
self.fetch_unreal_check.setChecked(self.settings.value("unreal_meta", False, bool))
|
||||
self.fetch_unreal_check.setChecked(self.settings.value(*options.unreal_meta))
|
||||
self.fetch_unreal_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked())
|
||||
lambda: self.settings.setValue(options.unreal_meta.key, self.fetch_unreal_check.isChecked())
|
||||
)
|
||||
|
||||
self.exclude_non_asset_check.setChecked(
|
||||
self.settings.value("exclude_non_asset", False, bool)
|
||||
)
|
||||
self.exclude_non_asset_check.setChecked(self.settings.value(*options.exclude_non_asset))
|
||||
self.exclude_non_asset_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("exclude_non_asset", self.exclude_non_asset_check.isChecked())
|
||||
)
|
||||
self.exclude_entitlements_check.setChecked(
|
||||
self.settings.value("exclude_entitlements", False, bool)
|
||||
lambda: self.settings.setValue(options.exclude_non_asset.key, self.exclude_non_asset_check.isChecked())
|
||||
)
|
||||
|
||||
self.exclude_entitlements_check.setChecked(self.settings.value(*options.exclude_entitlements))
|
||||
self.exclude_entitlements_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("exclude_entitlements", self.exclude_entitlements_check.isChecked())
|
||||
lambda: self.settings.setValue(options.exclude_entitlements.key, self.exclude_entitlements_check.isChecked())
|
||||
)
|
||||
|
||||
self.refresh_metadata_button.clicked.connect(self.refresh_metadata)
|
||||
|
|
|
@ -8,6 +8,7 @@ from PyQt5.QtCore import QSettings, Qt
|
|||
from PyQt5.QtWidgets import QWidget, QMessageBox
|
||||
|
||||
from rare.components.tabs.settings.widgets.rpc import RPCSettings
|
||||
from rare.models.options import options
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.ui.components.tabs.settings.rare import Ui_RareSettings
|
||||
from rare.utils.misc import (
|
||||
|
@ -39,18 +40,8 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
super(RareSettings, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
# (widget_name, option_name, default)
|
||||
self.checkboxes = [
|
||||
(self.sys_tray, "sys_tray", True),
|
||||
(self.auto_update, "auto_update", False),
|
||||
(self.confirm_start, "confirm_start", False),
|
||||
(self.auto_sync_cloud, "auto_sync_cloud", False),
|
||||
(self.notification, "notification", True),
|
||||
(self.save_size, "save_size", False),
|
||||
(self.log_games, "show_console", False),
|
||||
]
|
||||
self.settings = QSettings(self)
|
||||
|
||||
self.settings = QSettings()
|
||||
language = self.settings.value("language", self.core.language_code, type=str)
|
||||
|
||||
# Select lang
|
||||
|
@ -85,29 +76,37 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
self.rpc = RPCSettings(self)
|
||||
self.right_layout.insertWidget(1, self.rpc, alignment=Qt.AlignTop)
|
||||
|
||||
self.init_checkboxes(self.checkboxes)
|
||||
self.sys_tray.setChecked(self.settings.value(*options.sys_tray))
|
||||
self.sys_tray.stateChanged.connect(
|
||||
lambda: self.settings.setValue("sys_tray", self.sys_tray.isChecked())
|
||||
lambda: self.settings.setValue(options.sys_tray.key, self.sys_tray.isChecked())
|
||||
)
|
||||
|
||||
self.auto_update.setChecked(self.settings.value(*options.auto_update))
|
||||
self.auto_update.stateChanged.connect(
|
||||
lambda: self.settings.setValue("auto_update", self.auto_update.isChecked())
|
||||
lambda: self.settings.setValue(options.auto_update.key, self.auto_update.isChecked())
|
||||
)
|
||||
|
||||
self.confirm_start.setChecked(self.settings.value(*options.confirm_start))
|
||||
self.confirm_start.stateChanged.connect(
|
||||
lambda: self.settings.setValue(
|
||||
"confirm_start", self.confirm_start.isChecked()
|
||||
)
|
||||
lambda: self.settings.setValue(options.confirm_start.key, self.confirm_start.isChecked())
|
||||
)
|
||||
|
||||
self.auto_sync_cloud.setChecked(self.settings.value(*options.auto_sync_cloud))
|
||||
self.auto_sync_cloud.stateChanged.connect(
|
||||
lambda: self.settings.setValue(
|
||||
"auto_sync_cloud", self.auto_sync_cloud.isChecked()
|
||||
)
|
||||
lambda: self.settings.setValue(options.auto_sync_cloud.key, self.auto_sync_cloud.isChecked())
|
||||
)
|
||||
|
||||
self.notification.setChecked(self.settings.value(*options.notification))
|
||||
self.notification.stateChanged.connect(
|
||||
lambda: self.settings.setValue("notification", self.notification.isChecked())
|
||||
lambda: self.settings.setValue(options.notification.key, self.notification.isChecked())
|
||||
)
|
||||
|
||||
self.save_size.setChecked(self.settings.value(*options.save_size))
|
||||
self.save_size.stateChanged.connect(self.save_window_size)
|
||||
|
||||
self.log_games.setChecked(self.settings.value(*options.log_games))
|
||||
self.log_games.stateChanged.connect(
|
||||
lambda: self.settings.setValue("show_console", self.log_games.isChecked())
|
||||
lambda: self.settings.setValue(options.log_games.key, self.log_games.isChecked())
|
||||
)
|
||||
|
||||
if desktop_links_supported():
|
||||
|
@ -221,13 +220,8 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
subprocess.Popen([opener, log_dir()])
|
||||
|
||||
def save_window_size(self):
|
||||
self.settings.setValue("save_size", self.save_size.isChecked())
|
||||
self.settings.remove("window_size")
|
||||
self.settings.setValue(options.save_size.key, self.save_size.isChecked())
|
||||
self.settings.remove(options.window_size.key)
|
||||
|
||||
def update_lang(self, i: int):
|
||||
self.settings.setValue("language", languages[i][0])
|
||||
|
||||
def init_checkboxes(self, checkboxes):
|
||||
for cb in checkboxes:
|
||||
widget, option, default = cb
|
||||
widget.setChecked(self.settings.value(option, default, bool))
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from .overlay_settings import OverlaySettings, CustomOption
|
||||
from .overlays import OverlaySettings, CustomOption
|
||||
|
||||
|
||||
class DxvkSettings(OverlaySettings):
|
||||
def __init__(self):
|
||||
def __init__(self, parent=None):
|
||||
super(DxvkSettings, self).__init__(
|
||||
[
|
||||
("fps", QCoreApplication.translate("DxvkSettings", "FPS")),
|
||||
|
@ -19,7 +19,8 @@ class DxvkSettings(OverlaySettings):
|
|||
[
|
||||
(CustomOption.number_input("scale", 1, True), QCoreApplication.translate("DxvkSettings", "Scale"))
|
||||
],
|
||||
"DXVK_HUD", "0"
|
||||
"DXVK_HUD", "0",
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self.setTitle(self.tr("DXVK Settings"))
|
||||
|
|
|
@ -11,8 +11,10 @@ from rare.lgndr.core import LegendaryCore
|
|||
from rare.utils.misc import icon
|
||||
|
||||
if platform.system() != "Windows":
|
||||
from rare.utils.runners.wine import get_wine_environment
|
||||
if platform.system() != "Darwin":
|
||||
from rare.utils import proton
|
||||
from rare.utils.runners.proton import get_steam_environment
|
||||
|
||||
|
||||
|
||||
class EnvVarsTableModel(QAbstractTableModel):
|
||||
|
@ -27,14 +29,13 @@ class EnvVarsTableModel(QAbstractTableModel):
|
|||
self.__data_map: ChainMap = ChainMap()
|
||||
|
||||
self.__readonly = [
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
"WINEPREFIX",
|
||||
"DXVK_HUD",
|
||||
"MANGOHUD_CONFIG",
|
||||
]
|
||||
if platform.system() != "Windows":
|
||||
self.__readonly.extend(get_wine_environment().keys())
|
||||
if platform.system() != "Darwin":
|
||||
self.__readonly.extend(proton.get_steam_environment(None).keys())
|
||||
self.__readonly.extend(get_steam_environment().keys())
|
||||
|
||||
self.__default: str = "default"
|
||||
self.__appname: str = None
|
||||
|
@ -256,8 +257,6 @@ class EnvVarsTableModel(QAbstractTableModel):
|
|||
if __name__ == "__main__":
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QTableView, QHeaderView
|
||||
|
||||
from rare.resources import static_css
|
||||
from rare.resources.stylesheets import RareStyle
|
||||
from rare.utils.misc import set_style_sheet
|
||||
from legendary.core import LegendaryCore
|
||||
|
||||
|
|
|
@ -5,18 +5,17 @@ from PyQt5.QtCore import QCoreApplication, pyqtSignal
|
|||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from .overlay_settings import OverlaySettings, CustomOption, ActivationStates
|
||||
from rare.utils import config_helper
|
||||
from .overlays import OverlaySettings, CustomOption, ActivationStates
|
||||
|
||||
position_values = ["default", "top-left", "top-right", "middle-left", "middle-right", "bottom-left",
|
||||
"bottom-right", "top-center"]
|
||||
|
||||
|
||||
class MangoHudSettings(OverlaySettings):
|
||||
|
||||
set_wrapper_activated = pyqtSignal(bool)
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, parent=None):
|
||||
super(MangoHudSettings, self).__init__(
|
||||
[
|
||||
("fps", QCoreApplication.translate("MangoSettings", "FPS")),
|
||||
|
@ -45,7 +44,8 @@ class MangoHudSettings(OverlaySettings):
|
|||
QCoreApplication.translate("MangoSettings", "Position")
|
||||
)
|
||||
],
|
||||
"MANGOHUD_CONFIG", "no_display", set_activation_state=self.set_activation_state
|
||||
"MANGOHUD_CONFIG", "no_display", set_activation_state=self.set_activation_state,
|
||||
parent=parent
|
||||
)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.setTitle(self.tr("MangoHud Settings"))
|
||||
|
|
|
@ -82,8 +82,8 @@ class OverlaySettings(QGroupBox, Ui_OverlaySettings):
|
|||
|
||||
def __init__(self, checkboxes_map: List[Tuple[str, str]], value_map: List[Tuple[CustomOption, str]],
|
||||
config_env_var_name: str, no_display_value: str,
|
||||
set_activation_state: Callable[[Enum], None] = lambda x: None):
|
||||
super(OverlaySettings, self).__init__()
|
||||
set_activation_state: Callable[[Enum], None] = lambda x: None, parent=None):
|
||||
super(OverlaySettings, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.config_env_var_name = config_env_var_name
|
|
@ -2,18 +2,18 @@ import os
|
|||
import shutil
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QCheckBox, QFileDialog
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QCheckBox, QFileDialog, QWidget
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.utils import config_helper
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
|
||||
|
||||
class PreLaunchSettings(QHBoxLayout):
|
||||
class PreLaunchSettings(QWidget):
|
||||
app_name: str
|
||||
|
||||
def __init__(self):
|
||||
super(PreLaunchSettings, self).__init__()
|
||||
def __init__(self, parent=None):
|
||||
super(PreLaunchSettings, self).__init__(parent=parent)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.edit = PathEdit(
|
||||
path="",
|
||||
|
@ -22,12 +22,15 @@ class PreLaunchSettings(QHBoxLayout):
|
|||
edit_func=self.edit_command,
|
||||
save_func=self.save_pre_launch_command,
|
||||
)
|
||||
self.layout().addWidget(self.edit)
|
||||
|
||||
self.wait_check = QCheckBox(self.tr("Wait for finish"))
|
||||
self.layout().addWidget(self.wait_check)
|
||||
self.wait_check.stateChanged.connect(self.save_wait_finish)
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(self.edit)
|
||||
layout.addWidget(self.wait_check)
|
||||
|
||||
def edit_command(self, text: str) -> Tuple[bool, str, int]:
|
||||
if not text.strip():
|
||||
return True, text, IndicatorReasonsCommon.VALID
|
||||
|
|
|
@ -1,85 +1,106 @@
|
|||
import os
|
||||
from logging import getLogger
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
from typing import Tuple, Union, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtGui import QShowEvent
|
||||
from PyQt5.QtWidgets import QGroupBox, QFileDialog
|
||||
|
||||
from rare.components.tabs.settings import LinuxSettings
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.models.wrapper import Wrapper, WrapperType
|
||||
from rare.shared import RareCore
|
||||
from rare.shared.wrappers import Wrappers
|
||||
from rare.ui.components.tabs.settings.proton import Ui_ProtonSettings
|
||||
from rare.utils import config_helper, proton
|
||||
from rare.utils import config_helper as config
|
||||
from rare.utils.runners import proton
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
from .wrapper import WrapperSettings
|
||||
|
||||
logger = getLogger("Proton")
|
||||
logger = getLogger("ProtonSettings")
|
||||
|
||||
|
||||
class ProtonSettings(QGroupBox):
|
||||
# str: option key
|
||||
environ_changed = pyqtSignal(str)
|
||||
app_name: str
|
||||
changeable = True
|
||||
environ_changed: pyqtSignal = pyqtSignal(str)
|
||||
# bool: state
|
||||
tool_enabled: pyqtSignal = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, linux_settings: LinuxSettings, wrapper_settings: WrapperSettings):
|
||||
super(ProtonSettings, self).__init__()
|
||||
def __init__(self, parent=None):
|
||||
super(ProtonSettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_ProtonSettings()
|
||||
self.ui.setupUi(self)
|
||||
self._linux_settings = linux_settings
|
||||
self._wrapper_settings = wrapper_settings
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.possible_proton_combos = proton.find_proton_combos()
|
||||
|
||||
self.ui.proton_combo.addItems(self.possible_proton_combos)
|
||||
self.ui.proton_combo.currentIndexChanged.connect(self.change_proton)
|
||||
|
||||
self.ui.proton_combo.currentIndexChanged.connect(self.__on_proton_changed)
|
||||
self.proton_prefix = PathEdit(
|
||||
file_mode=QFileDialog.DirectoryOnly,
|
||||
edit_func=self.proton_prefix_edit,
|
||||
save_func=self.proton_prefix_save,
|
||||
placeholder=self.tr("Please select path for proton prefix")
|
||||
placeholder=self.tr("Please select path for proton prefix"),
|
||||
)
|
||||
self.ui.prefix_layout.addWidget(self.proton_prefix)
|
||||
|
||||
def change_proton(self, i):
|
||||
if not self.changeable:
|
||||
return
|
||||
# First combo box entry: Don't use Proton
|
||||
if i == 0:
|
||||
self._wrapper_settings.delete_wrapper("proton")
|
||||
config_helper.remove_option(self.app_name, "no_wine")
|
||||
config_helper.remove_option(f"{self.app_name}.env", "STEAM_COMPAT_DATA_PATH")
|
||||
self.environ_changed.emit("STEAM_COMPAT_DATA_PATH")
|
||||
config_helper.remove_option(f"{self.app_name}.env", "STEAM_COMPAT_CLIENT_INSTALL_PATH")
|
||||
self.environ_changed.emit("STEAM_COMPAT_CLIENT_INSTALL_PATH")
|
||||
self.app_name: str = "default"
|
||||
self.core = RareCore.instance().core()
|
||||
self.wrappers: Wrappers = RareCore.instance().wrappers()
|
||||
self.tool_wrapper: Optional[Wrapper] = None
|
||||
|
||||
self.proton_prefix.setEnabled(False)
|
||||
self.proton_prefix.setText("")
|
||||
|
||||
self._linux_settings.ui.wine_groupbox.setEnabled(True)
|
||||
else:
|
||||
self.proton_prefix.setEnabled(True)
|
||||
self._linux_settings.ui.wine_groupbox.setEnabled(False)
|
||||
wrapper = self.possible_proton_combos[i - 1]
|
||||
self._wrapper_settings.add_wrapper(wrapper)
|
||||
config_helper.add_option(self.app_name, "no_wine", "true")
|
||||
config_helper.add_option(
|
||||
f"{self.app_name}.env",
|
||||
"STEAM_COMPAT_CLIENT_INSTALL_PATH",
|
||||
str(Path.home().joinpath(".steam", "steam"))
|
||||
def showEvent(self, a0: QShowEvent) -> None:
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
self.ui.proton_combo.blockSignals(True)
|
||||
self.ui.proton_combo.clear()
|
||||
self.ui.proton_combo.addItem(self.tr("Don't use a compatibility tool"), None)
|
||||
tools = proton.find_tools()
|
||||
for tool in tools:
|
||||
self.ui.proton_combo.addItem(tool.name, tool)
|
||||
try:
|
||||
wrapper = next(
|
||||
filter(lambda w: w.is_compat_tool, self.wrappers.get_game_wrapper_list(self.app_name))
|
||||
)
|
||||
self.environ_changed.emit("STEAM_COMPAT_CLIENT_INSTALL_PATH")
|
||||
self.tool_wrapper = wrapper
|
||||
tool = next(filter(lambda t: t.checksum == wrapper.checksum, tools))
|
||||
index = self.ui.proton_combo.findData(tool)
|
||||
except StopIteration:
|
||||
index = 0
|
||||
self.ui.proton_combo.setCurrentIndex(index)
|
||||
self.ui.proton_combo.blockSignals(False)
|
||||
enabled = bool(self.ui.proton_combo.currentIndex())
|
||||
self.proton_prefix.blockSignals(True)
|
||||
self.proton_prefix.setText(config.get_envvar(self.app_name, "STEAM_COMPAT_DATA_PATH", fallback=""))
|
||||
self.proton_prefix.setEnabled(enabled)
|
||||
self.proton_prefix.blockSignals(False)
|
||||
super().showEvent(a0)
|
||||
|
||||
self.proton_prefix.setText(os.path.expanduser("~/.proton"))
|
||||
def __on_proton_changed(self, index):
|
||||
steam_tool: Union[proton.ProtonTool, proton.CompatibilityTool] = self.ui.proton_combo.itemData(index)
|
||||
|
||||
# Don't use Wine
|
||||
self._linux_settings.wine_exec.setText("")
|
||||
self._linux_settings.wine_prefix.setText("")
|
||||
steam_environ = proton.get_steam_environment(steam_tool)
|
||||
for key, value in steam_environ.items():
|
||||
if not value:
|
||||
config.remove_envvar(self.app_name, key)
|
||||
else:
|
||||
config.add_envvar(self.app_name, key, value)
|
||||
self.environ_changed.emit(key)
|
||||
|
||||
config_helper.save_config()
|
||||
wrappers = self.wrappers.get_game_wrapper_list(self.app_name)
|
||||
if self.tool_wrapper and self.tool_wrapper in wrappers:
|
||||
wrappers.remove(self.tool_wrapper)
|
||||
if steam_tool is None:
|
||||
self.tool_wrapper = None
|
||||
else:
|
||||
wrapper = Wrapper(
|
||||
command=steam_tool.command(), name=steam_tool.name, wtype=WrapperType.COMPAT_TOOL
|
||||
)
|
||||
wrappers.append(wrapper)
|
||||
self.tool_wrapper = wrapper
|
||||
self.wrappers.set_game_wrapper_list(self.app_name, wrappers)
|
||||
|
||||
def proton_prefix_edit(self, text: str) -> Tuple[bool, str, int]:
|
||||
self.proton_prefix.setEnabled(steam_tool is not None)
|
||||
self.proton_prefix.setText(os.path.expanduser("~/.proton") if steam_tool is not None else "")
|
||||
|
||||
self.tool_enabled.emit(steam_tool is not None)
|
||||
config.save_config()
|
||||
|
||||
@staticmethod
|
||||
def proton_prefix_edit(text: str) -> Tuple[bool, str, int]:
|
||||
if not text:
|
||||
return False, text, IndicatorReasonsCommon.EMPTY
|
||||
parent_dir = os.path.dirname(text)
|
||||
|
@ -88,28 +109,9 @@ class ProtonSettings(QGroupBox):
|
|||
def proton_prefix_save(self, text: str):
|
||||
if not text:
|
||||
return
|
||||
config_helper.add_option(
|
||||
f"{self.app_name}.env", "STEAM_COMPAT_DATA_PATH", text
|
||||
)
|
||||
config.add_envvar(self.app_name, "STEAM_COMPAT_DATA_PATH", text)
|
||||
self.environ_changed.emit("STEAM_COMPAT_DATA_PATH")
|
||||
config_helper.save_config()
|
||||
config.save_config()
|
||||
|
||||
def load_settings(self, app_name: str, proton: str):
|
||||
self.changeable = False
|
||||
def load_settings(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
proton = proton.replace('"', "")
|
||||
self.proton_prefix.setEnabled(bool(proton))
|
||||
if proton:
|
||||
self.ui.proton_combo.setCurrentText(
|
||||
f'"{proton.replace(" run", "")}" run'
|
||||
)
|
||||
else:
|
||||
self.ui.proton_combo.setCurrentIndex(0)
|
||||
|
||||
proton_prefix = self.core.lgd.config.get(
|
||||
f"{app_name}.env",
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
fallback="",
|
||||
)
|
||||
self.proton_prefix.setText(proton_prefix)
|
||||
self.changeable = True
|
||||
|
|
|
@ -2,6 +2,7 @@ from PyQt5.QtCore import QSettings
|
|||
from PyQt5.QtWidgets import QGroupBox
|
||||
|
||||
from rare.shared import GlobalSignalsSingleton
|
||||
from rare.models.options import options
|
||||
from rare.ui.components.tabs.settings.widgets.rpc import Ui_RPCSettings
|
||||
|
||||
|
||||
|
@ -13,22 +14,22 @@ class RPCSettings(QGroupBox, Ui_RPCSettings):
|
|||
|
||||
self.settings = QSettings()
|
||||
|
||||
self.enable.setCurrentIndex(self.settings.value("rpc_enable", 0, int))
|
||||
self.enable.currentIndexChanged.connect(self.changed)
|
||||
self.enable.setCurrentIndex(self.settings.value(*options.rpc_enable))
|
||||
self.enable.currentIndexChanged.connect(self.__enable_changed)
|
||||
|
||||
self.show_game.setChecked((self.settings.value("rpc_name", True, bool)))
|
||||
self.show_game.setChecked((self.settings.value(*options.rpc_name)))
|
||||
self.show_game.stateChanged.connect(
|
||||
lambda: self.settings.setValue("rpc_game", self.show_game.isChecked())
|
||||
lambda: self.settings.setValue(options.rpc_name.key, self.show_game.isChecked())
|
||||
)
|
||||
|
||||
self.show_os.setChecked((self.settings.value("rpc_os", True, bool)))
|
||||
self.show_os.setChecked((self.settings.value(*options.rpc_os)))
|
||||
self.show_os.stateChanged.connect(
|
||||
lambda: self.settings.setValue("rpc_os", self.show_os.isChecked())
|
||||
lambda: self.settings.setValue(options.rpc_os.key, self.show_os.isChecked())
|
||||
)
|
||||
|
||||
self.show_time.setChecked((self.settings.value("rpc_time", True, bool)))
|
||||
self.show_time.setChecked((self.settings.value(*options.rpc_time)))
|
||||
self.show_time.stateChanged.connect(
|
||||
lambda: self.settings.setValue("rpc_time", self.show_time.isChecked())
|
||||
lambda: self.settings.setValue(options.rpc_time.key, self.show_time.isChecked())
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -37,6 +38,6 @@ class RPCSettings(QGroupBox, Ui_RPCSettings):
|
|||
self.setDisabled(True)
|
||||
self.setToolTip(self.tr("Pypresence is not installed"))
|
||||
|
||||
def changed(self, i):
|
||||
self.settings.setValue("rpc_enable", i)
|
||||
def __enable_changed(self, i):
|
||||
self.settings.setValue(options.rpc_enable.key, i)
|
||||
self.signals.discord_rpc.apply_settings.emit()
|
||||
|
|
15
rare/components/tabs/settings/widgets/unix.py
Normal file
15
rare/components/tabs/settings/widgets/unix.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from components.tabs.settings import LinuxSettings
|
||||
|
||||
|
||||
class LinuxAppSettings(LinuxSettings):
|
||||
def __init__(self, parent=None):
|
||||
super(LinuxAppSettings, self).__init__(parent=parent)
|
||||
|
||||
def update_game(self, app_name):
|
||||
self.name = app_name
|
||||
self.wine_prefix.setText(self.load_prefix())
|
||||
self.wine_exec.setText(self.load_setting(self.name, "wine_executable"))
|
||||
|
||||
self.dxvk.load_settings(self.name)
|
||||
|
||||
self.mangohud.load_settings(self.name)
|
|
@ -3,31 +3,29 @@ import shutil
|
|||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QFileDialog, QWidget
|
||||
from PyQt5.QtWidgets import QFileDialog, QWidget, QFormLayout, QGroupBox
|
||||
|
||||
from rare.components.tabs.settings.widgets.dxvk import DxvkSettings
|
||||
from rare.components.tabs.settings.widgets.mangohud import MangoHudSettings
|
||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
||||
from rare.ui.components.tabs.settings.linux import Ui_LinuxSettings
|
||||
from rare.ui.components.tabs.settings.widgets.wine import Ui_WineSettings
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
from rare.utils import config_helper
|
||||
|
||||
logger = getLogger("LinuxSettings")
|
||||
|
||||
|
||||
class LinuxSettings(QWidget):
|
||||
class WineSettings(QGroupBox):
|
||||
# str: option key
|
||||
environ_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, name=None, parent=None):
|
||||
super(LinuxSettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_LinuxSettings()
|
||||
super(WineSettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_WineSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.signals = GlobalSignalsSingleton()
|
||||
|
||||
self.name = name if name is not None else "default"
|
||||
self.app_name: str = "default"
|
||||
|
||||
# Wine prefix
|
||||
self.wine_prefix = PathEdit(
|
||||
|
@ -36,49 +34,46 @@ class LinuxSettings(QWidget):
|
|||
edit_func=lambda path: (os.path.isdir(path) or not path, path, IndicatorReasonsCommon.DIR_NOT_EXISTS),
|
||||
save_func=self.save_prefix,
|
||||
)
|
||||
self.ui.prefix_layout.addWidget(self.wine_prefix)
|
||||
self.ui.main_layout.setWidget(
|
||||
self.ui.main_layout.getWidgetPosition(self.ui.prefix_label)[0],
|
||||
QFormLayout.FieldRole,
|
||||
self.wine_prefix
|
||||
)
|
||||
|
||||
# Wine executable
|
||||
self.wine_exec = PathEdit(
|
||||
self.load_setting(self.name, "wine_executable"),
|
||||
self.load_setting(self.app_name, "wine_executable"),
|
||||
file_mode=QFileDialog.ExistingFile,
|
||||
name_filters=["wine", "wine64"],
|
||||
edit_func=lambda text: (os.path.exists(text) or not text, text, IndicatorReasonsCommon.DIR_NOT_EXISTS),
|
||||
save_func=lambda text: self.save_setting(
|
||||
text, section=self.name, setting="wine_executable"
|
||||
text, section=self.app_name, setting="wine_executable"
|
||||
),
|
||||
)
|
||||
self.ui.exec_layout.addWidget(self.wine_exec)
|
||||
|
||||
# dxvk
|
||||
self.dxvk = DxvkSettings()
|
||||
self.dxvk.environ_changed.connect(self.environ_changed)
|
||||
self.ui.linux_layout.addWidget(self.dxvk)
|
||||
self.dxvk.load_settings(self.name)
|
||||
|
||||
self.mangohud = MangoHudSettings()
|
||||
self.mangohud.environ_changed.connect(self.environ_changed)
|
||||
self.ui.linux_layout.addWidget(self.mangohud)
|
||||
self.mangohud.load_settings(self.name)
|
||||
|
||||
self.ui.main_layout.setWidget(
|
||||
self.ui.main_layout.getWidgetPosition(self.ui.exec_label)[0],
|
||||
QFormLayout.FieldRole,
|
||||
self.wine_exec
|
||||
)
|
||||
|
||||
def load_prefix(self) -> str:
|
||||
return self.load_setting(
|
||||
f"{self.name}.env",
|
||||
f"{self.app_name}.env",
|
||||
"WINEPREFIX",
|
||||
fallback=self.load_setting(self.name, "wine_prefix"),
|
||||
fallback=self.load_setting(self.app_name, "wine_prefix"),
|
||||
)
|
||||
|
||||
def save_prefix(self, text: str):
|
||||
self.save_setting(text, f"{self.name}.env", "WINEPREFIX")
|
||||
self.save_setting(text, f"{self.app_name}.env", "WINEPREFIX")
|
||||
self.environ_changed.emit("WINEPREFIX")
|
||||
self.save_setting(text, self.name, "wine_prefix")
|
||||
self.save_setting(text, self.app_name, "wine_prefix")
|
||||
self.signals.application.prefix_updated.emit()
|
||||
|
||||
def load_setting(self, section: str, setting: str, fallback: str = ""):
|
||||
return self.core.lgd.config.get(section, setting, fallback=fallback)
|
||||
|
||||
def save_setting(self, text: str, section: str, setting: str):
|
||||
@staticmethod
|
||||
def save_setting(text: str, section: str, setting: str):
|
||||
if text:
|
||||
config_helper.add_option(section, setting, text)
|
||||
logger.debug(f"Set {setting} in {f'[{section}]'} to {text}")
|
|
@ -1,9 +1,9 @@
|
|||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
from logging import getLogger
|
||||
from typing import Dict, Optional
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QSettings, QSize, Qt, QMimeData, pyqtSlot, QCoreApplication
|
||||
from PyQt5.QtCore import pyqtSignal, QSize, Qt, QMimeData, pyqtSlot, QCoreApplication
|
||||
from PyQt5.QtGui import QDrag, QDropEvent, QDragEnterEvent, QDragMoveEvent, QFont, QMouseEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
|
@ -16,46 +16,41 @@ from PyQt5.QtWidgets import (
|
|||
QScrollArea,
|
||||
QAction,
|
||||
QToolButton,
|
||||
QMenu,
|
||||
QMenu, QDialog,
|
||||
)
|
||||
|
||||
from rare.models.wrapper import Wrapper
|
||||
from rare.shared import RareCore
|
||||
from rare.ui.components.tabs.settings.widgets.wrapper import Ui_WrapperSettings
|
||||
from rare.utils import config_helper
|
||||
from rare.utils.misc import icon
|
||||
from rare.utils.runners import proton
|
||||
|
||||
logger = getLogger("WrapperSettings")
|
||||
|
||||
extra_wrapper_regex = {
|
||||
"proton": "\".*proton\" run", # proton
|
||||
"mangohud": "mangohud" # mangohud
|
||||
}
|
||||
# extra_wrapper_regex = {
|
||||
# "proton": "\".*proton\" run", # proton
|
||||
# }
|
||||
|
||||
|
||||
class Wrapper:
|
||||
class WrapperDialog(QDialog):
|
||||
pass
|
||||
|
||||
|
||||
class WrapperWidget(QFrame):
|
||||
update_wrapper = pyqtSignal(str, str)
|
||||
delete_wrapper = pyqtSignal(str)
|
||||
# object: current, object: new
|
||||
update_wrapper = pyqtSignal(object, object)
|
||||
# object: current
|
||||
delete_wrapper = pyqtSignal(object)
|
||||
|
||||
def __init__(self, text: str, show_text=None, parent=None):
|
||||
def __init__(self, wrapper: Wrapper, parent=None):
|
||||
super(WrapperWidget, self).__init__(parent=parent)
|
||||
if not show_text:
|
||||
show_text = text.split()[0]
|
||||
|
||||
self.setFrameShape(QFrame.StyledPanel)
|
||||
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
self.setToolTip(wrapper.command)
|
||||
|
||||
self.text = text
|
||||
self.setToolTip(text)
|
||||
|
||||
unmanaged = show_text in extra_wrapper_regex.keys()
|
||||
|
||||
text_lbl = QLabel(show_text, parent=self)
|
||||
text_lbl = QLabel(wrapper.name, parent=self)
|
||||
text_lbl.setFont(QFont("monospace"))
|
||||
text_lbl.setDisabled(unmanaged)
|
||||
text_lbl.setEnabled(wrapper.is_editable)
|
||||
|
||||
image_lbl = QLabel(parent=self)
|
||||
image_lbl.setPixmap(icon("mdi.drag-vertical").pixmap(QSize(20, 20)))
|
||||
|
@ -72,8 +67,8 @@ class WrapperWidget(QFrame):
|
|||
manage_button.setIcon(icon("mdi.menu"))
|
||||
manage_button.setMenu(manage_menu)
|
||||
manage_button.setPopupMode(QToolButton.InstantPopup)
|
||||
manage_button.setDisabled(unmanaged)
|
||||
if unmanaged:
|
||||
manage_button.setEnabled(wrapper.is_editable)
|
||||
if not wrapper.is_editable:
|
||||
manage_button.setToolTip(self.tr("Manage through settings"))
|
||||
else:
|
||||
manage_button.setToolTip(self.tr("Manage"))
|
||||
|
@ -85,28 +80,39 @@ class WrapperWidget(QFrame):
|
|||
layout.addWidget(manage_button)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.wrapper = wrapper
|
||||
|
||||
# lk: set object names for the stylesheet
|
||||
self.setObjectName(type(self).__name__)
|
||||
manage_button.setObjectName(f"{self.objectName()}Button")
|
||||
|
||||
@pyqtSlot()
|
||||
def __delete(self):
|
||||
self.delete_wrapper.emit(self.text)
|
||||
def data(self) -> Wrapper:
|
||||
return self.wrapper
|
||||
|
||||
@pyqtSlot()
|
||||
def __delete(self) -> None:
|
||||
self.delete_wrapper.emit(self.wrapper)
|
||||
self.deleteLater()
|
||||
|
||||
@pyqtSlot()
|
||||
def __edit(self) -> None:
|
||||
dialog = QInputDialog(self)
|
||||
dialog.setWindowTitle(f"{self.tr('Edit wrapper')} - {QCoreApplication.instance().applicationName()}")
|
||||
dialog.setLabelText(self.tr("Edit wrapper command"))
|
||||
dialog.setTextValue(self.text)
|
||||
dialog.setTextValue(self.wrapper.command)
|
||||
accepted = dialog.exec()
|
||||
wrapper = dialog.textValue()
|
||||
command = dialog.textValue()
|
||||
dialog.deleteLater()
|
||||
if accepted and wrapper:
|
||||
self.update_wrapper.emit(self.text, wrapper)
|
||||
if accepted and command:
|
||||
new_wrapper = Wrapper(command=shlex.split(command))
|
||||
self.update_wrapper.emit(self.wrapper, new_wrapper)
|
||||
self.deleteLater()
|
||||
|
||||
def mouseMoveEvent(self, a0: QMouseEvent) -> None:
|
||||
if a0.buttons() == Qt.LeftButton:
|
||||
a0.accept()
|
||||
if self.wrapper.is_compat_tool:
|
||||
return
|
||||
drag = QDrag(self)
|
||||
mime = QMimeData()
|
||||
drag.setMimeData(mime)
|
||||
|
@ -114,30 +120,22 @@ class WrapperWidget(QFrame):
|
|||
|
||||
|
||||
class WrapperSettings(QWidget):
|
||||
def __init__(self):
|
||||
super(WrapperSettings, self).__init__()
|
||||
def __init__(self, parent=None):
|
||||
super(WrapperSettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_WrapperSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.wrappers: Dict[str, WrapperWidget] = {}
|
||||
self.app_name: str = "default"
|
||||
|
||||
self.wrapper_scroll = QScrollArea(self.ui.widget_stack)
|
||||
self.wrapper_scroll.setWidgetResizable(True)
|
||||
self.wrapper_scroll.setSizeAdjustPolicy(QScrollArea.AdjustToContents)
|
||||
self.wrapper_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.wrapper_scroll.setProperty("no_kinetic_scroll", True)
|
||||
self.scroll_content = WrapperContainer(
|
||||
save_cb=self.save, parent=self.wrapper_scroll
|
||||
)
|
||||
self.wrapper_scroll.setWidget(self.scroll_content)
|
||||
self.wrapper_container = WrapperContainer(parent=self.wrapper_scroll)
|
||||
self.wrapper_container.orderChanged.connect(self.__on_order_changed)
|
||||
self.wrapper_scroll.setWidget(self.wrapper_container)
|
||||
self.ui.widget_stack.insertWidget(0, self.wrapper_scroll)
|
||||
|
||||
self.core = RareCore.instance().core()
|
||||
|
||||
self.ui.add_button.clicked.connect(self.add_button_pressed)
|
||||
self.settings = QSettings()
|
||||
|
||||
self.ui.add_button.clicked.connect(self.__on_add_button_pressed)
|
||||
self.wrapper_scroll.horizontalScrollBar().rangeChanged.connect(self.adjust_scrollarea)
|
||||
|
||||
# lk: set object names for the stylesheet
|
||||
|
@ -149,18 +147,24 @@ class WrapperSettings(QWidget):
|
|||
self.wrapper_scroll.verticalScrollBar().setObjectName(
|
||||
f"{self.wrapper_scroll.objectName()}Bar")
|
||||
|
||||
self.ui.wrapper_settings_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.app_name: str = "default"
|
||||
self.core = RareCore.instance().core()
|
||||
self.wrappers = RareCore.instance().wrappers()
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def adjust_scrollarea(self, min: int, max: int):
|
||||
wrapper_widget = self.scroll_content.findChild(WrapperWidget)
|
||||
def adjust_scrollarea(self, minh: int, maxh: int):
|
||||
wrapper_widget = self.wrapper_container.findChild(WrapperWidget)
|
||||
if not wrapper_widget:
|
||||
return
|
||||
# lk: when the scrollbar is not visible, min and max are 0
|
||||
if max > min:
|
||||
if maxh > minh:
|
||||
self.wrapper_scroll.setMaximumHeight(
|
||||
wrapper_widget.sizeHint().height()
|
||||
+ self.wrapper_scroll.rect().height() // 2
|
||||
- self.wrapper_scroll.contentsRect().height() // 2
|
||||
+ self.scroll_content.layout().spacing()
|
||||
+ self.wrapper_container.layout().spacing()
|
||||
+ self.wrapper_scroll.horizontalScrollBar().sizeHint().height()
|
||||
)
|
||||
else:
|
||||
|
@ -170,187 +174,183 @@ class WrapperSettings(QWidget):
|
|||
- self.wrapper_scroll.contentsRect().height()
|
||||
)
|
||||
|
||||
def get_wrapper_string(self):
|
||||
return " ".join(self.get_wrapper_list())
|
||||
@pyqtSlot(QWidget, int)
|
||||
def __on_order_changed(self, widget: WrapperWidget, new_index: int):
|
||||
wrapper = widget.data()
|
||||
wrappers = self.wrappers.get_game_wrapper_list(self.app_name)
|
||||
wrappers.remove(wrapper)
|
||||
wrappers.insert(new_index, wrapper)
|
||||
self.wrappers.set_game_wrapper_list(self.app_name, wrappers)
|
||||
|
||||
def get_wrapper_list(self):
|
||||
wrappers = list(self.wrappers.values())
|
||||
wrappers.sort(key=lambda x: self.scroll_content.layout().indexOf(x))
|
||||
return [w.text for w in wrappers]
|
||||
|
||||
def add_button_pressed(self):
|
||||
@pyqtSlot()
|
||||
def __on_add_button_pressed(self):
|
||||
dialog = QInputDialog(self)
|
||||
dialog.setWindowTitle(f"{self.tr('Add wrapper')} - {QCoreApplication.instance().applicationName()}")
|
||||
dialog.setLabelText(self.tr("Enter wrapper command"))
|
||||
accepted = dialog.exec()
|
||||
wrapper = dialog.textValue()
|
||||
command = dialog.textValue()
|
||||
dialog.deleteLater()
|
||||
if accepted:
|
||||
self.add_wrapper(wrapper)
|
||||
|
||||
def add_wrapper(self, text: str, position: int = -1, from_load: bool = False):
|
||||
if text == "mangohud" and self.wrappers.get("mangohud"):
|
||||
return
|
||||
show_text = ""
|
||||
for key, extra_wrapper in extra_wrapper_regex.items():
|
||||
if re.match(extra_wrapper, text):
|
||||
show_text = key
|
||||
if not show_text:
|
||||
show_text = text.split()[0]
|
||||
|
||||
# validate
|
||||
if not text.strip(): # is empty
|
||||
return
|
||||
if not from_load:
|
||||
if self.wrappers.get(text):
|
||||
QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr("Wrapper <b>{0}</b> is already in the list").format(text)
|
||||
)
|
||||
return
|
||||
|
||||
if show_text != "proton" and not shutil.which(text.split()[0]):
|
||||
if (
|
||||
QMessageBox.question(
|
||||
self,
|
||||
self.tr("Warning"),
|
||||
self.tr("Wrapper <b>{0}</b> is not in $PATH. Add it anyway?").format(show_text),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No,
|
||||
)
|
||||
== QMessageBox.No
|
||||
):
|
||||
return
|
||||
|
||||
if text == "proton":
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
self.tr("Warning"),
|
||||
self.tr("Do not insert <b>proton</b> manually. Add it through Proton settings"),
|
||||
)
|
||||
return
|
||||
wrapper = Wrapper(shlex.split(command))
|
||||
self.add_user_wrapper(wrapper)
|
||||
|
||||
def __add_wrapper(self, wrapper: Wrapper, position: int = -1):
|
||||
self.ui.widget_stack.setCurrentIndex(0)
|
||||
|
||||
if widget := self.wrappers.get(show_text, None):
|
||||
widget.deleteLater()
|
||||
|
||||
widget = WrapperWidget(text, show_text, self.scroll_content)
|
||||
widget = WrapperWidget(wrapper, self.wrapper_container)
|
||||
if position < 0:
|
||||
self.scroll_content.layout().addWidget(widget)
|
||||
self.wrapper_container.addWidget(widget)
|
||||
else:
|
||||
self.scroll_content.layout().insertWidget(position, widget)
|
||||
self.wrapper_container.insertWidget(position, widget)
|
||||
self.adjust_scrollarea(
|
||||
self.wrapper_scroll.horizontalScrollBar().minimum(),
|
||||
self.wrapper_scroll.horizontalScrollBar().maximum(),
|
||||
)
|
||||
widget.update_wrapper.connect(self.update_wrapper)
|
||||
widget.delete_wrapper.connect(self.delete_wrapper)
|
||||
widget.update_wrapper.connect(self.__update_wrapper)
|
||||
widget.delete_wrapper.connect(self.__delete_wrapper)
|
||||
|
||||
self.wrappers[show_text] = widget
|
||||
def add_wrapper(self, wrapper: Wrapper, position: int = -1):
|
||||
wrappers = self.wrappers.get_game_wrapper_list(self.app_name)
|
||||
if position < 0 or wrapper.is_compat_tool:
|
||||
wrappers.append(wrapper)
|
||||
else:
|
||||
wrappers.insert(position, wrapper)
|
||||
self.wrappers.set_game_wrapper_list(self.app_name, wrappers)
|
||||
self.__add_wrapper(wrapper, position)
|
||||
|
||||
if not from_load:
|
||||
self.save()
|
||||
def add_user_wrapper(self, wrapper: Wrapper, position: int = -1):
|
||||
if not wrapper:
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def delete_wrapper(self, text: str):
|
||||
text = text.split()[0]
|
||||
widget = self.wrappers.get(text, None)
|
||||
if widget:
|
||||
self.wrappers.pop(text)
|
||||
widget.deleteLater()
|
||||
compat_cmds = [tool.command() for tool in proton.find_tools()]
|
||||
if wrapper.command in compat_cmds:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
self.tr("Warning"),
|
||||
self.tr("Do not insert compatibility tools manually. Add them through Proton settings"),
|
||||
)
|
||||
return
|
||||
|
||||
if not self.wrappers:
|
||||
# if text == "mangohud" and self.wrappers.get("mangohud"):
|
||||
# return
|
||||
# show_text = ""
|
||||
# for key, extra_wrapper in extra_wrapper_regex.items():
|
||||
# if re.match(extra_wrapper, text):
|
||||
# show_text = key
|
||||
# if not show_text:
|
||||
# show_text = text.split()[0]
|
||||
|
||||
if wrapper.checksum in self.wrappers.get_game_md5sum_list(self.app_name):
|
||||
QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr("Wrapper <b>{0}</b> is already in the list").format(wrapper.command)
|
||||
)
|
||||
return
|
||||
|
||||
if not shutil.which(wrapper.executable):
|
||||
ans = QMessageBox.question(
|
||||
self,
|
||||
self.tr("Warning"),
|
||||
self.tr("Wrapper <b>{0}</b> is not in $PATH. Add it anyway?").format(wrapper.executable),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
if ans == QMessageBox.No:
|
||||
return
|
||||
|
||||
self.add_wrapper(wrapper, position)
|
||||
|
||||
@pyqtSlot(object)
|
||||
def __delete_wrapper(self, wrapper: Wrapper):
|
||||
wrappers = self.wrappers.get_game_wrapper_list(self.app_name)
|
||||
wrappers.remove(wrapper)
|
||||
self.wrappers.set_game_wrapper_list(self.app_name, wrappers)
|
||||
if not wrappers:
|
||||
self.wrapper_scroll.setMaximumHeight(self.ui.label_page.sizeHint().height())
|
||||
self.ui.widget_stack.setCurrentIndex(1)
|
||||
|
||||
self.save()
|
||||
@pyqtSlot(object, object)
|
||||
def __update_wrapper(self, old: Wrapper, new: Wrapper):
|
||||
wrappers = self.wrappers.get_game_wrapper_list(self.app_name)
|
||||
index = wrappers.index(old)
|
||||
wrappers.remove(old)
|
||||
wrappers.insert(index, new)
|
||||
self.wrappers.set_game_wrapper_list(self.app_name, wrappers)
|
||||
self.__add_wrapper(new, index)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def update_wrapper(self, old: str, new: str):
|
||||
key = old.split()[0]
|
||||
idx = self.scroll_content.layout().indexOf(self.wrappers[key])
|
||||
self.delete_wrapper(key)
|
||||
self.add_wrapper(new, position=idx)
|
||||
|
||||
def save(self):
|
||||
# save wrappers twice, to support wrappers with spaces
|
||||
if len(self.wrappers) == 0:
|
||||
config_helper.remove_option(self.app_name, "wrapper")
|
||||
self.settings.remove(f"{self.app_name}/wrapper")
|
||||
else:
|
||||
config_helper.add_option(self.app_name, "wrapper", self.get_wrapper_string())
|
||||
self.settings.setValue(f"{self.app_name}/wrapper", self.get_wrapper_list())
|
||||
|
||||
def load_settings(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
for i in self.wrappers.values():
|
||||
i.deleteLater()
|
||||
self.wrappers.clear()
|
||||
|
||||
wrappers = self.settings.value(f"{self.app_name}/wrapper", [], str)
|
||||
|
||||
if not wrappers and (cfg := self.core.lgd.config.get(self.app_name, "wrapper", fallback="")):
|
||||
logger.info("Loading wrappers from legendary config")
|
||||
# no qt wrapper, but legendary wrapper, to have backward compatibility
|
||||
pattern = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''')
|
||||
wrappers = pattern.split(cfg)[1::2]
|
||||
|
||||
for wrapper in wrappers:
|
||||
self.add_wrapper(wrapper, from_load=True)
|
||||
|
||||
if not self.wrappers:
|
||||
@pyqtSlot()
|
||||
def update_state(self):
|
||||
for w in self.wrapper_container.findChildren(WrapperWidget, options=Qt.FindDirectChildrenOnly):
|
||||
w.deleteLater()
|
||||
wrappers = self.wrappers.get_game_wrapper_list(self.app_name)
|
||||
if not wrappers:
|
||||
self.wrapper_scroll.setMaximumHeight(self.ui.label_page.sizeHint().height())
|
||||
self.ui.widget_stack.setCurrentIndex(1)
|
||||
else:
|
||||
self.ui.widget_stack.setCurrentIndex(0)
|
||||
for wrapper in wrappers:
|
||||
self.__add_wrapper(wrapper)
|
||||
|
||||
self.save()
|
||||
def load_settings(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
self.update_state()
|
||||
|
||||
|
||||
class WrapperContainer(QWidget):
|
||||
# QWidget: moving widget, int: new index
|
||||
orderChanged: pyqtSignal = pyqtSignal(QWidget, int)
|
||||
|
||||
def __init__(self, save_cb, parent=None):
|
||||
def __init__(self, parent=None):
|
||||
super(WrapperContainer, self).__init__(parent=parent)
|
||||
self.setAcceptDrops(True)
|
||||
self.save = save_cb
|
||||
layout = QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
self.setLayout(layout)
|
||||
self.__layout = QHBoxLayout(self)
|
||||
self.__layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.__layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
|
||||
self.drag_widget: Optional[QWidget] = None
|
||||
self.__drag_widget: Optional[QWidget] = None
|
||||
|
||||
# lk: set object names for the stylesheet
|
||||
self.setObjectName(type(self).__name__)
|
||||
|
||||
# def count(self) -> int:
|
||||
# return self.__layout.count()
|
||||
#
|
||||
# def itemData(self, index: int) -> Any:
|
||||
# widget: WrapperWidget = self.__layout.itemAt(index).widget()
|
||||
# return widget.data()
|
||||
|
||||
def addWidget(self, widget: WrapperWidget):
|
||||
self.__layout.addWidget(widget)
|
||||
|
||||
def insertWidget(self, index: int, widget: WrapperWidget):
|
||||
self.__layout.insertWidget(index, widget)
|
||||
|
||||
def dragEnterEvent(self, e: QDragEnterEvent):
|
||||
widget = e.source()
|
||||
self.drag_widget = widget
|
||||
self.__drag_widget = widget
|
||||
e.accept()
|
||||
|
||||
def _get_drop_index(self, x):
|
||||
drag_idx = self.layout().indexOf(self.drag_widget)
|
||||
def __get_drop_index(self, x) -> int:
|
||||
drag_idx = self.__layout.indexOf(self.__drag_widget)
|
||||
|
||||
if drag_idx > 0:
|
||||
prev_widget = self.layout().itemAt(drag_idx - 1).widget()
|
||||
if x < self.drag_widget.x() - prev_widget.width() // 2:
|
||||
prev_widget = self.__layout.itemAt(drag_idx - 1).widget()
|
||||
if x < self.__drag_widget.x() - prev_widget.width() // 2:
|
||||
return drag_idx - 1
|
||||
if drag_idx < self.layout().count() - 1:
|
||||
next_widget = self.layout().itemAt(drag_idx + 1).widget()
|
||||
if x > self.drag_widget.x() + self.drag_widget.width() + next_widget.width() // 2:
|
||||
if drag_idx < self.__layout.count() - 1:
|
||||
next_widget = self.__layout.itemAt(drag_idx + 1).widget()
|
||||
if x > self.__drag_widget.x() + self.__drag_widget.width() + next_widget.width() // 2:
|
||||
return drag_idx + 1
|
||||
|
||||
return drag_idx
|
||||
|
||||
def dragMoveEvent(self, e: QDragMoveEvent) -> None:
|
||||
i = self._get_drop_index(e.pos().x())
|
||||
self.layout().insertWidget(i, self.drag_widget)
|
||||
new_x = self.__get_drop_index(e.pos().x())
|
||||
self.__layout.insertWidget(new_x, self.__drag_widget)
|
||||
|
||||
def dropEvent(self, e: QDropEvent):
|
||||
pos = e.pos()
|
||||
widget = e.source()
|
||||
index = self._get_drop_index(pos.x())
|
||||
self.layout().insertWidget(index, widget)
|
||||
self.drag_widget = None
|
||||
new_x = self.__get_drop_index(pos.x())
|
||||
self.__layout.insertWidget(new_x, widget)
|
||||
self.__drag_widget = None
|
||||
self.orderChanged.emit(widget, new_x)
|
||||
e.accept()
|
||||
self.save()
|
||||
|
|
|
@ -5,6 +5,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSettings
|
|||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction, QApplication
|
||||
|
||||
from rare.models.options import options
|
||||
from rare.shared import RareCore
|
||||
|
||||
logger = getLogger("TrayIcon")
|
||||
|
@ -61,7 +62,7 @@ class TrayIcon(QSystemTrayIcon):
|
|||
|
||||
@pyqtSlot(str, str)
|
||||
def notify(self, title: str, body: str):
|
||||
if self.settings.value("notification", True, bool):
|
||||
if self.settings.value(*options.notification):
|
||||
self.showMessage(f"{QApplication.applicationName()} - {title}", body, QSystemTrayIcon.Information, 4000)
|
||||
|
||||
@pyqtSlot()
|
||||
|
|
|
@ -17,6 +17,7 @@ from legendary.models.game import SaveGameStatus
|
|||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.base_game import RareGameSlim
|
||||
from rare.models.launcher import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
|
||||
from rare.models.options import options
|
||||
from rare.widgets.rare_app import RareApp, RareAppException
|
||||
from .cloud_sync_dialog import CloudSyncDialog, CloudSyncDialogResult
|
||||
from .console_dialog import ConsoleDialog
|
||||
|
@ -145,7 +146,7 @@ class RareLauncher(RareApp):
|
|||
lang = self.settings.value("language", self.core.language_code, type=str)
|
||||
self.load_translator(lang)
|
||||
|
||||
if QSettings().value("show_console", False, bool):
|
||||
if QSettings(self).value(*options.log_games):
|
||||
self.console = ConsoleDialog()
|
||||
self.console.show()
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ def main() -> int:
|
|||
sys.stderr = open(os.devnull, 'w')
|
||||
|
||||
os.environ["QT_QPA_PLATFORMTHEME"] = ""
|
||||
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"
|
||||
os.environ["QT_SCALE_FACTOR_ROUNDING_POLICY"] = "Floor"
|
||||
if "LEGENDARY_CONFIG_PATH" in os.environ:
|
||||
os.environ["LEGENDARY_CONFIG_PATH"] = os.path.expanduser(os.environ["LEGENDARY_CONFIG_PATH"])
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ from legendary.lfs import eos
|
|||
from legendary.models.game import SaveGameFile, SaveGameStatus, Game, InstalledGame
|
||||
from legendary.utils.selective_dl import get_sdl_appname
|
||||
|
||||
from rare.models.options import options
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.install import UninstallOptionsModel, InstallOptionsModel
|
||||
|
||||
|
@ -222,11 +223,13 @@ class RareGameSlim(RareGameBase):
|
|||
|
||||
@property
|
||||
def auto_sync_saves(self):
|
||||
return self.supports_cloud_saves and QSettings().value(
|
||||
f"{self.app_name}/auto_sync_cloud",
|
||||
QSettings().value("auto_sync_cloud", False, bool),
|
||||
bool
|
||||
auto_sync_cloud = QSettings(self).value(
|
||||
f"{self.app_name}/{options.auto_sync_cloud.key}",
|
||||
options.auto_sync_cloud.default,
|
||||
options.auto_sync_cloud.dtype
|
||||
)
|
||||
auto_sync_cloud = auto_sync_cloud or QSettings(self).value(*options.auto_sync_cloud)
|
||||
return self.supports_cloud_saves and auto_sync_cloud
|
||||
|
||||
@property
|
||||
def save_path(self) -> Optional[str]:
|
||||
|
|
|
@ -2,7 +2,7 @@ import json
|
|||
import os
|
||||
import platform
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from datetime import datetime, UTC
|
||||
from logging import getLogger
|
||||
from threading import Lock
|
||||
from typing import List, Optional, Dict, Set
|
||||
|
@ -19,7 +19,7 @@ from rare.shared.game_process import GameProcess
|
|||
from rare.shared.image_manager import ImageManager
|
||||
from rare.utils.paths import data_dir, get_rare_executable
|
||||
from rare.utils.steam_grades import get_rating
|
||||
from rare.utils.config_helper import add_envvar, remove_envvar
|
||||
from rare.utils.config_helper import add_envvar
|
||||
|
||||
logger = getLogger("RareGame")
|
||||
|
||||
|
@ -27,11 +27,10 @@ logger = getLogger("RareGame")
|
|||
class RareGame(RareGameSlim):
|
||||
@dataclass
|
||||
class Metadata:
|
||||
auto_update: bool = False
|
||||
queued: bool = False
|
||||
queue_pos: Optional[int] = None
|
||||
last_played: datetime = datetime.min
|
||||
grant_date: Optional[datetime] = None
|
||||
grant_date: datetime = datetime.min
|
||||
steam_appid: Optional[int] = None
|
||||
steam_grade: Optional[str] = None
|
||||
steam_date: datetime = datetime.min
|
||||
|
@ -40,24 +39,23 @@ class RareGame(RareGameSlim):
|
|||
@classmethod
|
||||
def from_dict(cls, data: Dict):
|
||||
return cls(
|
||||
auto_update=data.get("auto_update", False),
|
||||
queued=data.get("queued", False),
|
||||
queue_pos=data.get("queue_pos", None),
|
||||
last_played=datetime.fromisoformat(data["last_played"]) if data.get("last_played", None) else datetime.min,
|
||||
grant_date=datetime.fromisoformat(data["grant_date"]) if data.get("grant_date", None) else None,
|
||||
last_played=datetime.fromisoformat(x) if (x := data.get("last_played", None)) else datetime.min,
|
||||
grant_date=datetime.fromisoformat(x) if (x := data.get("grant_date", None)) else datetime.min,
|
||||
steam_appid=data.get("steam_appid", None),
|
||||
steam_grade=data.get("steam_grade", None),
|
||||
steam_date=datetime.fromisoformat(data["steam_date"]) if data.get("steam_date", None) else datetime.min,
|
||||
steam_date=datetime.fromisoformat(x) if (x := data.get("steam_date", None)) else datetime.min,
|
||||
tags=data.get("tags", []),
|
||||
)
|
||||
|
||||
def as_dict(self):
|
||||
@property
|
||||
def __dict__(self):
|
||||
return dict(
|
||||
auto_update=self.auto_update,
|
||||
queued=self.queued,
|
||||
queue_pos=self.queue_pos,
|
||||
last_played=self.last_played.isoformat() if self.last_played else datetime.min,
|
||||
grant_date=self.grant_date.isoformat() if self.grant_date else None,
|
||||
grant_date=self.grant_date.isoformat() if self.grant_date else datetime.min,
|
||||
steam_appid=self.steam_appid,
|
||||
steam_grade=self.steam_grade,
|
||||
steam_date=self.steam_date.isoformat() if self.steam_date else datetime.min,
|
||||
|
@ -81,7 +79,6 @@ class RareGame(RareGameSlim):
|
|||
self.pixmap: QPixmap = QPixmap()
|
||||
self.metadata: RareGame.Metadata = RareGame.Metadata()
|
||||
self.__load_metadata()
|
||||
if self.metadata.grant_date is None:
|
||||
self.grant_date()
|
||||
|
||||
self.owned_dlcs: Set[RareGame] = set()
|
||||
|
@ -143,13 +140,14 @@ class RareGame(RareGameSlim):
|
|||
def __load_metadata_json() -> Dict:
|
||||
if RareGame.__metadata_json is None:
|
||||
metadata = {}
|
||||
file = os.path.join(data_dir(), "game_meta.json")
|
||||
try:
|
||||
with open(os.path.join(data_dir(), "game_meta.json"), "r") as metadata_fh:
|
||||
metadata = json.load(metadata_fh)
|
||||
with open(file, "r") as f:
|
||||
metadata = json.load(f)
|
||||
except FileNotFoundError:
|
||||
logger.info("Game metadata json file does not exist.")
|
||||
logger.info("%s does not exist", file)
|
||||
except json.JSONDecodeError:
|
||||
logger.warning("Game metadata json file is corrupt.")
|
||||
logger.warning("%s is corrupt", file)
|
||||
finally:
|
||||
RareGame.__metadata_json = metadata
|
||||
return RareGame.__metadata_json
|
||||
|
@ -166,9 +164,9 @@ class RareGame(RareGameSlim):
|
|||
with RareGame.__metadata_lock:
|
||||
metadata: Dict = self.__load_metadata_json()
|
||||
# pylint: disable=unsupported-assignment-operation
|
||||
metadata[self.app_name] = self.metadata.as_dict()
|
||||
with open(os.path.join(data_dir(), "game_meta.json"), "w") as metadata_json:
|
||||
json.dump(metadata, metadata_json, indent=2)
|
||||
metadata[self.app_name] = vars(self.metadata)
|
||||
with open(os.path.join(data_dir(), "game_meta.json"), "w+") as file:
|
||||
json.dump(metadata, file, indent=2)
|
||||
|
||||
def update_game(self):
|
||||
self.game = self.core.get_game(
|
||||
|
@ -432,29 +430,27 @@ class RareGame(RareGameSlim):
|
|||
def steam_grade(self) -> str:
|
||||
if platform.system() == "Windows" or self.is_unreal:
|
||||
return "na"
|
||||
if self.metadata.steam_grade != "pending":
|
||||
elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date)
|
||||
if (
|
||||
self.metadata.steam_grade is not None
|
||||
and self.metadata.steam_appid is not None
|
||||
and elapsed_time.days < 3
|
||||
):
|
||||
return self.metadata.steam_grade
|
||||
|
||||
if elapsed_time.days > 3 and (self.metadata.steam_grade is None or self.metadata.steam_appid is None):
|
||||
def _set_steam_grade():
|
||||
appid, rating = get_rating(self.core, self.app_name)
|
||||
self.set_steam_grade(appid, rating)
|
||||
|
||||
worker = QRunnable.create(_set_steam_grade)
|
||||
QThreadPool.globalInstance().start(worker)
|
||||
return "pending"
|
||||
self.metadata.steam_grade = "pending"
|
||||
return self.metadata.steam_grade
|
||||
|
||||
@property
|
||||
def steam_appid(self) -> Optional[int]:
|
||||
return self.metadata.steam_appid
|
||||
|
||||
def set_steam_grade(self, appid: int, grade: str) -> None:
|
||||
if appid or self.steam_appid is None:
|
||||
if appid and self.steam_appid is None:
|
||||
add_envvar(self.app_name, "SteamAppId", str(appid))
|
||||
add_envvar(self.app_name, "SteamGameId", str(appid))
|
||||
add_envvar(self.app_name, "STEAM_COMPAT_APP_ID", str(appid))
|
||||
self.metadata.steam_appid = appid
|
||||
self.metadata.steam_grade = grade
|
||||
self.metadata.steam_date = datetime.utcnow()
|
||||
|
@ -463,17 +459,17 @@ class RareGame(RareGameSlim):
|
|||
|
||||
def grant_date(self, force=False) -> datetime:
|
||||
if (entitlements := self.core.lgd.entitlements) is None:
|
||||
return self.metadata.grant_date
|
||||
if self.metadata.grant_date is None or force:
|
||||
return self.metadata.grant_date.replace(tzinfo=UTC)
|
||||
if self.metadata.grant_date == datetime.min.replace(tzinfo=UTC) or force:
|
||||
logger.debug("Grant date for %s not found in metadata, resolving", self.app_name)
|
||||
matching = filter(lambda ent: ent["namespace"] == self.game.namespace, entitlements)
|
||||
entitlement = next(matching, None)
|
||||
grant_date = datetime.fromisoformat(
|
||||
entitlement["grantDate"].replace("Z", "+00:00")
|
||||
) if entitlement else None
|
||||
) if entitlement else datetime.min.replace(tzinfo=UTC)
|
||||
self.metadata.grant_date = grant_date
|
||||
self.__save_metadata()
|
||||
return self.metadata.grant_date
|
||||
return self.metadata.grant_date.replace(tzinfo=UTC)
|
||||
|
||||
def set_origin_attributes(self, path: str, size: int = 0) -> None:
|
||||
self.__origin_install_path = path
|
||||
|
|
19
rare/models/library.py
Normal file
19
rare/models/library.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from enum import IntEnum
|
||||
|
||||
|
||||
class LibraryFilter(IntEnum):
|
||||
ALL = 1
|
||||
INSTALLED = 2
|
||||
OFFLINE = 3
|
||||
HIDDEN = 4
|
||||
WIN32 = 5
|
||||
MAC = 6
|
||||
INSTALLABLE = 7
|
||||
INCLUDE_UE = 8
|
||||
|
||||
|
||||
class LibraryOrder(IntEnum):
|
||||
TITLE = 1
|
||||
RECENT = 2
|
||||
NEWEST = 3
|
||||
OLDEST = 4
|
53
rare/models/options.py
Normal file
53
rare/models/options.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
import platform as pf
|
||||
from argparse import Namespace
|
||||
from typing import Any, Type
|
||||
from .library import LibraryFilter, LibraryOrder
|
||||
|
||||
|
||||
class Value(Namespace):
|
||||
key: str
|
||||
default: Any
|
||||
dtype: Type
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__dict__)
|
||||
|
||||
def __iter__(self):
|
||||
for v in self.__dict__.values():
|
||||
yield v
|
||||
|
||||
|
||||
# They key names are set to the existing option name
|
||||
class Defaults(Namespace):
|
||||
win32_meta = Value(key="win32_meta", default=False, dtype=bool)
|
||||
macos_meta = Value(key="macos_meta", default=pf.system() == "Darwin", dtype=bool)
|
||||
unreal_meta = Value(key="unreal_meta", default=False, dtype=bool)
|
||||
exclude_non_asset = Value(key="exclude_non_asset", default=False, dtype=bool)
|
||||
exclude_entitlements = Value(key="exclude_entitlements", default=False, dtype=bool)
|
||||
|
||||
sys_tray = Value(key="sys_tray", default=True, dtype=bool)
|
||||
auto_update = Value(key="auto_update", default=False, dtype=bool)
|
||||
auto_sync_cloud = Value(key="auto_sync_cloud", default=False, dtype=bool)
|
||||
confirm_start = Value(key="confirm_start", default=False, dtype=bool)
|
||||
save_size = Value(key="save_size", default=False, dtype=bool)
|
||||
window_size = Value(key="window_size", default=(1280, 720), dtype=tuple)
|
||||
notification = Value(key="notification", default=True, dtype=bool)
|
||||
log_games = Value(key="show_console", default=False, dtype=bool)
|
||||
|
||||
library_filter = Value(
|
||||
key="library_filter",
|
||||
default=int(LibraryFilter.MAC if pf.system() == "Darwin" else LibraryFilter.ALL), dtype=int
|
||||
)
|
||||
library_order = Value(
|
||||
key="library_order", default=int(LibraryOrder.TITLE), dtype=int
|
||||
)
|
||||
|
||||
rpc_enable = Value(key="rpc_enable", default=0, dtype=int)
|
||||
rpc_name = Value(key="rpc_game", default=True, dtype=bool)
|
||||
rpc_time = Value(key="rpc_time", default=True, dtype=bool)
|
||||
rpc_os = Value(key="rpc_os", default=True, dtype=bool)
|
||||
|
||||
|
||||
options = Defaults()
|
||||
|
||||
__all__ = ['options']
|
|
@ -2,46 +2,72 @@ import os
|
|||
from typing import Union, List
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.game import InstalledGame
|
||||
|
||||
from rare.utils.config_helper import get_prefixes
|
||||
|
||||
|
||||
class PathSpec:
|
||||
__egl_path_vars = {
|
||||
"{appdata}": os.path.expandvars("%LOCALAPPDATA%"),
|
||||
"{userdir}": os.path.expandvars("%USERPROFILE%/Documents"),
|
||||
# '{userprofile}': os.path.expandvars('%userprofile%'), # possibly wrong
|
||||
"{usersavedgames}": os.path.expandvars("%USERPROFILE%/Saved Games"),
|
||||
}
|
||||
egl_appdata: str = r"%LOCALAPPDATA%\EpicGamesLauncher\Saved\Config\Windows"
|
||||
egl_programdata: str = r"%PROGRAMDATA%\Epic\EpicGamesLauncher\Data\Manifests"
|
||||
wine_programdata: str = r"dosdevices/c:/ProgramData"
|
||||
|
||||
def __init__(self, core: LegendaryCore = None, app_name: str = "default"):
|
||||
if core is not None:
|
||||
self.__egl_path_vars.update({"{epicid}": core.lgd.userdata["account_id"]})
|
||||
self.app_name = app_name
|
||||
@staticmethod
|
||||
def egl_appdata() -> str:
|
||||
return r"%LOCALAPPDATA%\EpicGamesLauncher\Saved\Config\Windows"
|
||||
|
||||
def cook(self, path: str) -> str:
|
||||
cooked_path = [self.__egl_path_vars.get(p.lower(), p) for p in path.split("/")]
|
||||
return os.path.join(*cooked_path)
|
||||
@staticmethod
|
||||
def egl_programdata() -> str:
|
||||
return r"%PROGRAMDATA%\Epic\EpicGamesLauncher\Data\Manifests"
|
||||
|
||||
@property
|
||||
def wine_egl_programdata(self):
|
||||
return self.egl_programdata.replace("\\", "/").replace("%PROGRAMDATA%", self.wine_programdata)
|
||||
@staticmethod
|
||||
def wine_programdata() -> str:
|
||||
return r"ProgramData"
|
||||
|
||||
def wine_egl_prefixes(self, results: int = 0) -> Union[List[str], str]:
|
||||
possible_prefixes = [
|
||||
os.path.expanduser("~/.wine"),
|
||||
os.path.expanduser("~/Games/epic-games-store"),
|
||||
]
|
||||
@staticmethod
|
||||
def wine_egl_programdata() -> str:
|
||||
return PathSpec.egl_programdata(
|
||||
).replace(
|
||||
"\\", "/"
|
||||
).replace(
|
||||
"%PROGRAMDATA%", PathSpec.wine_programdata()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def prefix_egl_programdata(prefix: str) -> str:
|
||||
return os.path.join(prefix, "dosdevices/c:", PathSpec.wine_egl_programdata())
|
||||
|
||||
@staticmethod
|
||||
def wine_egl_prefixes(results: int = 0) -> Union[List[str], str]:
|
||||
possible_prefixes = get_prefixes()
|
||||
prefixes = []
|
||||
for prefix in possible_prefixes:
|
||||
if os.path.exists(os.path.join(prefix, self.wine_egl_programdata)):
|
||||
if os.path.exists(os.path.join(prefix, PathSpec.wine_egl_programdata())):
|
||||
prefixes.append(prefix)
|
||||
if not prefixes:
|
||||
return str()
|
||||
return ""
|
||||
if not results:
|
||||
return prefixes
|
||||
elif results == 1:
|
||||
return prefixes[0]
|
||||
else:
|
||||
return prefixes[:results]
|
||||
|
||||
def __init__(self, core: LegendaryCore = None, igame: InstalledGame = None):
|
||||
self.__egl_path_vars = {
|
||||
"{appdata}": os.path.expandvars("%LOCALAPPDATA%"),
|
||||
"{userdir}": os.path.expandvars("%USERPROFILE%/Documents"),
|
||||
"{userprofile}": os.path.expandvars("%userprofile%"), # possibly wrong
|
||||
"{usersavedgames}": os.path.expandvars("%USERPROFILE%/Saved Games"),
|
||||
}
|
||||
|
||||
if core is not None:
|
||||
self.__egl_path_vars.update({
|
||||
"{epicid}": core.lgd.userdata["account_id"]
|
||||
})
|
||||
|
||||
if igame is not None:
|
||||
self.__egl_path_vars.update({
|
||||
"{installdir}": igame.install_path,
|
||||
})
|
||||
|
||||
def resolve_egl_path_vars(self, path: str) -> str:
|
||||
cooked_path = [self.__egl_path_vars.get(p.lower(), p) for p in path.split("/")]
|
||||
return os.path.join(*cooked_path)
|
||||
|
|
74
rare/models/wrapper.py
Normal file
74
rare/models/wrapper.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
import os
|
||||
import shlex
|
||||
from hashlib import md5
|
||||
from enum import IntEnum
|
||||
from typing import Dict, List, Union
|
||||
|
||||
|
||||
class WrapperType(IntEnum):
|
||||
NONE = 0
|
||||
COMPAT_TOOL = 1
|
||||
LEGENDARY_IMPORT = 8
|
||||
USER_DEFINED = 9
|
||||
|
||||
|
||||
class Wrapper:
|
||||
def __init__(self, command: Union[str, List[str]], name: str = None, wtype: WrapperType = None):
|
||||
self.__command: List[str] = shlex.split(command) if isinstance(command, str) else command
|
||||
self.__name: str = name if name is not None else os.path.basename(self.__command[0])
|
||||
self.__wtype: WrapperType = wtype if wtype is not None else WrapperType.USER_DEFINED
|
||||
|
||||
@property
|
||||
def is_compat_tool(self) -> bool:
|
||||
return self.__wtype == WrapperType.COMPAT_TOOL
|
||||
|
||||
@property
|
||||
def is_editable(self) -> bool:
|
||||
return self.__wtype == WrapperType.USER_DEFINED or self.__wtype == WrapperType.LEGENDARY_IMPORT
|
||||
|
||||
@property
|
||||
def checksum(self) -> str:
|
||||
return md5(self.command.encode("utf-8")).hexdigest()
|
||||
|
||||
@property
|
||||
def executable(self) -> str:
|
||||
return shlex.quote(self.__command[0])
|
||||
|
||||
@property
|
||||
def command(self) -> str:
|
||||
return " ".join(shlex.quote(part) for part in self.__command)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.__name
|
||||
|
||||
@property
|
||||
def type(self) -> WrapperType:
|
||||
return self.__wtype
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return self.command == other.command
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__command)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
if not self.is_editable:
|
||||
return True
|
||||
return bool(self.command.strip())
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict):
|
||||
return cls(
|
||||
command=data.get("command"),
|
||||
name=data.get("name"),
|
||||
wtype=WrapperType(data.get("wtype", WrapperType.USER_DEFINED))
|
||||
)
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
return dict(
|
||||
command=self.__command,
|
||||
name=self.__name,
|
||||
wtype=int(self.__wtype)
|
||||
)
|
|
@ -29,7 +29,7 @@ from .workers import (
|
|||
)
|
||||
from .workers.uninstall import uninstall_game
|
||||
from .workers.worker import QueueWorkerInfo, QueueWorkerState
|
||||
from rare.utils import config_helper
|
||||
from .wrappers import Wrappers
|
||||
|
||||
logger = getLogger("RareCore")
|
||||
|
||||
|
@ -53,6 +53,8 @@ class RareCore(QObject):
|
|||
self.__signals: Optional[GlobalSignals] = None
|
||||
self.__core: Optional[LegendaryCore] = None
|
||||
self.__image_manager: Optional[ImageManager] = None
|
||||
self.__settings: Optional[QSettings] = None
|
||||
self.__wrappers: Optional[Wrappers] = None
|
||||
|
||||
self.__start_time = time.perf_counter()
|
||||
|
||||
|
@ -61,8 +63,8 @@ class RareCore(QObject):
|
|||
self.core(init=True)
|
||||
config_helper.init_config_handler(self.__core)
|
||||
self.image_manager(init=True)
|
||||
|
||||
self.settings = QSettings(self)
|
||||
self.__settings = QSettings(self)
|
||||
self.__wrappers = Wrappers()
|
||||
|
||||
self.queue_workers: List[QueueWorker] = []
|
||||
self.queue_threadpool = QThreadPool()
|
||||
|
@ -205,6 +207,12 @@ class RareCore(QObject):
|
|||
self.__image_manager = ImageManager(self.signals(), self.core())
|
||||
return self.__image_manager
|
||||
|
||||
def wrappers(self) -> Wrappers:
|
||||
return self.__wrappers
|
||||
|
||||
def settings(self) -> QSettings:
|
||||
return self.__settings
|
||||
|
||||
def deleteLater(self) -> None:
|
||||
self.__image_manager.deleteLater()
|
||||
del self.__image_manager
|
||||
|
@ -328,10 +336,13 @@ class RareCore(QObject):
|
|||
self.__core.lgd.entitlements = result
|
||||
self.__fetched_entitlements = True
|
||||
|
||||
logger.info(f"Acquired data for {FetchWorker.Result(result_type).name}")
|
||||
logger.info("Acquired data from %s worker", FetchWorker.Result(result_type).name)
|
||||
|
||||
if all([self.__fetched_games_dlcs, self.__fetched_entitlements]):
|
||||
logger.debug(f"Fetch time {time.perf_counter() - self.__start_time} seconds")
|
||||
logger.debug("Fetch time %s seconds", time.perf_counter() - self.__start_time)
|
||||
self.__wrappers.import_wrappers(
|
||||
self.__core, self.__settings, [rgame.app_name for rgame in self.games]
|
||||
)
|
||||
self.progress.emit(100, self.tr("Launching Rare"))
|
||||
self.completed.emit()
|
||||
QTimer.singleShot(100, self.__post_init)
|
||||
|
@ -366,7 +377,7 @@ class RareCore(QObject):
|
|||
continue
|
||||
self.__library[app_name].load_saves(saves)
|
||||
except (HTTPError, ConnectionError) as e:
|
||||
logger.error(f"Exception while fetching saves from EGS.")
|
||||
logger.error("Exception while fetching saves from EGS.")
|
||||
logger.error(e)
|
||||
return
|
||||
logger.info(f"Saves: {len(saves_dict)}")
|
||||
|
|
|
@ -8,6 +8,7 @@ from requests.exceptions import HTTPError, ConnectionError
|
|||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.utils.metrics import timelogger
|
||||
from rare.models.options import options
|
||||
from .worker import Worker
|
||||
|
||||
logger = getLogger("FetchWorker")
|
||||
|
@ -32,12 +33,12 @@ class FetchWorker(Worker):
|
|||
|
||||
|
||||
class EntitlementsWorker(FetchWorker):
|
||||
def __init__(self, core: LegendaryCore, args: Namespace):
|
||||
super(EntitlementsWorker, self).__init__(core, args)
|
||||
|
||||
def run_real(self):
|
||||
|
||||
want_entitlements = not self.settings.value(*options.exclude_entitlements)
|
||||
|
||||
entitlements = ()
|
||||
want_entitlements = not self.settings.value("exclude_entitlements", False, bool)
|
||||
if want_entitlements:
|
||||
# Get entitlements, Ubisoft integration also uses them
|
||||
self.signals.progress.emit(0, self.signals.tr("Updating entitlements"))
|
||||
|
@ -51,20 +52,20 @@ class EntitlementsWorker(FetchWorker):
|
|||
|
||||
class GamesDlcsWorker(FetchWorker):
|
||||
|
||||
def __init__(self, core: LegendaryCore, args: Namespace):
|
||||
super(GamesDlcsWorker, self).__init__(core, args)
|
||||
self.exclude_non_asset = QSettings().value("exclude_non_asset", False, bool)
|
||||
|
||||
def run_real(self):
|
||||
|
||||
# Fetch regular EGL games with assets
|
||||
want_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug
|
||||
want_win32 = self.settings.value("win32_meta", False, bool)
|
||||
want_macos = self.settings.value("macos_meta", False, bool)
|
||||
# want_unreal = self.settings.value(*options.unreal_meta) or self.args.debug
|
||||
# want_win32 = self.settings.value(*options.win32_meta) or self.args.debug
|
||||
# want_macos = self.settings.value(*options.macos_meta) or self.args.debug
|
||||
want_unreal = self.settings.value(*options.unreal_meta)
|
||||
want_win32 = self.settings.value(*options.win32_meta)
|
||||
want_macos = self.settings.value(*options.macos_meta)
|
||||
want_non_asset = not self.settings.value(*options.exclude_non_asset)
|
||||
need_macos = platform.system() == "Darwin"
|
||||
need_windows = not any([want_win32, want_macos, need_macos, self.args.debug]) and not self.args.offline
|
||||
need_windows = not any([want_win32, want_macos, need_macos]) and not self.args.offline
|
||||
|
||||
if want_win32 or self.args.debug:
|
||||
if want_win32:
|
||||
logger.info(
|
||||
"Requesting Win32 metadata due to %s, %s Unreal engine",
|
||||
"settings" if want_win32 else "debug",
|
||||
|
@ -76,7 +77,7 @@ class GamesDlcsWorker(FetchWorker):
|
|||
update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal
|
||||
)
|
||||
|
||||
if need_macos or want_macos or self.args.debug:
|
||||
if need_macos or want_macos:
|
||||
logger.info(
|
||||
"Requesting macOS metadata due to %s, %s Unreal engine",
|
||||
"platform" if need_macos else "settings" if want_macos else "debug",
|
||||
|
@ -101,7 +102,6 @@ class GamesDlcsWorker(FetchWorker):
|
|||
logger.info(f"Games: %s. Games with DLCs: %s", len(games), len(dlc_dict))
|
||||
|
||||
# Fetch non-asset games
|
||||
want_non_asset = not self.settings.value("exclude_non_asset", False, bool)
|
||||
if want_non_asset:
|
||||
self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
|
||||
try:
|
||||
|
|
|
@ -11,7 +11,7 @@ from rare.lgndr.glue.arguments import LgndrUninstallGameArgs
|
|||
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.install import UninstallOptionsModel
|
||||
from rare.utils import config_helper
|
||||
from rare.utils import config_helper as config
|
||||
from rare.utils.paths import desktop_links_supported, desktop_link_types, desktop_link_path
|
||||
from .worker import Worker
|
||||
|
||||
|
@ -31,7 +31,7 @@ def uninstall_game(
|
|||
|
||||
logger.info('Removing registry entries...')
|
||||
if platform.system() != "Window":
|
||||
prefixes = config_helper.get_wine_prefixes()
|
||||
prefixes = config.get_prefixes()
|
||||
if platform.system() == "Darwin":
|
||||
# TODO: add crossover support
|
||||
pass
|
||||
|
@ -65,10 +65,10 @@ def uninstall_game(
|
|||
)
|
||||
if not keep_config:
|
||||
logger.info("Removing sections in config file")
|
||||
config_helper.remove_section(rgame.app_name)
|
||||
config_helper.remove_section(f"{rgame.app_name}.env")
|
||||
config.remove_section(rgame.app_name)
|
||||
config.remove_section(f"{rgame.app_name}.env")
|
||||
|
||||
config_helper.save_config()
|
||||
config.save_config()
|
||||
|
||||
return status.success, status.message
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@ import platform
|
|||
import time
|
||||
from configparser import ConfigParser
|
||||
from logging import getLogger
|
||||
from typing import Union, Iterable
|
||||
from typing import Union, Iterable, Mapping, List
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QRunnable
|
||||
|
||||
import rare.utils.wine as wine
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.pathspec import PathSpec
|
||||
from rare.utils import runners, config_helper as config
|
||||
from rare.utils.misc import path_size, format_size
|
||||
from .worker import Worker
|
||||
|
||||
|
@ -22,31 +22,52 @@ if platform.system() == "Windows":
|
|||
logger = getLogger("WineResolver")
|
||||
|
||||
|
||||
class WineResolver(Worker):
|
||||
class WinePathResolver(Worker):
|
||||
class Signals(QObject):
|
||||
result_ready = pyqtSignal(str)
|
||||
result_ready = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, core: LegendaryCore, path: str, app_name: str):
|
||||
super(WineResolver, self).__init__()
|
||||
self.signals = WineResolver.Signals()
|
||||
self.wine_env = wine.environ(core, app_name)
|
||||
self.wine_exec = wine.wine(core, app_name)
|
||||
self.path = PathSpec(core, app_name).cook(path)
|
||||
def __init__(self, command: List[str], environ: Mapping, path: str):
|
||||
super(WinePathResolver, self). __init__()
|
||||
self.signals = WinePathResolver.Signals()
|
||||
self.command = command
|
||||
self.environ = environ
|
||||
self.path = path
|
||||
|
||||
@staticmethod
|
||||
def _resolve_unix_path(cmd, env, path: str) -> str:
|
||||
logger.info("Resolving path '%s'", path)
|
||||
wine_path = runners.resolve_path(cmd, env, path)
|
||||
logger.debug("Resolved Wine path '%s'", path)
|
||||
unix_path = runners.convert_to_unix_path(cmd, env, wine_path)
|
||||
logger.debug("Resolved Unix path '%s'", unix_path)
|
||||
return unix_path
|
||||
|
||||
def run_real(self):
|
||||
if "WINEPREFIX" not in self.wine_env or not os.path.exists(self.wine_env["WINEPREFIX"]):
|
||||
# pylint: disable=E1136
|
||||
self.signals.result_ready[str].emit("")
|
||||
path = self._resolve_unix_path(self.command, self.environ, self.path)
|
||||
self.signals.result_ready.emit(path, "default")
|
||||
return
|
||||
if not os.path.exists(self.wine_exec):
|
||||
# pylint: disable=E1136
|
||||
self.signals.result_ready[str].emit("")
|
||||
return
|
||||
path = wine.resolve_path(self.wine_exec, self.wine_env, self.path)
|
||||
|
||||
|
||||
class WineSavePathResolver(WinePathResolver):
|
||||
|
||||
def __init__(self, core: LegendaryCore, rgame: RareGame):
|
||||
cmd = core.get_app_launch_command(rgame.app_name)
|
||||
env = core.get_app_environment(rgame.app_name)
|
||||
env = runners.get_environment(env, silent=True)
|
||||
path = PathSpec(core, rgame.igame).resolve_egl_path_vars(rgame.raw_save_path)
|
||||
if not (cmd and env and path):
|
||||
raise RuntimeError(f"Cannot setup {type(self).__name__}, missing infomation")
|
||||
super(WineSavePathResolver, self).__init__(cmd, env, path)
|
||||
self.rgame = rgame
|
||||
|
||||
def run_real(self):
|
||||
logger.info("Resolving save path for %s (%s)", self.rgame.app_title, self.rgame.app_name)
|
||||
path = self._resolve_unix_path(self.command, self.environ, self.path)
|
||||
# Clean wine output
|
||||
real_path = wine.convert_to_unix_path(self.wine_exec, self.wine_env, path)
|
||||
# pylint: disable=E1136
|
||||
self.signals.result_ready[str].emit(real_path)
|
||||
if os.path.exists(path):
|
||||
self.rgame.save_path = path
|
||||
self.signals.result_ready.emit(path, self.rgame.app_name)
|
||||
return
|
||||
|
||||
|
||||
|
@ -55,9 +76,7 @@ class OriginWineWorker(QRunnable):
|
|||
super(OriginWineWorker, self).__init__()
|
||||
self.__cache: dict[str, ConfigParser] = {}
|
||||
self.core = core
|
||||
if isinstance(games, RareGame):
|
||||
games = [games]
|
||||
self.games = games
|
||||
self.games = [games] if isinstance(games, RareGame) else games
|
||||
|
||||
def run(self) -> None:
|
||||
t = time.time()
|
||||
|
@ -79,15 +98,19 @@ class OriginWineWorker(QRunnable):
|
|||
if platform.system() == "Windows":
|
||||
install_dir = windows_helpers.query_registry_value(winreg.HKEY_LOCAL_MACHINE, reg_path, reg_key)
|
||||
else:
|
||||
wine_env = wine.environ(self.core, rgame.app_name)
|
||||
wine_exec = wine.wine(self.core, rgame.app_name)
|
||||
command = self.core.get_app_launch_command(rgame.app_name)
|
||||
environ = self.core.get_app_environment(rgame.app_name)
|
||||
environ = runners.get_environment(environ, silent=True)
|
||||
|
||||
prefix = config.get_prefix(rgame.app_name)
|
||||
if not prefix:
|
||||
return
|
||||
|
||||
use_wine = False
|
||||
if not use_wine:
|
||||
# lk: this is the original way of getting the path by parsing "system.reg"
|
||||
wine_prefix = wine.prefix(self.core, rgame.app_name)
|
||||
reg = self.__cache.get(wine_prefix, None) or wine.read_registry("system.reg", wine_prefix)
|
||||
self.__cache[wine_prefix] = reg
|
||||
reg = self.__cache.get(prefix, None) or runners.read_registry("system.reg", prefix)
|
||||
self.__cache[prefix] = reg
|
||||
|
||||
reg_path = reg_path.replace("SOFTWARE", "Software").replace("WOW6432Node", "Wow6432Node")
|
||||
# lk: split and rejoin the registry path to avoid slash expansion
|
||||
|
@ -96,11 +119,11 @@ class OriginWineWorker(QRunnable):
|
|||
install_dir = reg.get(reg_path, f'"{reg_key}"', fallback=None)
|
||||
else:
|
||||
# lk: this is the alternative way of getting the path by using wine itself
|
||||
install_dir = wine.query_reg_key(wine_exec, wine_env, f"HKLM\\{reg_path}", reg_key)
|
||||
install_dir = runners.query_reg_key(command, environ, f"HKLM\\{reg_path}", reg_key)
|
||||
|
||||
if install_dir:
|
||||
logger.debug("Found Wine install directory %s", install_dir)
|
||||
install_dir = wine.convert_to_unix_path(wine_exec, wine_env, install_dir)
|
||||
install_dir = runners.convert_to_unix_path(command, environ, install_dir)
|
||||
if install_dir:
|
||||
logger.debug("Found Unix install directory %s", install_dir)
|
||||
else:
|
||||
|
|
169
rare/shared/wrappers.py
Normal file
169
rare/shared/wrappers.py
Normal file
|
@ -0,0 +1,169 @@
|
|||
import json
|
||||
import os
|
||||
from logging import getLogger
|
||||
import shlex
|
||||
from typing import List, Dict, Iterable
|
||||
from rare.utils import config_helper as config
|
||||
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.wrapper import Wrapper, WrapperType
|
||||
from rare.utils.paths import config_dir
|
||||
|
||||
logger = getLogger("Wrappers")
|
||||
|
||||
|
||||
class Wrappers:
|
||||
def __init__(self):
|
||||
self.__file = os.path.join(config_dir(), "wrappers.json")
|
||||
self.__wrappers_dict = {}
|
||||
try:
|
||||
with open(self.__file) as f:
|
||||
self.__wrappers_dict = json.load(f)
|
||||
except FileNotFoundError:
|
||||
logger.info("%s does not exist", self.__file)
|
||||
except json.JSONDecodeError:
|
||||
logger.warning("%s is corrupt", self.__file)
|
||||
|
||||
self.__wrappers: Dict[str, Wrapper] = {}
|
||||
for wrap_id, wrapper in self.__wrappers_dict.get("wrappers", {}).items():
|
||||
self.__wrappers.update({wrap_id: Wrapper.from_dict(wrapper)})
|
||||
|
||||
self.__applists: Dict[str, List[str]] = {}
|
||||
for app_name, wrapper_list in self.__wrappers_dict.get("applists", {}).items():
|
||||
self.__applists.update({app_name: wrapper_list})
|
||||
|
||||
def import_wrappers(self, core: LegendaryCore, settings: QSettings, app_names: List):
|
||||
for app_name in app_names:
|
||||
wrappers = self.get_game_wrapper_list(app_name)
|
||||
if not wrappers and (commands := settings.value(f"{app_name}/wrapper", [], list)):
|
||||
logger.info("Importing wrappers from Rare's config")
|
||||
settings.remove(f"{app_name}/wrapper")
|
||||
for command in commands:
|
||||
wrapper = Wrapper(command=shlex.split(command))
|
||||
wrappers.append(wrapper)
|
||||
self.set_game_wrapper_list(app_name, wrappers)
|
||||
logger.debug("Imported previous wrappers in %s Rare: %s", app_name, wrapper.name)
|
||||
|
||||
# NOTE: compatibility with Legendary
|
||||
if not wrappers and (command := core.lgd.config.get(app_name, "wrapper", fallback="")):
|
||||
logger.info("Importing wrappers from legendary's config")
|
||||
# no qt wrapper, but legendary wrapper, to have backward compatibility
|
||||
# pattern = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''')
|
||||
# wrappers = pattern.split(command)[1::2]
|
||||
wrapper = Wrapper(
|
||||
command=shlex.split(command),
|
||||
name="Imported from Legendary",
|
||||
wtype=WrapperType.LEGENDARY_IMPORT
|
||||
)
|
||||
wrappers = [wrapper]
|
||||
self.set_game_wrapper_list(app_name, wrappers)
|
||||
logger.debug("Imported existing wrappers in %s legendary: %s", app_name, wrapper.name)
|
||||
|
||||
@property
|
||||
def user_wrappers(self) -> Iterable[Wrapper]:
|
||||
return filter(lambda w: w.is_editable, self.__wrappers.values())
|
||||
# for wrap in self.__wrappers.values():
|
||||
# if wrap.is_user_defined:
|
||||
# yield wrap
|
||||
|
||||
def get_game_wrapper_string(self, app_name: str) -> str:
|
||||
commands = [wrapper.command for wrapper in self.get_game_wrapper_list(app_name)]
|
||||
return " ".join(commands)
|
||||
|
||||
def get_game_wrapper_list(self, app_name: str) -> List[Wrapper]:
|
||||
_wrappers = []
|
||||
for wrap_id in self.__applists.get(app_name, []):
|
||||
if wrap := self.__wrappers.get(wrap_id, None):
|
||||
_wrappers.append(wrap)
|
||||
return _wrappers
|
||||
|
||||
def get_game_md5sum_list(self, app_name: str) -> List[str]:
|
||||
return self.__applists.get(app_name, [])
|
||||
|
||||
def set_game_wrapper_list(self, app_name: str, wrappers: List[Wrapper]) -> None:
|
||||
_wrappers = sorted(wrappers, key=lambda w: w.is_compat_tool)
|
||||
for w in _wrappers:
|
||||
if (md5sum := w.checksum) in self.__wrappers.keys():
|
||||
if w != self.__wrappers[md5sum]:
|
||||
logger.error(
|
||||
"Non-unique md5sum for different wrappers %s, %s",
|
||||
w.name,
|
||||
self.__wrappers[md5sum].name,
|
||||
)
|
||||
if w.is_compat_tool:
|
||||
self.__wrappers.update({md5sum: w})
|
||||
else:
|
||||
self.__wrappers.update({md5sum: w})
|
||||
self.__applists[app_name] = [w.checksum for w in _wrappers]
|
||||
self.__save_config(app_name)
|
||||
self.__save_wrappers()
|
||||
|
||||
def __save_config(self, app_name: str):
|
||||
command_string = self.get_game_wrapper_string(app_name)
|
||||
if command_string:
|
||||
config.add_option(app_name, "wrapper", command_string)
|
||||
else:
|
||||
config.remove_option(app_name, "wrapper")
|
||||
config.save_config()
|
||||
|
||||
def __save_wrappers(self):
|
||||
existing = {wrap_id for wrap_id in self.__wrappers.keys()}
|
||||
in_use = {wrap_id for wrappers in self.__applists.values() for wrap_id in wrappers}
|
||||
|
||||
for redudant in existing.difference(in_use):
|
||||
del self.__wrappers[redudant]
|
||||
|
||||
self.__wrappers_dict["wrappers"] = self.__wrappers
|
||||
self.__wrappers_dict["applists"] = self.__applists
|
||||
|
||||
with open(os.path.join(self.__file), "w+") as f:
|
||||
json.dump(self.__wrappers_dict, f, default=lambda o: vars(o), indent=2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from pprint import pprint
|
||||
from argparse import Namespace
|
||||
|
||||
from rare.utils.runners import proton
|
||||
|
||||
global config_dir
|
||||
config_dir = os.getcwd
|
||||
global config
|
||||
config = Namespace()
|
||||
config.add_option = lambda x, y, z: print(x, y, z)
|
||||
config.remove_option = lambda x, y: print(x, y)
|
||||
config.save_config = lambda: print()
|
||||
|
||||
wr = Wrappers()
|
||||
|
||||
w1 = Wrapper(command=["/usr/bin/w1"], wtype=WrapperType.NONE)
|
||||
w2 = Wrapper(command=["/usr/bin/w2"], wtype=WrapperType.COMPAT_TOOL)
|
||||
w3 = Wrapper(command=["/usr/bin/w3"], wtype=WrapperType.USER_DEFINED)
|
||||
w4 = Wrapper(command=["/usr/bin/w4"], wtype=WrapperType.USER_DEFINED)
|
||||
wr.set_game_wrapper_list("testgame", [w1, w2, w3, w4])
|
||||
|
||||
w5 = Wrapper(command=["/usr/bin/w5"], wtype=WrapperType.COMPAT_TOOL)
|
||||
wr.set_game_wrapper_list("testgame2", [w2, w1, w5])
|
||||
|
||||
w6 = Wrapper(command=["/usr/bin/w 6", "-w", "-t"], wtype=WrapperType.USER_DEFINED)
|
||||
wr.set_game_wrapper_list("testgame", [w1, w2, w3, w6])
|
||||
|
||||
w7 = Wrapper(command=["/usr/bin/w2"], wtype=WrapperType.COMPAT_TOOL)
|
||||
wrs = wr.get_game_wrapper_list("testgame")
|
||||
wrs.remove(w7)
|
||||
wr.set_game_wrapper_list("testgame", wrs)
|
||||
|
||||
game_wrappers = wr.get_game_wrapper_list("testgame")
|
||||
pprint(game_wrappers)
|
||||
game_wrappers = wr.get_game_wrapper_list("testgame2")
|
||||
pprint(game_wrappers)
|
||||
|
||||
for i, tool in enumerate(proton.find_tools()):
|
||||
wt = Wrapper(command=tool.command(), name=tool.name, wtype=WrapperType.COMPAT_TOOL)
|
||||
wr.set_game_wrapper_list(f"compat_game_{i}", [wt])
|
||||
print(wt.command)
|
||||
|
||||
for wrp in wr.user_wrappers:
|
||||
pprint(wrp)
|
103
rare/ui/components/tabs/settings/game.py
Normal file
103
rare/ui/components/tabs/settings/game.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/game_settings.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.10
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_GameSettings(object):
|
||||
def setupUi(self, GameSettings):
|
||||
GameSettings.setObjectName("GameSettings")
|
||||
GameSettings.resize(393, 202)
|
||||
self.main_layout = QtWidgets.QVBoxLayout(GameSettings)
|
||||
self.main_layout.setObjectName("main_layout")
|
||||
self.launch_settings_group = QtWidgets.QGroupBox(GameSettings)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.launch_settings_group.sizePolicy().hasHeightForWidth())
|
||||
self.launch_settings_group.setSizePolicy(sizePolicy)
|
||||
self.launch_settings_group.setObjectName("launch_settings_group")
|
||||
self.launch_layout = QtWidgets.QFormLayout(self.launch_settings_group)
|
||||
self.launch_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.launch_layout.setObjectName("launch_layout")
|
||||
self.skip_update_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.skip_update_label.setObjectName("skip_update_label")
|
||||
self.launch_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.skip_update_label)
|
||||
self.skip_update_combo = QtWidgets.QComboBox(self.launch_settings_group)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.skip_update_combo.sizePolicy().hasHeightForWidth())
|
||||
self.skip_update_combo.setSizePolicy(sizePolicy)
|
||||
self.skip_update_combo.setObjectName("skip_update_combo")
|
||||
self.skip_update_combo.addItem("")
|
||||
self.skip_update_combo.addItem("")
|
||||
self.skip_update_combo.addItem("")
|
||||
self.launch_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.skip_update_combo)
|
||||
self.offline_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.offline_label.setObjectName("offline_label")
|
||||
self.launch_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.offline_label)
|
||||
self.offline_combo = QtWidgets.QComboBox(self.launch_settings_group)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.offline_combo.sizePolicy().hasHeightForWidth())
|
||||
self.offline_combo.setSizePolicy(sizePolicy)
|
||||
self.offline_combo.setObjectName("offline_combo")
|
||||
self.offline_combo.addItem("")
|
||||
self.offline_combo.addItem("")
|
||||
self.offline_combo.addItem("")
|
||||
self.launch_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.offline_combo)
|
||||
self.launch_params_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.launch_params_label.setObjectName("launch_params_label")
|
||||
self.launch_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.launch_params_label)
|
||||
self.launch_params_edit = QtWidgets.QLineEdit(self.launch_settings_group)
|
||||
self.launch_params_edit.setObjectName("launch_params_edit")
|
||||
self.launch_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.launch_params_edit)
|
||||
self.pre_launch_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.pre_launch_label.setObjectName("pre_launch_label")
|
||||
self.launch_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.pre_launch_label)
|
||||
self.override_exe_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.override_exe_label.setObjectName("override_exe_label")
|
||||
self.launch_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.override_exe_label)
|
||||
self.wrapper_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.wrapper_label.setObjectName("wrapper_label")
|
||||
self.launch_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.wrapper_label)
|
||||
self.main_layout.addWidget(self.launch_settings_group)
|
||||
|
||||
self.retranslateUi(GameSettings)
|
||||
|
||||
def retranslateUi(self, GameSettings):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
GameSettings.setWindowTitle(_translate("GameSettings", "GameSettings"))
|
||||
self.launch_settings_group.setTitle(_translate("GameSettings", "Launch Settings"))
|
||||
self.skip_update_label.setText(_translate("GameSettings", "Skip update check"))
|
||||
self.skip_update_combo.setItemText(0, _translate("GameSettings", "Default"))
|
||||
self.skip_update_combo.setItemText(1, _translate("GameSettings", "Yes"))
|
||||
self.skip_update_combo.setItemText(2, _translate("GameSettings", "No"))
|
||||
self.offline_label.setText(_translate("GameSettings", "Offline mode"))
|
||||
self.offline_combo.setItemText(0, _translate("GameSettings", "Default"))
|
||||
self.offline_combo.setItemText(1, _translate("GameSettings", "Yes"))
|
||||
self.offline_combo.setItemText(2, _translate("GameSettings", "No"))
|
||||
self.launch_params_label.setText(_translate("GameSettings", "Launch parameters"))
|
||||
self.launch_params_edit.setPlaceholderText(_translate("GameSettings", "parameters"))
|
||||
self.pre_launch_label.setText(_translate("GameSettings", "Pre-launch command"))
|
||||
self.override_exe_label.setText(_translate("GameSettings", "Override executable"))
|
||||
self.wrapper_label.setText(_translate("GameSettings", "Wrappers"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
GameSettings = QtWidgets.QWidget()
|
||||
ui = Ui_GameSettings()
|
||||
ui.setupUi(GameSettings)
|
||||
GameSettings.show()
|
||||
sys.exit(app.exec_())
|
|
@ -6,14 +6,14 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>483</width>
|
||||
<height>154</height>
|
||||
<width>393</width>
|
||||
<height>202</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>GameSettings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="game_settings_layout">
|
||||
<layout class="QVBoxLayout" name="main_layout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="launch_settings_group">
|
||||
<property name="sizePolicy">
|
||||
|
@ -25,7 +25,7 @@
|
|||
<property name="title">
|
||||
<string>Launch Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="launch_settings_layout">
|
||||
<layout class="QFormLayout" name="launch_layout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
|
@ -37,7 +37,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="skip_update">
|
||||
<widget class="QComboBox" name="skip_update_combo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -69,7 +69,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="offline">
|
||||
<widget class="QComboBox" name="offline_combo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -101,12 +101,33 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="launch_params">
|
||||
<widget class="QLineEdit" name="launch_params_edit">
|
||||
<property name="placeholderText">
|
||||
<string>parameters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="pre_launch_label">
|
||||
<property name="text">
|
||||
<string>Pre-launch command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="override_exe_label">
|
||||
<property name="text">
|
||||
<string>Override executable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="wrapper_label">
|
||||
<property name="text">
|
||||
<string>Wrappers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
|
@ -1,100 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/game_settings.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_GameSettings(object):
|
||||
def setupUi(self, GameSettings):
|
||||
GameSettings.setObjectName("GameSettings")
|
||||
GameSettings.resize(505, 261)
|
||||
self.game_settings_layout = QtWidgets.QVBoxLayout(GameSettings)
|
||||
self.game_settings_layout.setObjectName("game_settings_layout")
|
||||
self.launch_settings_group = QtWidgets.QGroupBox(GameSettings)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.launch_settings_group.sizePolicy().hasHeightForWidth())
|
||||
self.launch_settings_group.setSizePolicy(sizePolicy)
|
||||
self.launch_settings_group.setObjectName("launch_settings_group")
|
||||
self.launch_settings_layout = QtWidgets.QFormLayout(self.launch_settings_group)
|
||||
self.launch_settings_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.launch_settings_layout.setObjectName("launch_settings_layout")
|
||||
self.skip_update_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.skip_update_label.setObjectName("skip_update_label")
|
||||
self.launch_settings_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.skip_update_label)
|
||||
self.skip_update = QtWidgets.QComboBox(self.launch_settings_group)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.skip_update.sizePolicy().hasHeightForWidth())
|
||||
self.skip_update.setSizePolicy(sizePolicy)
|
||||
self.skip_update.setObjectName("skip_update")
|
||||
self.skip_update.addItem("")
|
||||
self.skip_update.addItem("")
|
||||
self.skip_update.addItem("")
|
||||
self.launch_settings_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.skip_update)
|
||||
self.offline_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.offline_label.setObjectName("offline_label")
|
||||
self.launch_settings_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.offline_label)
|
||||
self.offline = QtWidgets.QComboBox(self.launch_settings_group)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.offline.sizePolicy().hasHeightForWidth())
|
||||
self.offline.setSizePolicy(sizePolicy)
|
||||
self.offline.setObjectName("offline")
|
||||
self.offline.addItem("")
|
||||
self.offline.addItem("")
|
||||
self.offline.addItem("")
|
||||
self.launch_settings_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.offline)
|
||||
self.launch_params_label = QtWidgets.QLabel(self.launch_settings_group)
|
||||
self.launch_params_label.setObjectName("launch_params_label")
|
||||
self.launch_settings_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.launch_params_label)
|
||||
self.launch_params = QtWidgets.QLineEdit(self.launch_settings_group)
|
||||
self.launch_params.setObjectName("launch_params")
|
||||
self.launch_settings_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.launch_params)
|
||||
self.game_settings_layout.addWidget(self.launch_settings_group)
|
||||
self.proton_layout = QtWidgets.QVBoxLayout()
|
||||
self.proton_layout.setObjectName("proton_layout")
|
||||
self.game_settings_layout.addLayout(self.proton_layout)
|
||||
self.linux_settings_widget = QtWidgets.QWidget(GameSettings)
|
||||
self.linux_settings_widget.setObjectName("linux_settings_widget")
|
||||
self.linux_settings_layout = QtWidgets.QVBoxLayout(self.linux_settings_widget)
|
||||
self.linux_settings_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.linux_settings_layout.setObjectName("linux_settings_layout")
|
||||
self.game_settings_layout.addWidget(self.linux_settings_widget)
|
||||
|
||||
self.retranslateUi(GameSettings)
|
||||
|
||||
def retranslateUi(self, GameSettings):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
GameSettings.setWindowTitle(_translate("GameSettings", "GameSettings"))
|
||||
self.launch_settings_group.setTitle(_translate("GameSettings", "Launch Settings"))
|
||||
self.skip_update_label.setText(_translate("GameSettings", "Skip update check"))
|
||||
self.skip_update.setItemText(0, _translate("GameSettings", "Default"))
|
||||
self.skip_update.setItemText(1, _translate("GameSettings", "Yes"))
|
||||
self.skip_update.setItemText(2, _translate("GameSettings", "No"))
|
||||
self.offline_label.setText(_translate("GameSettings", "Offline mode"))
|
||||
self.offline.setItemText(0, _translate("GameSettings", "Default"))
|
||||
self.offline.setItemText(1, _translate("GameSettings", "Yes"))
|
||||
self.offline.setItemText(2, _translate("GameSettings", "No"))
|
||||
self.launch_params_label.setText(_translate("GameSettings", "Launch parameters"))
|
||||
self.launch_params.setPlaceholderText(_translate("GameSettings", "parameters"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
GameSettings = QtWidgets.QWidget()
|
||||
ui = Ui_GameSettings()
|
||||
ui.setupUi(GameSettings)
|
||||
GameSettings.show()
|
||||
sys.exit(app.exec_())
|
|
@ -1,63 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/linux.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_LinuxSettings(object):
|
||||
def setupUi(self, LinuxSettings):
|
||||
LinuxSettings.setObjectName("LinuxSettings")
|
||||
LinuxSettings.resize(394, 84)
|
||||
self.linux_layout = QtWidgets.QVBoxLayout(LinuxSettings)
|
||||
self.linux_layout.setObjectName("linux_layout")
|
||||
self.wine_groupbox = QtWidgets.QGroupBox(LinuxSettings)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.wine_groupbox.sizePolicy().hasHeightForWidth())
|
||||
self.wine_groupbox.setSizePolicy(sizePolicy)
|
||||
self.wine_groupbox.setObjectName("wine_groupbox")
|
||||
self.wine_layout = QtWidgets.QFormLayout(self.wine_groupbox)
|
||||
self.wine_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.wine_layout.setObjectName("wine_layout")
|
||||
self.prefix_label = QtWidgets.QLabel(self.wine_groupbox)
|
||||
self.prefix_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.prefix_label.setObjectName("prefix_label")
|
||||
self.wine_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.prefix_label)
|
||||
self.prefix_layout = QtWidgets.QVBoxLayout()
|
||||
self.prefix_layout.setObjectName("prefix_layout")
|
||||
self.wine_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.prefix_layout)
|
||||
self.exec_label = QtWidgets.QLabel(self.wine_groupbox)
|
||||
self.exec_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.exec_label.setObjectName("exec_label")
|
||||
self.wine_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.exec_label)
|
||||
self.exec_layout = QtWidgets.QVBoxLayout()
|
||||
self.exec_layout.setObjectName("exec_layout")
|
||||
self.wine_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.exec_layout)
|
||||
self.linux_layout.addWidget(self.wine_groupbox)
|
||||
|
||||
self.retranslateUi(LinuxSettings)
|
||||
|
||||
def retranslateUi(self, LinuxSettings):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
LinuxSettings.setWindowTitle(_translate("LinuxSettings", "LinuxSettings"))
|
||||
self.wine_groupbox.setTitle(_translate("LinuxSettings", "Wine Settings"))
|
||||
self.prefix_label.setText(_translate("LinuxSettings", "Prefix"))
|
||||
self.exec_label.setText(_translate("LinuxSettings", "Executable"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
LinuxSettings = QtWidgets.QWidget()
|
||||
ui = Ui_LinuxSettings()
|
||||
ui.setupUi(LinuxSettings)
|
||||
LinuxSettings.show()
|
||||
sys.exit(app.exec_())
|
|
@ -1,65 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LinuxSettings</class>
|
||||
<widget class="QWidget" name="LinuxSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>394</width>
|
||||
<height>84</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>LinuxSettings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="linux_layout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="wine_groupbox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Wine Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="wine_layout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="prefix_label">
|
||||
<property name="text">
|
||||
<string>Prefix</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="prefix_layout"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="exec_label">
|
||||
<property name="text">
|
||||
<string>Executable</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QVBoxLayout" name="exec_layout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/proton.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
# Created by: PyQt5 UI code generator 5.15.10
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
@ -14,14 +14,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||
class Ui_ProtonSettings(object):
|
||||
def setupUi(self, ProtonSettings):
|
||||
ProtonSettings.setObjectName("ProtonSettings")
|
||||
ProtonSettings.resize(190, 86)
|
||||
ProtonSettings.resize(180, 84)
|
||||
ProtonSettings.setWindowTitle("ProtonSettings")
|
||||
self.proton_settings_layout = QtWidgets.QFormLayout(ProtonSettings)
|
||||
self.proton_settings_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.proton_settings_layout.setObjectName("proton_settings_layout")
|
||||
self.main_layout = QtWidgets.QFormLayout(ProtonSettings)
|
||||
self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.main_layout.setObjectName("main_layout")
|
||||
self.proton_wrapper_label = QtWidgets.QLabel(ProtonSettings)
|
||||
self.proton_wrapper_label.setObjectName("proton_wrapper_label")
|
||||
self.proton_settings_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.proton_wrapper_label)
|
||||
self.main_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.proton_wrapper_label)
|
||||
self.proton_combo = QtWidgets.QComboBox(ProtonSettings)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
|
@ -30,13 +30,10 @@ class Ui_ProtonSettings(object):
|
|||
self.proton_combo.setSizePolicy(sizePolicy)
|
||||
self.proton_combo.setObjectName("proton_combo")
|
||||
self.proton_combo.addItem("")
|
||||
self.proton_settings_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.proton_combo)
|
||||
self.main_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.proton_combo)
|
||||
self.proton_prefix_label = QtWidgets.QLabel(ProtonSettings)
|
||||
self.proton_prefix_label.setObjectName("proton_prefix_label")
|
||||
self.proton_settings_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.proton_prefix_label)
|
||||
self.prefix_layout = QtWidgets.QHBoxLayout()
|
||||
self.prefix_layout.setObjectName("prefix_layout")
|
||||
self.proton_settings_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.prefix_layout)
|
||||
self.main_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.proton_prefix_label)
|
||||
|
||||
self.retranslateUi(ProtonSettings)
|
||||
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>190</width>
|
||||
<height>86</height>
|
||||
<width>180</width>
|
||||
<height>84</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -16,7 +16,7 @@
|
|||
<property name="title">
|
||||
<string>Proton Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="proton_settings_layout">
|
||||
<layout class="QFormLayout" name="main_layout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
|
@ -49,9 +49,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="prefix_layout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
47
rare/ui/components/tabs/settings/widgets/wine.py
Normal file
47
rare/ui/components/tabs/settings/widgets/wine.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/wine.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.10
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_WineSettings(object):
|
||||
def setupUi(self, WineSettings):
|
||||
WineSettings.setObjectName("WineSettings")
|
||||
WineSettings.resize(104, 72)
|
||||
WineSettings.setWindowTitle("WIneSettings")
|
||||
self.main_layout = QtWidgets.QFormLayout(WineSettings)
|
||||
self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.main_layout.setObjectName("main_layout")
|
||||
self.prefix_label = QtWidgets.QLabel(WineSettings)
|
||||
self.prefix_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.prefix_label.setObjectName("prefix_label")
|
||||
self.main_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.prefix_label)
|
||||
self.exec_label = QtWidgets.QLabel(WineSettings)
|
||||
self.exec_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.exec_label.setObjectName("exec_label")
|
||||
self.main_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.exec_label)
|
||||
|
||||
self.retranslateUi(WineSettings)
|
||||
|
||||
def retranslateUi(self, WineSettings):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
WineSettings.setTitle(_translate("WineSettings", "Wine Settings"))
|
||||
self.prefix_label.setText(_translate("WineSettings", "Prefix"))
|
||||
self.exec_label.setText(_translate("WineSettings", "Executable"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
WineSettings = QtWidgets.QGroupBox()
|
||||
ui = Ui_WineSettings()
|
||||
ui.setupUi(WineSettings)
|
||||
WineSettings.show()
|
||||
sys.exit(app.exec_())
|
47
rare/ui/components/tabs/settings/widgets/wine.ui
Normal file
47
rare/ui/components/tabs/settings/widgets/wine.ui
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>WineSettings</class>
|
||||
<widget class="QGroupBox" name="WineSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>104</width>
|
||||
<height>72</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">WIneSettings</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Wine Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="main_layout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="prefix_label">
|
||||
<property name="text">
|
||||
<string>Prefix</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="exec_label">
|
||||
<property name="text">
|
||||
<string>Executable</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -7,6 +7,7 @@ from PyQt5.QtCore import QObject, QSettings
|
|||
from pypresence import Presence
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
||||
from rare.models.options import options
|
||||
|
||||
client_id = "830732538225360908"
|
||||
logger = getLogger("RPC")
|
||||
|
@ -21,7 +22,7 @@ class DiscordRPC(QObject):
|
|||
self.signals = GlobalSignalsSingleton()
|
||||
|
||||
self.settings = QSettings()
|
||||
if self.settings.value("rpc_enable", 0, int) == 1: # show always
|
||||
if self.settings.value(*options.rpc_enable) == 1: # show always
|
||||
self.state = 2
|
||||
self.set_discord_rpc()
|
||||
|
||||
|
@ -32,7 +33,7 @@ class DiscordRPC(QObject):
|
|||
self.set_discord_rpc(app_name)
|
||||
|
||||
def changed_settings(self, game_running: list = None):
|
||||
value = self.settings.value("rpc_enable", 0, int)
|
||||
value = self.settings.value(*options.rpc_enable)
|
||||
if value == 2:
|
||||
self.remove_rpc()
|
||||
return
|
||||
|
@ -44,7 +45,7 @@ class DiscordRPC(QObject):
|
|||
self.set_discord_rpc(game_running[0])
|
||||
|
||||
def remove_rpc(self):
|
||||
if self.settings.value("rpc_enable", 0, int) != 1:
|
||||
if self.settings.value(*options.rpc_enable) != 1:
|
||||
if not self.RPC:
|
||||
return
|
||||
try:
|
||||
|
@ -85,8 +86,8 @@ class DiscordRPC(QObject):
|
|||
self.update_rpc(app_name)
|
||||
|
||||
def update_rpc(self, app_name=None):
|
||||
if self.settings.value("rpc_enable", 0, int) == 2 or (
|
||||
not app_name and self.settings.value("rpc_enable", 0, int) == 0
|
||||
if self.settings.value(*options.rpc_enable) == 2 or (
|
||||
not app_name and self.settings.value(*options.rpc_enable) == 0
|
||||
):
|
||||
self.remove_rpc()
|
||||
return
|
||||
|
@ -96,17 +97,17 @@ class DiscordRPC(QObject):
|
|||
large_image="logo", details="https://github.com/RareDevs/Rare"
|
||||
)
|
||||
return
|
||||
if self.settings.value("rpc_name", True, bool):
|
||||
if self.settings.value(*options.rpc_name):
|
||||
try:
|
||||
title = self.core.get_installed_game(app_name).title
|
||||
except AttributeError:
|
||||
logger.error(f"Could not get title of game: {app_name}")
|
||||
title = app_name
|
||||
start = None
|
||||
if self.settings.value("rpc_time", True, bool):
|
||||
if self.settings.value(*options.rpc_time):
|
||||
start = str(time.time()).split(".")[0]
|
||||
os = None
|
||||
if self.settings.value("rpc_os", True, bool):
|
||||
if self.settings.value(*options.rpc_os):
|
||||
os = f"via Rare on {platform.system()}"
|
||||
|
||||
self.RPC.update(
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from configparser import ConfigParser
|
||||
from logging import getLogger
|
||||
from typing import Mapping, Dict, List, Tuple
|
||||
from typing import Mapping, Dict, List, Tuple, Optional
|
||||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.utils import config_helper as config
|
||||
from . import proton
|
||||
from . import wine
|
||||
|
||||
logger = getLogger("Wine")
|
||||
logger = getLogger("Runners")
|
||||
|
||||
|
||||
# this is a copied function from legendary.utils.wine_helpers, but registry file can be specified
|
||||
def read_registry(registry: str, wine_pfx: str) -> ConfigParser:
|
||||
def read_registry(registry: str, prefix: str) -> ConfigParser:
|
||||
accepted = ["system.reg", "user.reg"]
|
||||
if registry not in accepted:
|
||||
raise RuntimeError(f'Unknown target "{registry}" not in {accepted}')
|
||||
reg = ConfigParser(comment_prefixes=(';', '#', '/', 'WINE'), allow_no_value=True,
|
||||
strict=False)
|
||||
reg.optionxform = str
|
||||
reg.read(os.path.join(wine_pfx, 'system.reg'))
|
||||
reg.read(os.path.join(prefix, 'system.reg'))
|
||||
return reg
|
||||
|
||||
|
||||
def execute(command: List, wine_env: Mapping) -> Tuple[str, str]:
|
||||
def execute(command: List[str], environment: Mapping) -> Tuple[str, str]:
|
||||
if os.environ.get("container") == "flatpak":
|
||||
flatpak_command = ["flatpak-spawn", "--host"]
|
||||
for name, value in wine_env.items():
|
||||
for name, value in environment.items():
|
||||
flatpak_command.append(f"--env={name}={value}")
|
||||
command = flatpak_command + command
|
||||
try:
|
||||
|
@ -35,7 +36,7 @@ def execute(command: List, wine_env: Mapping) -> Tuple[str, str]:
|
|||
stderr=subprocess.PIPE,
|
||||
# Use the current environment if we are in flatpak or our own if we are on host
|
||||
# In flatpak our environment is passed through `flatpak-spawn` arguments
|
||||
env=os.environ.copy() if os.environ.get("container") == "flatpak" else wine_env,
|
||||
env=os.environ.copy() if os.environ.get("container") == "flatpak" else environment,
|
||||
shell=False,
|
||||
text=True,
|
||||
)
|
||||
|
@ -45,13 +46,13 @@ def execute(command: List, wine_env: Mapping) -> Tuple[str, str]:
|
|||
return res
|
||||
|
||||
|
||||
def resolve_path(wine_exec: str, wine_env: Mapping, path: str) -> str:
|
||||
def resolve_path(command: List[str], environment: Mapping, path: str) -> str:
|
||||
path = path.strip().replace("/", "\\")
|
||||
# lk: if path does not exist form
|
||||
cmd = [wine_exec, "cmd", "/c", "echo", path]
|
||||
cmd = command + ["cmd", "/c", "echo", path]
|
||||
# lk: if path exists and needs a case-sensitive interpretation form
|
||||
# cmd = [wine_cmd, 'cmd', '/c', f'cd {path} & cd']
|
||||
out, err = execute(cmd, wine_env)
|
||||
out, err = execute(cmd, environment)
|
||||
out, err = out.strip(), err.strip()
|
||||
if not out:
|
||||
logger.error("Failed to resolve wine path due to \"%s\"", err)
|
||||
|
@ -63,9 +64,9 @@ def query_reg_path(wine_exec: str, wine_env: Mapping, reg_path: str):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
def query_reg_key(wine_exec: str, wine_env: Mapping, reg_path: str, reg_key) -> str:
|
||||
cmd = [wine_exec, "reg", "query", reg_path, "/v", reg_key]
|
||||
out, err = execute(cmd, wine_env)
|
||||
def query_reg_key(command: List[str], environment: Mapping, reg_path: str, reg_key) -> str:
|
||||
cmd = command + ["reg", "query", reg_path, "/v", reg_key]
|
||||
out, err = execute(cmd, environment)
|
||||
out, err = out.strip(), err.strip()
|
||||
if not out:
|
||||
logger.error("Failed to query registry key due to \"%s\"", err)
|
||||
|
@ -83,40 +84,23 @@ def convert_to_windows_path(wine_exec: str, wine_env: Mapping, path: str) -> str
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
def convert_to_unix_path(wine_exec: str, wine_env: Mapping, path: str) -> str:
|
||||
def convert_to_unix_path(command: List[str], environment: Mapping, path: str) -> str:
|
||||
path = path.strip().strip('"')
|
||||
cmd = [wine_exec, "winepath.exe", "-u", path]
|
||||
out, err = execute(cmd, wine_env)
|
||||
cmd = command + ["winepath.exe", "-u", path]
|
||||
out, err = execute(cmd, environment)
|
||||
out, err = out.strip(), err.strip()
|
||||
if not out:
|
||||
logger.error("Failed to convert to unix path due to \"%s\"", err)
|
||||
return os.path.realpath(out) if (out := out.strip()) else out
|
||||
|
||||
|
||||
def wine(core: LegendaryCore, app_name: str = "default") -> str:
|
||||
_wine = core.lgd.config.get(
|
||||
app_name, "wine_executable", fallback=core.lgd.config.get(
|
||||
"default", "wine_executable", fallback=shutil.which("wine")
|
||||
)
|
||||
)
|
||||
return _wine
|
||||
|
||||
|
||||
def environ(core: LegendaryCore, app_name: str = "default") -> Dict:
|
||||
# Get a clean environment if we are in flatpak, this environment will be pass
|
||||
def get_environment(app_environment: Dict, silent: bool = True) -> Dict:
|
||||
# Get a clean environment if we are in flatpak, this environment will be passed
|
||||
# to `flatpak-spawn`, otherwise use the system's.
|
||||
_environ = {} if os.environ.get("container") == "flatpak" else os.environ.copy()
|
||||
_environ.update(core.get_app_environment(app_name))
|
||||
_environ.update(app_environment)
|
||||
if silent:
|
||||
_environ["WINEDEBUG"] = "-all"
|
||||
_environ["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
|
||||
_environ["DISPLAY"] = ""
|
||||
return _environ
|
||||
|
||||
|
||||
def prefix(core: LegendaryCore, app_name: str = "default") -> str:
|
||||
_prefix = core.lgd.config.get(
|
||||
app_name, "wine_prefix", fallback=core.lgd.config.get(
|
||||
"default", "wine_prefix", fallback=os.path.expanduser("~/.wine")
|
||||
)
|
||||
)
|
||||
return _prefix if os.path.isdir(_prefix) else ""
|
|
@ -1,6 +1,8 @@
|
|||
import platform as pf
|
||||
import os
|
||||
import shlex
|
||||
from dataclasses import dataclass
|
||||
from hashlib import md5
|
||||
from logging import getLogger
|
||||
from typing import Optional, Union, List, Dict
|
||||
|
||||
|
@ -28,11 +30,22 @@ def find_libraries(steam_path: str) -> List[str]:
|
|||
return libraries
|
||||
|
||||
|
||||
# Notes:
|
||||
# Anything older than 'Proton 5.13' doesn't have the 'require_tool_appid' attribute.
|
||||
# Anything older than 'Proton 7.0' doesn't have the 'compatmanager_layer_name' attribute.
|
||||
# In addition to that, the 'Steam Linux Runtime 1.0 (scout)' runtime lists the
|
||||
# 'Steam Linux Runtime 2.0 (soldier)' runtime as a dependency and is probably what was
|
||||
# being used for any version before 5.13.
|
||||
#
|
||||
# As a result the following implementation will list versions from 7.0 onwards which honestly
|
||||
# is a good trade-off for the amount of complexity supporting everything would ensue.
|
||||
|
||||
|
||||
@dataclass
|
||||
class SteamBase:
|
||||
steam_path: str
|
||||
tool_path: str
|
||||
toolmanifest: dict
|
||||
toolmanifest: Dict
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.tool_path == other.tool_path
|
||||
|
@ -40,23 +53,36 @@ class SteamBase:
|
|||
def __hash__(self):
|
||||
return hash(self.tool_path)
|
||||
|
||||
def commandline(self):
|
||||
cmd = "".join([f"'{self.tool_path}'", self.toolmanifest["manifest"]["commandline"]])
|
||||
cmd = os.path.normpath(cmd)
|
||||
@property
|
||||
def required_tool(self) -> Optional[str]:
|
||||
return self.toolmanifest["manifest"].get("require_tool_appid", None)
|
||||
|
||||
def command(self, setup: bool = False) -> List[str]:
|
||||
tool_path = os.path.normpath(self.tool_path)
|
||||
cmd = "".join([shlex.quote(tool_path), self.toolmanifest["manifest"]["commandline"]])
|
||||
# NOTE: "waitforexitandrun" seems to be the verb used in by steam to execute stuff
|
||||
cmd = cmd.replace("%verb%", "waitforexitandrun")
|
||||
return cmd
|
||||
# `run` is used when setting up the environment, so use that if we are setting up the prefix.
|
||||
verb = "run" if setup else "waitforexitandrun"
|
||||
cmd = cmd.replace("%verb%", verb)
|
||||
return shlex.split(cmd)
|
||||
|
||||
@property
|
||||
def checksum(self) -> str:
|
||||
command = " ".join(shlex.quote(part) for part in self.command(setup=False))
|
||||
return md5(command.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
@dataclass
|
||||
class SteamRuntime(SteamBase):
|
||||
steam_library: str
|
||||
appmanifest: dict
|
||||
appmanifest: Dict
|
||||
|
||||
def name(self):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.appmanifest["AppState"]["name"]
|
||||
|
||||
def appid(self):
|
||||
@property
|
||||
def appid(self) -> str:
|
||||
return self.appmanifest["AppState"]["appid"]
|
||||
|
||||
|
||||
|
@ -64,33 +90,36 @@ class SteamRuntime(SteamBase):
|
|||
class ProtonTool(SteamRuntime):
|
||||
runtime: SteamRuntime = None
|
||||
|
||||
def __bool__(self):
|
||||
if appid := self.toolmanifest.get("require_tool_appid", False):
|
||||
return self.runtime is not None and self.runtime.appid() == appid
|
||||
def __bool__(self) -> bool:
|
||||
if appid := self.required_tool:
|
||||
return self.runtime is not None and self.runtime.appid == appid
|
||||
return True
|
||||
|
||||
def commandline(self):
|
||||
runtime_cmd = self.runtime.commandline()
|
||||
cmd = super().commandline()
|
||||
return " ".join([runtime_cmd, cmd])
|
||||
def command(self, setup: bool = False) -> List[str]:
|
||||
cmd = self.runtime.command(setup)
|
||||
cmd.extend(super().command(setup))
|
||||
return cmd
|
||||
|
||||
|
||||
@dataclass
|
||||
class CompatibilityTool(SteamBase):
|
||||
compatibilitytool: dict
|
||||
compatibilitytool: Dict
|
||||
runtime: SteamRuntime = None
|
||||
|
||||
def __bool__(self):
|
||||
if appid := self.toolmanifest.get("require_tool_appid", False):
|
||||
return self.runtime is not None and self.runtime.appid() == appid
|
||||
def __bool__(self) -> bool:
|
||||
if appid := self.required_tool:
|
||||
return self.runtime is not None and self.runtime.appid == appid
|
||||
return True
|
||||
|
||||
def name(self):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
name, data = list(self.compatibilitytool["compatibilitytools"]["compat_tools"].items())[0]
|
||||
return data["display_name"]
|
||||
|
||||
def commandline(self):
|
||||
runtime_cmd = self.runtime.commandline() if self.runtime is not None else ""
|
||||
cmd = super().commandline()
|
||||
return " ".join([runtime_cmd, cmd])
|
||||
def command(self, setup: bool = False) -> List[str]:
|
||||
cmd = self.runtime.command(setup) if self.runtime is not None else []
|
||||
cmd.extend(super().command(setup))
|
||||
return cmd
|
||||
|
||||
|
||||
def find_appmanifests(library: str) -> List[dict]:
|
||||
|
@ -187,16 +216,18 @@ def find_runtimes(steam_path: str, library: str) -> Dict[str, SteamRuntime]:
|
|||
def find_runtime(
|
||||
tool: Union[ProtonTool, CompatibilityTool], runtimes: Dict[str, SteamRuntime]
|
||||
) -> Optional[SteamRuntime]:
|
||||
required_tool = tool.toolmanifest["manifest"].get("require_tool_appid")
|
||||
required_tool = tool.required_tool
|
||||
if required_tool is None:
|
||||
return None
|
||||
return runtimes[required_tool]
|
||||
return runtimes.get(required_tool, None)
|
||||
|
||||
|
||||
def get_steam_environment(tool: Optional[Union[ProtonTool, CompatibilityTool]], app_name: str = None) -> Dict:
|
||||
environ = {}
|
||||
def get_steam_environment(
|
||||
tool: Optional[Union[ProtonTool, CompatibilityTool]] = None, compat_path: Optional[str] = None
|
||||
) -> Dict:
|
||||
# If the tool is unset, return all affected env variable names
|
||||
# IMPORTANT: keep this in sync with the code below
|
||||
environ = {"STEAM_COMPAT_DATA_PATH": compat_path if compat_path else ""}
|
||||
if tool is None:
|
||||
environ["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = ""
|
||||
environ["STEAM_COMPAT_LIBRARY_PATHS"] = ""
|
||||
|
@ -221,7 +252,8 @@ def find_tools() -> List[Union[ProtonTool, CompatibilityTool]]:
|
|||
steam_path = find_steam()
|
||||
logger.debug("Using Steam install in %s", steam_path)
|
||||
steam_libraries = find_libraries(steam_path)
|
||||
logger.debug("Searching for tools in libraries %s", steam_libraries)
|
||||
logger.debug("Searching for tools in libraries:")
|
||||
logger.debug("%s", steam_libraries)
|
||||
|
||||
runtimes = {}
|
||||
for library in steam_libraries:
|
||||
|
@ -236,6 +268,8 @@ def find_tools() -> List[Union[ProtonTool, CompatibilityTool]]:
|
|||
runtime = find_runtime(tool, runtimes)
|
||||
tool.runtime = runtime
|
||||
|
||||
tools = list(filter(lambda t: bool(t), tools))
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
|
@ -247,7 +281,9 @@ if __name__ == "__main__":
|
|||
|
||||
for tool in _tools:
|
||||
print(get_steam_environment(tool))
|
||||
print(tool.name(), tool.commandline())
|
||||
print(tool.name)
|
||||
print(tool.command())
|
||||
print(" ".join(tool.command()))
|
||||
|
||||
|
||||
def find_proton_combos():
|
88
rare/utils/runners/wine.py
Normal file
88
rare/utils/runners/wine.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
import os
|
||||
from dataclasses import dataclass
|
||||
from logging import getLogger
|
||||
from typing import Dict, Tuple, List, Optional
|
||||
|
||||
logger = getLogger("Wine")
|
||||
|
||||
lutris_runtime_paths = [
|
||||
os.path.expanduser("~/.local/share/lutris")
|
||||
]
|
||||
|
||||
__lutris_runtime: str = None
|
||||
__lutris_wine: str = None
|
||||
|
||||
|
||||
def find_lutris() -> Tuple[str, str]:
|
||||
global __lutris_runtime, __lutris_wine
|
||||
for path in lutris_runtime_paths:
|
||||
runtime_path = os.path.join(path, "runtime")
|
||||
wine_path = os.path.join(path, "runners", "wine")
|
||||
if os.path.isdir(path) and os.path.isdir(runtime_path) and os.path.isdir(wine_path):
|
||||
__lutris_runtime, __lutris_wine = runtime_path, wine_path
|
||||
return runtime_path, wine_path
|
||||
|
||||
|
||||
@dataclass
|
||||
class WineRuntime:
|
||||
name: str
|
||||
path: str
|
||||
environ: Dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class WineRunner:
|
||||
name: str
|
||||
path: str
|
||||
environ: Dict
|
||||
runtime: Optional[WineRuntime] = None
|
||||
|
||||
|
||||
def find_lutris_wines(runtime_path: str = None, wine_path: str = None) -> List[WineRunner]:
|
||||
runners = []
|
||||
if not runtime_path and not wine_path:
|
||||
return runners
|
||||
|
||||
|
||||
def __get_lib_path(executable: str, basename: str = "") -> str:
|
||||
path = os.path.dirname(os.path.dirname(executable))
|
||||
lib32 = os.path.realpath(os.path.join(path, "lib32", basename))
|
||||
lib64 = os.path.realpath(os.path.join(path, "lib64", basename))
|
||||
lib = os.path.realpath(os.path.join(path, "lib", basename))
|
||||
if lib32 == lib or not os.path.exists(lib32):
|
||||
ldpath = ":".join([lib64, lib])
|
||||
elif lib64 == lib or not os.path.exists(lib64):
|
||||
ldpath = ":".join([lib, lib32])
|
||||
else:
|
||||
ldpath = lib if os.path.exists(lib) else lib64
|
||||
return ldpath
|
||||
|
||||
|
||||
def get_wine_environment(executable: str = None, prefix: str = None) -> Dict:
|
||||
# If the tool is unset, return all affected env variable names
|
||||
# IMPORTANT: keep this in sync with the code below
|
||||
environ = {"WINEPREFIX": prefix if prefix is not None else ""}
|
||||
if executable is None:
|
||||
environ["WINEDLLPATH"] = ""
|
||||
environ["LD_LIBRARY_PATH"] = ""
|
||||
else:
|
||||
winedllpath = __get_lib_path(executable, "wine")
|
||||
environ["WINEDLLPATH"] = winedllpath
|
||||
librarypath = __get_lib_path(executable, "")
|
||||
environ["LD_LIBRARY_PATH"] = librarypath
|
||||
return environ
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from pprint import pprint
|
||||
|
||||
pprint(get_wine_environment(
|
||||
"/opt/wine-ge-custom/bin/wine", None))
|
||||
pprint(get_wine_environment(
|
||||
"/usr/bin/wine", None))
|
||||
pprint(get_wine_environment(
|
||||
"/usr/share/steam/compatitiblitytools.d/dist/bin/wine", None))
|
||||
pprint(get_wine_environment(
|
||||
os.path.expanduser("~/.local/share/Steam/compatibilitytools.d/GE-Proton8-14/files/bin/wine"), None))
|
||||
pprint(get_wine_environment(
|
||||
os.path.expanduser("~/.local/share/lutris/runners/wine/lutris-GE-Proton8-14-x86_64/bin/wine"), None))
|
|
@ -1,6 +1,7 @@
|
|||
import difflib
|
||||
import os
|
||||
from datetime import datetime
|
||||
from enum import StrEnum, Enum
|
||||
from typing import Tuple
|
||||
|
||||
import orjson
|
||||
|
@ -18,6 +19,31 @@ __grades_json = None
|
|||
__active_download = False
|
||||
|
||||
|
||||
class ProtondbRatings(int, Enum):
|
||||
# internal
|
||||
PENDING = ("pending", -2)
|
||||
FAIL = ("fail", -1)
|
||||
# protondb
|
||||
NA = ("na", 0)
|
||||
BORKED = ("borked", 1)
|
||||
BRONZE = ("bronze", 2)
|
||||
SILVER = ("silver", 3)
|
||||
GOLD = ("gold", 4)
|
||||
PLATINUM = ("platinum", 5)
|
||||
|
||||
def __new__(cls, name: str, value: int):
|
||||
obj = int.__new__(cls, value)
|
||||
obj._value_ = value
|
||||
obj._name_ = name
|
||||
return obj
|
||||
|
||||
def __str__(self):
|
||||
return self._name_
|
||||
|
||||
def __int__(self):
|
||||
return self._value_
|
||||
|
||||
|
||||
def get_rating(core: LegendaryCore, app_name: str) -> Tuple[int, str]:
|
||||
game = core.get_game(app_name)
|
||||
try:
|
||||
|
@ -31,7 +57,7 @@ def get_rating(core: LegendaryCore, app_name: str) -> Tuple[int, str]:
|
|||
return steam_id, grade
|
||||
|
||||
|
||||
# you should iniciate the module with the game's steam code
|
||||
# you should initiate the module with the game's steam code
|
||||
def get_grade(steam_code):
|
||||
if steam_code == 0:
|
||||
return "fail"
|
||||
|
|
Loading…
Reference in a new issue