diff --git a/rare/commands/launcher/lgd_helper.py b/rare/commands/launcher/lgd_helper.py index 48ee17888e..595800a6be 100644 --- a/rare/commands/launcher/lgd_helper.py +++ b/rare/commands/launcher/lgd_helper.py @@ -11,6 +11,7 @@ from PySide6.QtCore import QProcess, QProcessEnvironment from rare.models.game_slim import RareGameSlim +from rare.utils.compat.utils import create_compat_users from rare.utils.paths import setup_compat_shaders_dir logger = getLogger("RareLauncherUtils") @@ -155,21 +156,21 @@ def prepare_process(command: List[str], environment: Dict) -> Tuple[str, List[st environ = environment.copy() # Sanity check environment (mostly for Linux) - command_line = shlex.join(command) - if os.environ.get("XDG_CURRENT_DESKTOP", None) == "gamescope" or "gamescope" in command_line: - # disable mangohud in gamescope - environ["MANGOHUD"] = "0" # ensure shader compat dirs exist if platform.system() in {"Linux", "FreeBSD"}: - environ["UMU_USE_STEAM"] = "1" + command_line = shlex.join(command) + if os.environ.get("XDG_CURRENT_DESKTOP", None) == "gamescope" or "gamescope" in command_line: + # disable mangohud in gamescope + environ["MANGOHUD"] = "0" if "STEAM_COMPAT_CLIENT_INSTALL_PATH" not in environ: environ["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = "" - if "WINEPREFIX" in environ and not os.path.isdir(environ["WINEPREFIX"]): - os.makedirs(environ["WINEPREFIX"], exist_ok=True) if "STEAM_COMPAT_DATA_PATH" in environ: compat_pfx = os.path.join(environ["STEAM_COMPAT_DATA_PATH"], "pfx") - if not os.path.isdir(compat_pfx): - os.makedirs(compat_pfx, exist_ok=True) + os.makedirs(compat_pfx, exist_ok=True) + create_compat_users(compat_pfx) + if "WINEPREFIX" in environ and not os.path.isdir(environ["WINEPREFIX"]): + os.makedirs(environ["WINEPREFIX"], exist_ok=True) + create_compat_users(environ["WINEPREFIX"]) if "STEAM_COMPAT_SHADER_PATH" in environ: environ.update(setup_compat_shaders_dir(environ["STEAM_COMPAT_SHADER_PATH"])) environ["WINEDLLOVERRIDES"] = environ.get("WINEDLLOVERRIDES", "") + ";lsteamclient=d;" diff --git a/rare/components/__init__.py b/rare/components/__init__.py index e34f31cc70..9c460997c3 100644 --- a/rare/components/__init__.py +++ b/rare/components/__init__.py @@ -73,22 +73,22 @@ def relogin(self): @Slot() def launch_app(self): self.launch_dialog = LaunchDialog(self.rcore, parent=None) - self.launch_dialog.rejected.connect(self.__on_exit_app) + self.launch_dialog.rejected.connect(self._on_exit_app) # lk: the reason we use the `start_app` signal here instead of accepted, is to keep the dialog # until the main window has been created, then we call `accept()` to close the dialog - self.launch_dialog.start_app.connect(self.__on_start_app) + self.launch_dialog.start_app.connect(self._on_start_app) self.launch_dialog.start_app.connect(self.launch_dialog.accept) self.launch_dialog.login() @Slot() - def __on_start_app(self): + def _on_start_app(self): self.relogin_timer = QTimer(self) self.relogin_timer.setTimerType(Qt.TimerType.VeryCoarseTimer) self.relogin_timer.timeout.connect(self.relogin) self.poke_timer() self.main_window = RareWindow(self.settings, self.rcore) - self.main_window.exit_app.connect(self.__on_exit_app) + self.main_window.exit_app.connect(self._on_exit_app) if (not self.args.silent) and (not self.settings.get_value(app_settings.sys_tray_start)): self.main_window.show() @@ -96,11 +96,11 @@ def __on_start_app(self): if self.args.test_start: self.main_window.close() self.main_window = None - self.__on_exit_app(0) + self._on_exit_app(0) @Slot() @Slot(int) - def __on_exit_app(self, exit_code=0): + def _on_exit_app(self, exit_code=0): threadpool = QThreadPool.globalInstance() threadpool.waitForDone() if self.relogin_timer is not None: @@ -118,7 +118,7 @@ def __on_exit_app(self, exit_code=0): self.exit(exit_code) -def start(args) -> int: +def start(args: Namespace) -> int: while True: QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True) QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True) diff --git a/rare/components/dialogs/install/dialog.py b/rare/components/dialogs/install/dialog.py index 592b5c1684..70d261760a 100644 --- a/rare/components/dialogs/install/dialog.py +++ b/rare/components/dialogs/install/dialog.py @@ -52,9 +52,9 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal self.core = rgame.core self.rgame = rgame - self.__options: InstallOptionsModel = options - self.__download: Optional[InstallDownloadModel] = None - self.__queue_item: Optional[InstallQueueItemModel] = None + self._options: InstallOptionsModel = options + self._download: Optional[InstallDownloadModel] = None + self._queue_item: Optional[InstallQueueItemModel] = None self.selectable = InstallDialogSelective(rgame, parent=self) self.selectable.stateChanged.connect(self._on_option_changed) @@ -86,11 +86,11 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal self.install_dir_edit = PathEdit( path=options.base_path, file_mode=QFileDialog.FileMode.Directory, - edit_func=self.__install_dir_edit_callback, - save_func=self.__install_dir_save_callback, + edit_func=self._install_dir_edit_callback, + save_func=self._install_dir_save_callback, parent=self, ) - self.install_dir_edit.validationFinished.connect(self.__on_install_dir_validation) + self.install_dir_edit.validationFinished.connect(self._on_install_dir_validation) self.ui.main_layout.setWidget( self.ui.main_layout.getWidgetPosition(self.ui.install_dir_label)[0], QFormLayout.ItemRole.FieldRole, @@ -145,13 +145,9 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal self.advanced.ui.download_only_check.setChecked(options.no_install) self.advanced.ui.download_only_check.checkStateChanged.connect(self._on_option_changed_no_reload) - self.reset_install_dir(self.ui.platform_combo.currentIndex()) - self.selectable.update_list(self.ui.platform_combo.currentText()) - self.check_incompatible_platform(self.ui.platform_combo.currentIndex()) - self.accept_button.setEnabled(False) - if self.__options.overlay: + if self._options.overlay: self.ui.platform_label.setEnabled(False) self.ui.platform_combo.setEnabled(False) self.advanced.ui.ignore_space_label.setEnabled(False) @@ -171,7 +167,7 @@ def __init__(self, settings: RareAppSettings, rgame: "RareGame", options: Instal self.advanced.ui.install_prereqs_label.setEnabled(False) self.advanced.ui.install_prereqs_check.setEnabled(False) self.advanced.ui.install_prereqs_check.checkStateChanged.connect(self._on_option_changed_no_reload) - self.advanced.ui.install_prereqs_check.setChecked(self.__options.install_prereqs) + self.advanced.ui.install_prereqs_check.setChecked(self._options.install_prereqs) # lk: set object names for CSS properties self.accept_button.setText(header) @@ -190,7 +186,11 @@ def showEvent(self, a0: QShowEvent) -> None: return super().showEvent(a0) def execute(self): - if self.__options.silent: + self.reset_install_dir(self.ui.platform_combo.currentIndex()) + self.check_incompatible_platform(self.ui.platform_combo.currentIndex()) + self.selectable.update_list(self.ui.platform_combo.currentText()) + + if self._options.silent: self.get_download_info() else: self.action_handler() @@ -215,31 +215,31 @@ def check_incompatible_platform(self, index: int): self.set_error_labels() def get_options(self): - base_path = os.path.join(self.install_dir_edit.text(), ".overlay" if self.__options.overlay else "") + base_path = os.path.join(self.install_dir_edit.text(), ".overlay" if self._options.overlay else "") # TODO: investigate if this check is needed if self.rgame.is_installed or self.rgame.is_dlc: - self.__options.base_path = "" + self._options.base_path = "" else: - self.__options.base_path = base_path - self.__options.platform = self.ui.platform_combo.currentText() - self.__options.create_shortcut = self.ui.shortcut_check.isChecked() - self.__options.max_workers = self.advanced.ui.max_workers_spin.value() - self.__options.shared_memory = self.advanced.ui.max_memory_spin.value() - self.__options.read_files = self.advanced.ui.read_files_check.isChecked() - self.__options.always_use_signed_urls = self.advanced.ui.use_signed_urls_check.isChecked() - self.__options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked() - self.__options.force = self.advanced.ui.force_download_check.isChecked() - self.__options.ignore_space = self.advanced.ui.ignore_space_check.isChecked() - self.__options.no_install = self.advanced.ui.download_only_check.isChecked() - self.__options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked() - self.__options.install_tag = self.selectable.install_tags() - self.__options.reset_sdl = True + self._options.base_path = base_path + self._options.platform = self.ui.platform_combo.currentText() + self._options.create_shortcut = self.ui.shortcut_check.isChecked() + self._options.max_workers = self.advanced.ui.max_workers_spin.value() + self._options.shared_memory = self.advanced.ui.max_memory_spin.value() + self._options.read_files = self.advanced.ui.read_files_check.isChecked() + self._options.always_use_signed_urls = self.advanced.ui.use_signed_urls_check.isChecked() + self._options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked() + self._options.force = self.advanced.ui.force_download_check.isChecked() + self._options.ignore_space = self.advanced.ui.ignore_space_check.isChecked() + self._options.no_install = self.advanced.ui.download_only_check.isChecked() + self._options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked() + self._options.install_tag = self.selectable.enabled_tags() + self._options.reset_sdl = True def get_download_info(self): - self.__download = None - info_worker = InstallInfoWorker(self.core, self.__options) - info_worker.signals.result.connect(self.__on_worker_result) - info_worker.signals.failed.connect(self.__on_worker_failed) + self._download = None + info_worker = InstallInfoWorker(self.core, self._options) + info_worker.signals.result.connect(self._on_worker_result) + info_worker.signals.failed.connect(self._on_worker_failed) self.threadpool.start(info_worker) def action_handler(self): @@ -260,15 +260,15 @@ def _on_option_changed(self): @Slot(Qt.CheckState) def _on_option_changed_no_reload(self, state: Qt.CheckState): if self.sender() is self.advanced.ui.download_only_check: - self.__options.no_install = state != Qt.CheckState.Unchecked + self._options.no_install = state != Qt.CheckState.Unchecked elif self.sender() is self.ui.shortcut_check: self.settings.set_value(app_settings.create_shortcut, state != Qt.CheckState.Unchecked) - self.__options.create_shortcut = state != Qt.CheckState.Unchecked + self._options.create_shortcut = state != Qt.CheckState.Unchecked elif self.sender() is self.advanced.ui.install_prereqs_check: - self.__options.install_prereqs = state != Qt.CheckState.Unchecked + self._options.install_prereqs = state != Qt.CheckState.Unchecked @staticmethod - def __install_dir_edit_callback(path: str) -> Tuple[bool, str, int]: + def _install_dir_edit_callback(path: str) -> Tuple[bool, str, int]: if not path: return False, path, IndicatorReasonsCommon.IS_EMPTY try: @@ -281,14 +281,14 @@ def __install_dir_edit_callback(path: str) -> Tuple[bool, str, int]: return False, path, IndicatorReasonsCommon.DIR_NOT_EXISTS return True, path, IndicatorReasonsCommon.VALID - def __install_dir_save_callback(self, path: str): + def _install_dir_save_callback(self, path: str): if not os.path.exists(path): return _, _, free_space = shutil.disk_usage(path) self.ui.available_space_text.setText(format_size(free_space)) @Slot(bool, str) - def __on_install_dir_validation(self, is_valid: bool, reason: str): + def _on_install_dir_validation(self, is_valid: bool, reason: str): self.accept_button.setEnabled(False) self.action_button.setEnabled(is_valid and not self.active()) if not is_valid: @@ -307,9 +307,9 @@ def same_platform(download: InstallDownloadModel) -> bool: return False @Slot(InstallDownloadModel) - def __on_worker_result(self, download: InstallDownloadModel): + def _on_worker_result(self, download: InstallDownloadModel): self.setActive(False) - self.__download = download + self._download = download download_size = download.analysis.dl_size install_size = download.analysis.install_size # install_size = self.dl_item.download.analysis.disk_space_delta @@ -330,28 +330,30 @@ def __on_worker_result(self, download: InstallDownloadModel): self.advanced.ui.install_prereqs_check.setEnabled(has_prereqs) self.advanced.ui.install_prereqs_check.setChecked(has_prereqs and self.same_platform(download)) - # new_manifest_data, _, _ = self.core.get_cdn_manifest(download.game, download.igame.platform, self.__options.disable_https) + # TODO: there is information about the files to be downloaded in analres, don't fetch the manifest again + # TODO: see if you can re-use the one from selective downloads + # new_manifest_data, _, _ = self.core.get_cdn_manifest(download.game, download.igame.platform, self._options.disable_https) # new_manifest = self.core.load_manifest(new_manifest_data) # self.file_filters.clear() # for e in new_manifest.file_manifest_list.elements: # self.file_filters.add_item(e.filename.lower()) - if self.__options.silent: + if self._options.silent: self.accept() @Slot(str) - def __on_worker_failed(self, message: str): + def _on_worker_failed(self, message: str): self.setActive(False) error_text = self.tr("Error") self.set_size_labels(error_text, error_text) self.set_error_labels(error_text, message) self.action_button.setEnabled(self.options_changed) self.accept_button.setEnabled(False) - if self.__options.silent: + if self._options.silent: self.open() @staticmethod - def __set_size_label(label: QLabel, value: Union[int, float, str]): + def _set_size_label(label: QLabel, value: Union[int, float, str]): is_numeric = isinstance(value, (int, float)) font = label.font() font.setBold(is_numeric) @@ -361,8 +363,8 @@ def __set_size_label(label: QLabel, value: Union[int, float, str]): label.setText(text) def set_size_labels(self, download: Union[int, float, str], install: Union[int, float, str]): - self.__set_size_label(self.ui.download_size_text, download) - self.__set_size_label(self.ui.install_size_text, install) + self._set_size_label(self.ui.download_size_text, download) + self._set_size_label(self.ui.install_size_text, install) def set_error_labels(self, label: str = "", message: str = ""): self.ui.warning_label.setVisible(bool(label)) @@ -373,11 +375,11 @@ def set_error_labels(self, label: str = "", message: str = ""): def done_handler(self): self.threadpool.clear() self.threadpool.waitForDone() - self.result_ready.emit(self.__queue_item) + self.result_ready.emit(self._queue_item) - # lk: __download is already set at this point so just do nothing. + # lk: _download is already set at this point so just do nothing. def accept_handler(self): - self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download) + self._queue_item = InstallQueueItemModel(options=self._options, download=self._download) def reject_handler(self): - self.__queue_item = InstallQueueItemModel(options=self.__options, download=None) + self._queue_item = InstallQueueItemModel(options=self._options, download=None) diff --git a/rare/components/dialogs/install/selective.py b/rare/components/dialogs/install/selective.py index cf79b4e4f0..4f016f11c1 100644 --- a/rare/components/dialogs/install/selective.py +++ b/rare/components/dialogs/install/selective.py @@ -26,40 +26,44 @@ class SelectiveWidget(QWidget): def __init__(self, rgame: RareGame, platform: str, parent=None): super().__init__(parent=parent) + self._has_tags = False main_layout = QVBoxLayout(self) - main_layout.setSpacing(0) + main_layout.setSpacing(2) core = rgame.core config_tags = core.lgd.config.get(rgame.app_name, "install_tags", fallback=None) config_disable_sdl = core.lgd.config.getboolean(rgame.app_name, "disable_sdl", fallback=False) - sdl_name = get_sdl_appname(rgame.app_name) - if not config_disable_sdl and sdl_name is not None: - sdl_data = core.get_sdl_data(sdl_name, platform=platform) - if sdl_data: - for tag, info in sdl_data.items(): - cb = InstallTagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"]) - if tag == "__required": + + sdl_data = rgame.sdl_data(platform) + if not config_disable_sdl and sdl_data: + for group, info in sdl_data.items(): + cb = InstallTagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"]) + if group == "__required": + cb.setChecked(True) + cb.setDisabled(True) + if config_tags is not None: + if all(tag in config_tags for tag in info["tags"]): cb.setChecked(True) - cb.setDisabled(True) - if config_tags is not None: - if all(elem in config_tags for elem in info["tags"]): - cb.setChecked(True) - cb.stateChanged.connect(self.stateChanged) - main_layout.addWidget(cb) - self.parent().setDisabled(False) + cb.stateChanged.connect(self.stateChanged) + main_layout.addWidget(cb) + self._has_tags = True else: - self.parent().setDisabled(True) + self._has_tags = False - def install_tags(self): - install_tags = [""] + def enabled_tags(self) -> List[str]: + install_tags = set() for cb in self.findChildren(InstallTagCheckBox, options=Qt.FindChildOption.FindDirectChildrenOnly): if data := cb.isChecked(): # noinspection PyTypeChecker - install_tags.extend(data) + install_tags.update(data) + install_tags = ["", *install_tags] return install_tags + def supports_tags(self) -> bool: + return self._has_tags + class InstallDialogSelective(CollapsibleFrame): stateChanged: Signal = Signal() @@ -68,7 +72,7 @@ def __init__(self, rgame: RareGame, parent=None): super(InstallDialogSelective, self).__init__(parent=parent) title = self.tr("Optional downloads") self.setTitle(title) - self.setEnabled(bool(rgame.sdl_name)) + self.setEnabled(False) self.widget: SelectiveWidget = None self.rgame = rgame @@ -78,11 +82,12 @@ def update_list(self, platform: str): self.widget.disconnect(self.widget) self.widget.deleteLater() self.widget = SelectiveWidget(self.rgame, platform, parent=self) + self.setEnabled(self.widget.supports_tags()) self.widget.stateChanged.connect(self.stateChanged) self.setWidget(self.widget) - def install_tags(self): - return self.widget.install_tags() + def enabled_tags(self) -> List[str]: + return self.widget.enabled_tags() __all__ = ["InstallDialogSelective", "SelectiveWidget"] diff --git a/rare/components/tabs/downloads/thread.py b/rare/components/tabs/downloads/thread.py index 3a4386c40b..acaaae4c88 100644 --- a/rare/components/tabs/downloads/thread.py +++ b/rare/components/tabs/downloads/thread.py @@ -190,6 +190,7 @@ def chunk_url_sign_thread(): result.shortcut = not self.item.options.update and self.item.options.create_shortcut result.folder_name = self.rgame.folder_name + return finally: ticket_thread.stop = True sign_thread.stop = True diff --git a/rare/components/tabs/library/details/cloud_saves.py b/rare/components/tabs/library/details/cloud_saves.py index 2dfe6d326a..2fa3431490 100644 --- a/rare/components/tabs/library/details/cloud_saves.py +++ b/rare/components/tabs/library/details/cloud_saves.py @@ -68,7 +68,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None): self.cloud_save_path_edit = PathEdit( path="", file_mode=QFileDialog.FileMode.Directory, - placeholder=self.tr('Use "Calculate path" or "Browse" ...'), + placeholder=self.tr('Use "Resolve path" or "Browse" ...'), edit_func=self.edit_save_path, save_func=self.save_save_path, ) diff --git a/rare/components/tabs/library/details/details.py b/rare/components/tabs/library/details/details.py index 1626e9a120..7d28108ec6 100644 --- a/rare/components/tabs/library/details/details.py +++ b/rare/components/tabs/library/details/details.py @@ -184,7 +184,7 @@ def __on_verify(self): self.tr("Installation path for {} does not exist. Cannot continue.").format(self.rgame.app_title), ) return - if self.rgame.sdl_name is not None: + if self.rgame.sdl_available: selective_dialog = SelectiveDialog(self.rgame, parent=self) selective_dialog.result_ready.connect(self.verify_game) selective_dialog.open() @@ -362,7 +362,7 @@ def __update_widget(self): self.ui.import_button.setEnabled((not self.rgame.is_installed or self.rgame.is_non_asset) and self.rgame.is_idle) self.ui.modify_button.setEnabled( - self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle and self.rgame.sdl_name is not None + self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle and self.rgame.sdl_available ) self.ui.verify_button.setEnabled(self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle) diff --git a/rare/components/tabs/settings/widgets/overlay.py b/rare/components/tabs/settings/widgets/overlay.py index b8e87b0a3b..dc68dd49f7 100644 --- a/rare/components/tabs/settings/widgets/overlay.py +++ b/rare/components/tabs/settings/widgets/overlay.py @@ -151,9 +151,9 @@ def __init__(self, parent=None): # self.values: Dict[str, Union[OverlayLineEdit, OverlayComboBox]] = {} self.ui.options_group.setTitle(self.tr("Custom options")) - self.ui.overlay_state_combo.currentIndexChanged.connect(self.__update_settings) + self.ui.overlay_state_combo.currentIndexChanged.connect(self._update_settings) - self.environ_changed.connect(self.__update_current_value) + self.environ_changed.connect(self._update_current_value) def setupWidget( self, @@ -174,26 +174,26 @@ def setupWidget( self.ui.overlay_state_label.setText(label) - for i, widget in enumerate(grid_map): + for idx, widget in enumerate(grid_map): widget.setParent(self.ui.options_group) - self.ui.options_grid.addWidget(widget, i // 4, i % 4) + self.ui.options_grid.addWidget(widget, idx // 4, idx % 4) # self.checkboxes[widget.option] = widget self.option_widgets.append(widget) - widget.stateChanged.connect(self.__update_settings) + widget.stateChanged.connect(self._update_settings) for widget, label in form_map: widget.setParent(self.ui.options_group) self.ui.options_form.addRow(label, widget) # self.values[widget.option] = widget self.option_widgets.append(widget) - widget.valueChanged.connect(self.__update_settings) + widget.valueChanged.connect(self._update_settings) @abstractmethod def update_settings_override(self, state: ActivationStates): raise NotImplementedError @Slot() - def __update_settings(self): + def _update_settings(self): current_state = self.ui.overlay_state_combo.currentData(Qt.ItemDataRole.UserRole) self.ui.options_group.setEnabled(current_state == ActivationStates.CUSTOM) @@ -212,14 +212,13 @@ def __update_settings(self): self.ui.options_group.setDisabled(False) # custom options options = (name for widget in self.option_widgets if (name := widget.getValue()) is not None) - config.set_envvar(self.app_name, self.config_key, self.separator.join(options)) self.environ_changed.emit(self.config_key) self.update_settings_override(current_state) @Slot() - def __update_current_value(self): + def _update_current_value(self): self.ui.current_value_info.setText(config.get_envvar_with_global(self.app_name, self.config_key, "")) def setCurrentState(self, state: ActivationStates): @@ -262,7 +261,7 @@ def showEvent(self, a0: QShowEvent): ) self.ui.options_group.blockSignals(False) - self.__update_current_value() + self._update_current_value() return super().showEvent(a0) diff --git a/rare/components/tabs/settings/widgets/proton.py b/rare/components/tabs/settings/widgets/proton.py index 0ba250f098..4e73a75e43 100644 --- a/rare/components/tabs/settings/widgets/proton.py +++ b/rare/components/tabs/settings/widgets/proton.py @@ -16,7 +16,7 @@ from rare.models.wrapper import Wrapper, WrapperType from rare.shared import RareCore from rare.shared.wrappers import Wrappers -from rare.utils import config_helper as lgd_conf +from rare.utils import config_helper as lgd_config from rare.utils.compat import steam from rare.utils.paths import proton_compat_dir from rare.widgets.indicator_edit import IndicatorReasonsCommon, PathEdit @@ -49,18 +49,18 @@ def __init__(self, rcore: RareCore, parent=None): self.setTitle(self.tr("Proton")) self.tool_combo = QComboBox(self) - self.tool_combo.currentIndexChanged.connect(self.__on_tool_changed) + self.tool_combo.currentIndexChanged.connect(self._on_tool_changed) self.compat_combo = QComboBox(self) self.compat_combo.addItem(self.tr("Shared"), ProtonSettings.CompatLocation.SHARED) self.compat_combo.addItem(self.tr("Isolated"), ProtonSettings.CompatLocation.ISOLATED) self.compat_combo.addItem(self.tr("Custom"), ProtonSettings.CompatLocation.CUSTOM) - self.compat_combo.currentIndexChanged.connect(self.__on_compat_changed) + self.compat_combo.currentIndexChanged.connect(self._on_compat_changed) self.compat_edit = PathEdit( file_mode=QFileDialog.FileMode.Directory, - edit_func=self.proton_prefix_edit, - save_func=self.proton_prefix_save, + edit_func=self._proton_prefix_edit, + save_func=self._proton_prefix_save, placeholder=self.tr("Please select path for proton prefix"), parent=self, ) @@ -127,7 +127,7 @@ def showEvent(self, a0: QShowEvent) -> None: self.tool_combo.blockSignals(False) enabled = bool(self.tool_combo.currentData(Qt.ItemDataRole.UserRole)) - compat_path = lgd_conf.get_compat_data_path(self.app_name, fallback="") + compat_path = lgd_config.get_compat_data_path(self.app_name, fallback="") self.compat_combo.blockSignals(True) compat_location = self._update_compat_folder(compat_path) @@ -142,7 +142,7 @@ def showEvent(self, a0: QShowEvent) -> None: return super().showEvent(a0) @Slot(int) - def __on_tool_changed(self, index): + def _on_tool_changed(self, index): steam_tool: Union[steam.ProtonTool, steam.CompatibilityTool] = self.tool_combo.itemData(index, Qt.ItemDataRole.UserRole) steam_environ = steam.get_steam_environment(steam_tool, self.compat_edit.text()) @@ -156,7 +156,7 @@ def __on_tool_changed(self, index): steam_environ["STEAM_COMPAT_INSTALL_PATH"] = install_path steam_environ["STEAM_COMPAT_LIBRARY_PATHS"] = library_paths for key, value in steam_environ.items(): - lgd_conf.adjust_envvar(self.app_name, key, value) + lgd_config.adjust_envvar(self.app_name, key, value) self.environ_changed.emit(key) wrappers = self.wrappers.get_wrappers(self.app_name) @@ -178,7 +178,7 @@ def __on_tool_changed(self, index): self.compat_edit.setEnabled(steam_tool is not None) compat_path = "" if steam_tool: - compat_path = lgd_conf.get_compat_data_path(self.app_name, fallback="") + compat_path = lgd_config.get_compat_data_path(self.app_name, fallback="") if not compat_path: compat_path = str(self._get_compat_path(ProtonSettings.CompatLocation.NONE)) self._update_compat_folder(compat_path) @@ -186,10 +186,10 @@ def __on_tool_changed(self, index): self.compat_tool_enabled.emit(steam_tool is not None, compat_path) @Slot(int) - def __on_compat_changed(self, index): + def _on_compat_changed(self, index): compat_location: ProtonSettings.CompatLocation = self.compat_combo.itemData(index, Qt.ItemDataRole.UserRole) compat_path = str(self._get_compat_path(compat_location)) - lgd_conf.adjust_compat_data_path(self.app_name, compat_path) + lgd_config.adjust_compat_data_path(self.app_name, compat_path) self.compat_edit.setText(compat_path) self.compat_edit.setEnabled( compat_location @@ -200,17 +200,23 @@ def __on_compat_changed(self, index): ) @staticmethod - def proton_prefix_edit(text: str) -> Tuple[bool, str, int]: + def _proton_prefix_edit(text: str) -> Tuple[bool, str, int]: if not text: return False, text, IndicatorReasonsCommon.IS_EMPTY if os.path.isdir(text): - if os.listdir(text) and not os.path.exists(os.path.join(text, "pfx")): - return False, text, IndicatorReasonsCommon.DIR_NOT_EMPTY - return True, text, IndicatorReasonsCommon.VALID + dir_list = os.listdir(text) + if not dir_list: + return True, text, IndicatorReasonsCommon.VALID + if any( + (x in dir_list) for x in + ("pfx", "shadercache", "dosdevices", "drive_c", "system.reg", "user.reg", "userdef.reg") + ): + return True, text, IndicatorReasonsCommon.VALID + return False, text, IndicatorReasonsCommon.DIR_NOT_EMPTY else: return False, text, IndicatorReasonsCommon.DIR_NOT_EXISTS - def proton_prefix_save(self, text: str): - lgd_conf.adjust_compat_data_path(self.app_name, text) + def _proton_prefix_save(self, text: str): + lgd_config.adjust_compat_data_path(self.app_name, text) self.environ_changed.emit("STEAM_COMPAT_DATA_PATH") self.compat_path_changed.emit(text) diff --git a/rare/components/tabs/settings/widgets/runner.py b/rare/components/tabs/settings/widgets/runner.py index 7ebe18cff1..3f2c50b575 100644 --- a/rare/components/tabs/settings/widgets/runner.py +++ b/rare/components/tabs/settings/widgets/runner.py @@ -1,11 +1,15 @@ +import os.path import platform as pf +from getpass import getuser from typing import Type, TypeVar -from PySide6.QtCore import Qt, Signal, Slot -from PySide6.QtWidgets import QCheckBox, QFormLayout, QGroupBox, QVBoxLayout +from PySide6.QtCore import Qt, QUrl, Signal, Slot +from PySide6.QtGui import QDesktopServices +from PySide6.QtWidgets import QCheckBox, QFormLayout, QGroupBox, QHBoxLayout, QPushButton, QVBoxLayout from rare.models.settings import RareAppSettings, app_settings from rare.shared import RareCore +from rare.utils import config_helper as lgd_config from .wine import WineSettings @@ -33,20 +37,11 @@ def __init__( self.setTitle(self.tr("Compatibility")) - # self.compat_label = QLabel(self.tr("Runner")) - # self.compat_combo = QComboBox(self) - # self.compat_stack = QStackedWidget(self) - self.main_layout = QVBoxLayout(self) self.wine = wine_widget(settings, rcore, self) self.wine.environ_changed.connect(self.environ_changed) self.main_layout.addWidget(self.wine) - # self.compat_layout = QFormLayout(self.compat) - # self.compat_layout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.compat_label) - # self.compat_layout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.compat_combo) - # self.compat_layout.setWidget(1, QFormLayout.ItemRole.SpanningRole, self.compat_stack) - # self.compat_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.FieldsStayAtSizeHint) self.ctool = False if proton_widget is not None: @@ -55,7 +50,18 @@ def __init__( self.ctool.compat_tool_enabled.connect(self.wine.compat_tool_enabled) self.ctool.compat_path_changed.connect(self.wine.compat_path_changed) self.main_layout.addWidget(self.ctool) - # self.ctool.compat_tool_enabled.connect(self.compat_tool_enabled) + + self.pfx_folder_button = QPushButton(self.tr("Open prefix folder"), self) + self.pfx_folder_button.clicked.connect(self._open_pfx_folder) + self.usr_folder_button = QPushButton(self.tr("Open user folder"), self) + self.usr_folder_button.clicked.connect(self._open_usr_folder) + self.winetricks_button = QPushButton(self.tr("Run winetricks"), self) + + self.button_layout = QHBoxLayout() + self.button_layout.addWidget(self.pfx_folder_button) + self.button_layout.addWidget(self.usr_folder_button) + self.button_layout.addWidget(self.winetricks_button) + self.button_layout.setAlignment(Qt.AlignmentFlag.AlignRight) font = self.font() font.setItalic(True) @@ -64,12 +70,8 @@ def __init__( self.shader_cache_check.setChecked(self.settings.get_value(app_settings.local_shader_cache)) self.shader_cache_check.checkStateChanged.connect(self._shader_cache_check_changed) - # wine_index = self.compat_stack.addWidget(self.wine) - # self.compat_combo.addItem("Wine", wine_index) - # proton_index = self.compat_stack.addWidget(self.proton_tool) - # self.compat_combo.addItem("Proton", proton_index) - self.form_layout = QFormLayout() + self.form_layout.addRow(self.tr(""), self.button_layout) self.form_layout.addRow(self.tr("Shader cache"), self.shader_cache_check) self.form_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) self.form_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) @@ -80,5 +82,26 @@ def __init__( def _shader_cache_check_changed(self, state: Qt.CheckState): self.settings.set_value(app_settings.local_shader_cache, state != Qt.CheckState.Unchecked) + @Slot() + def _open_pfx_folder(self): + QDesktopServices.openUrl( + QUrl.fromLocalFile(lgd_config.get_wine_prefix_with_global(self.app_name)) + ) + + @Slot() + def _open_usr_folder(self): + path = os.path.join( + lgd_config.get_wine_prefix_with_global(self.app_name), "drive_c", "users", getuser() + ) + if not os.path.exists(path): + path = os.path.join( + lgd_config.get_wine_prefix_with_global(self.app_name), "drive_c", "users", "steamuser" + ) + QDesktopServices.openUrl(path) + + @Slot() + def _run_winetricks(self): + pass + RunnerSettingsType = TypeVar("RunnerSettingsType", bound=RunnerSettingsBase) diff --git a/rare/components/tabs/settings/widgets/wine.py b/rare/components/tabs/settings/widgets/wine.py index 8fcb2c022b..8b9077503c 100644 --- a/rare/components/tabs/settings/widgets/wine.py +++ b/rare/components/tabs/settings/widgets/wine.py @@ -8,7 +8,7 @@ from rare.models.settings import RareAppSettings from rare.shared import RareCore -from rare.utils import config_helper as lgd_conf +from rare.utils import config_helper as lgd_config from rare.widgets.indicator_edit import IndicatorReasonsCommon, PathEdit logger = getLogger("WineSettings") @@ -54,7 +54,7 @@ def __init__(self, settings: RareAppSettings, rcore: RareCore, parent=None): layout = QFormLayout(self) layout.addRow(self.tr("Executable"), self.wine_execut_edit) - layout.addRow(self.tr("Prefix"), self.wine_prefix_edit) + layout.addRow(self.tr("Prefix folder"), self.wine_prefix_edit) layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow) layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) layout.setFormAlignment(Qt.AlignmentFlag.AlignLeading | Qt.AlignmentFlag.AlignVCenter) @@ -64,7 +64,7 @@ def __update_widget(self): self.wine_prefix_edit.setText(self.load_prefix()) _ = QSignalBlocker(self.wine_execut_edit) self.wine_execut_edit.setText(self.load_execut()) - self.setDisabled(lgd_conf.get_boolean(self.app_name, "no_wine", fallback=False)) + self.setDisabled(lgd_config.get_boolean(self.app_name, "no_wine", fallback=False)) def showEvent(self, a0: QShowEvent): if a0.spontaneous(): @@ -74,47 +74,48 @@ def showEvent(self, a0: QShowEvent): @Slot(str) def compat_path_changed(self, text: str): - self.wine_prefix_edit.setText(os.path.join(text, "pfx") if text else text) + path = os.path.join(text, "pfx") if text else text + self.wine_prefix_edit.setText(path) @Slot(bool) def compat_tool_enabled(self, enabled: bool, path: str): old_wine_execut = self.settings.value(f"{self.app_name}/wine_execut", defaultValue=None) old_wine_prefix = self.settings.value(f"{self.app_name}/wine_prefix", defaultValue=None) if enabled: - wine_execut = lgd_conf.get_option(self.app_name, "wine_executable", "") - wine_prefix = lgd_conf.get_wine_prefix(self.app_name, "") + wine_execut = lgd_config.get_option(self.app_name, "wine_executable", "") + wine_prefix = lgd_config.get_wine_prefix(self.app_name, "") if old_wine_execut is None: self.settings.setValue(f"{self.app_name}/wine_execut", wine_execut) if old_wine_prefix is None: self.settings.setValue(f"{self.app_name}/wine_prefix", wine_prefix) self.wine_execut_edit.setText("") self.wine_prefix_edit.setText(os.path.join(path, "pfx")) - lgd_conf.set_boolean(self.app_name, "no_wine", True) + lgd_config.set_boolean(self.app_name, "no_wine", True) else: self.settings.remove(f"{self.app_name}/wine_execut") self.settings.remove(f"{self.app_name}/wine_prefix") self.wine_execut_edit.setText(old_wine_execut) self.wine_prefix_edit.setText(old_wine_prefix) - lgd_conf.remove_option(self.app_name, "no_wine") + lgd_config.remove_option(self.app_name, "no_wine") self.setDisabled(enabled) def load_prefix(self) -> str: if self.app_name is None: raise RuntimeError - return lgd_conf.get_wine_prefix(self.app_name, "") + return lgd_config.get_wine_prefix(self.app_name, "") def save_prefix(self, path: str) -> None: if self.app_name is None: raise RuntimeError - lgd_conf.adjust_wine_prefix(self.app_name, path) + lgd_config.adjust_wine_prefix(self.app_name, path) self.environ_changed.emit("WINEPREFIX") def load_execut(self) -> str: if self.app_name is None: raise RuntimeError - return lgd_conf.get_option(self.app_name, "wine_executable", "") + return lgd_config.get_option(self.app_name, "wine_executable", "") def save_execut(self, text: str) -> None: if self.app_name is None: raise RuntimeError - lgd_conf.adjust_option(self.app_name, "wine_executable", text) + lgd_config.adjust_option(self.app_name, "wine_executable", text) diff --git a/rare/lgndr/cli.py b/rare/lgndr/cli.py index b259ea204c..7a6c2c2d5a 100644 --- a/rare/lgndr/cli.py +++ b/rare/lgndr/cli.py @@ -348,12 +348,14 @@ def chunk_url_sign_thread(): self.install_game_cleanup(game, igame, args.repair_mode, args.repair_file) logger.info(f'Finished installation process in {end_t - start_t:.02f} seconds.') + + return ret finally: ticket_thread.stop = True sign_thread.stop = True ticket_thread.join() sign_thread.join() - return ret + @unlock_installed.__func__ def install_game_cleanup(self, game: Game, igame: InstalledGame, repair_mode: bool = False, repair_file: str = '') -> None: diff --git a/rare/models/game.py b/rare/models/game.py index 06012d7266..7f5cb2855f 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -10,6 +10,7 @@ from legendary.lfs import eos from legendary.models.game import Game, InstalledGame +from legendary.utils.selective_dl import get_sdl_appname from PySide6.QtCore import QProcess, QRunnable, QThreadPool, Slot from PySide6.QtGui import QPixmap @@ -117,8 +118,8 @@ def __init__( self.__steam_grade_pending: bool = False self.game_process = GameProcess(self.game) - self.game_process.launched.connect(self.__game_launched) - self.game_process.finished.connect(self.__game_finished) + self.game_process.launched.connect(self._game_launched) + self.game_process.finished.connect(self._game_finished) if self.is_installed and not self.is_dlc: self.game_process.connect_to_server(on_startup=True) @@ -165,7 +166,7 @@ def del_worker(self, worker: QRunnable): self.state = RareGame.State.IDLE @Slot(int) - def __game_launched(self, code: int): + def _game_launched(self, code: int): self.state = RareGame.State.RUNNING self.metadata.last_played = datetime.now(timezone.utc) if code == GameProcess.Code.ON_STARTUP: @@ -174,7 +175,7 @@ def __game_launched(self, code: int): self.signals.game.launched.emit(self.app_name) @Slot(int) - def __game_finished(self, exit_code: int): + def _game_finished(self, exit_code: int): if exit_code == GameProcess.Code.ON_STARTUP: return if self.supports_cloud_saves: @@ -341,7 +342,7 @@ def set_installed(self, installed: bool) -> None: self.core.egstore_delete(self.igame) self.igame = None self.signals.game.uninstalled.emit(self.app_name) - self.__update_pixmap() + self._update_pixmap() @property def can_run_offline(self) -> bool: @@ -524,6 +525,44 @@ def eulas(self) -> List: return not_accepted_eulas + def sdl_data(self, platform: str) -> Optional[Dict[str, Dict]]: + sdl_data = {} + + sdl_name = get_sdl_appname(self.app_name) + if data := self.core.get_sdl_data(sdl_name, platform=platform): + sdl_data.update(data) + known_install_tags = set() + if sdl_data: + known_install_tags = set(tag for _, info in sdl_data.items() for tag in info["tags"]) + + if self.igame is not None and not self.has_update: + manifest_data = self.core.lgd.load_manifest(self.app_name, self.igame.version, self.igame.platform) + else: + manifest_data, _, _ = self.core.get_cdn_manifest(self.game, platform) + manifest = self.core.load_manifest(manifest_data) + manifest_install_tags = set() + for fm in manifest.file_manifest_list.elements: + for tag in fm.install_tags: + manifest_install_tags.add(tag) + + extra_install_tags = manifest_install_tags.difference(known_install_tags) + for extra_tag in extra_install_tags: + sdl_data[extra_tag] = {"name": extra_tag, "description": "", "tags": [extra_tag]} + + return sdl_data + + @property + def sdl_available(self) -> bool: + if self.igame is not None: + manifest_data = self.core.lgd.load_manifest(self.app_name, self.igame.version, self.igame.platform) + manifest = self.core.load_manifest(manifest_data) + manifest_install_tags = set() + for fm in manifest.file_manifest_list.elements: + for tag in fm.install_tags: + manifest_install_tags.add(tag) + return bool(manifest_install_tags) + return get_sdl_appname(self.app_name) is not None + def reset_steam_date(self): self.metadata.steam_date = datetime.min.replace(tzinfo=timezone.utc) self.signals.widget.refresh.emit() @@ -598,7 +637,7 @@ def set_origin_attributes(self, path: str, size: int = 0) -> None: self.signals.game.installed.emit(self.app_name) else: self.signals.game.uninstalled.emit(self.app_name) - self.__update_pixmap() + self._update_pixmap() @property def can_launch(self) -> bool: @@ -614,7 +653,7 @@ def get_pixmap(self, preset: ImageSize.Preset, color=True) -> QPixmap: return self.image_manager.get_pixmap(self.app_name, preset, color) @Slot() - def __update_pixmap(self): + def _update_pixmap(self): self.has_pixmap = self.image_manager.has_pixmaps(self.app_name) if self.has_pixmap: self.signals.widget.refresh.emit() @@ -622,10 +661,10 @@ def __update_pixmap(self): def load_pixmaps(self): """Do not call this function, call set_pixmap instead. This is only used for initial image loading""" if not self.has_pixmap: - self.image_manager.download_image(self.game, self.__update_pixmap, 0, False) + self.image_manager.download_image(self.game, self._update_pixmap, 0, False) def refresh_pixmap(self): - self.image_manager.download_image(self.game, self.__update_pixmap, 0, True) + self.image_manager.download_image(self.game, self._update_pixmap, 0, True) @property def __install_base_path(self) -> str: @@ -690,7 +729,7 @@ def uninstall(self) -> bool: UninstallOptionsModel( app_name=self.app_name, keep_folder=self.is_dlc, - keep_config=self.sdl_name is not None or self.is_dlc or platform.system() not in {"Windows"}, + keep_config=self.sdl_available or self.is_dlc or platform.system() not in {"Windows"}, )) return True @@ -763,10 +802,10 @@ def __init__( self.image_manager = image_manager self.igame: Optional[InstalledGame] = self.core.lgd.get_overlay_install_info() - self.image_manager.download_image(game, self.__update_pixmap, 0, False) + self.image_manager.download_image(game, self._update_pixmap, 0, False) @Slot() - def __update_pixmap(self): + def _update_pixmap(self): self.has_pixmap = self.image_manager.has_pixmaps(self.app_name) if self.has_pixmap: self.signals.widget.refresh.emit() diff --git a/rare/models/game_slim.py b/rare/models/game_slim.py index 98e4e27e48..270f0ed0cd 100644 --- a/rare/models/game_slim.py +++ b/rare/models/game_slim.py @@ -4,11 +4,10 @@ from datetime import datetime from enum import IntEnum from logging import getLogger -from typing import List, Optional, Tuple +from typing import Dict, List, Optional, Tuple from legendary.lfs import eos from legendary.models.game import Game, InstalledGame, SaveGameFile, SaveGameStatus -from legendary.utils.selective_dl import get_sdl_appname from PySide6.QtCore import QObject, Signal from rare.lgndr.core import LegendaryCore @@ -203,10 +202,6 @@ def is_launchable_addon(self) -> bool: except AttributeError: return False - @property - def sdl_name(self) -> Optional[str]: - return get_sdl_appname(self.app_name) - @property def version(self) -> str: """! diff --git a/rare/resources/static_css/__init__.py b/rare/resources/static_css/__init__.py index 0d68eed80b..5b6d4b6d35 100644 --- a/rare/resources/static_css/__init__.py +++ b/rare/resources/static_css/__init__.py @@ -1,69 +1,70 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.9.1 +# Created by: The Resource Compiler for Qt version 6.10.1 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore qt_resource_data = b"\ -\x00\x00\x03\x80\ +\x00\x00\x03\x82\ \x00\ -\x00\x0f\xa5x\x9c\xbdW\xdf\x8f\xd2@\x10~\xe7\xaf\xd8\ -\x83\x90\xe8E\xb0?h\x81\x1a_N\x8d\xb9\xe4N\xbd\ -\x80\xfa`|\xd8v\x87vse\xb7\xd9n\x054\xfe\ -\xefni\x81\x16\xda\x05\x8dJ\x08]vg\xbf\x99\xf9\ -f:\xb3\xfb\xfc\x1a\xcd#\x9a\xa2\x05\x8d\x01\xa9'\xce\ -$\x1f\x84\xc0@`\x09\x04-\x04_\xa2n*71\ -\xa4\x11\x80\x1c&\x9b\xee\x10\xbd~\x8f\xde\xbd\x9f\xa37\ -\xafo\xe7WWW\xe8\xfay\xa7\xf3\xf0!K\xa3\x9b\ -LJ\xce\xd0\x8f\x0eR\x9f%e\x83\x08h\x18I\x0f\ -Y\xc3\xb1\x03\xeb\x17\x9d\x9fJN\xf0P@\x9a\xde`\ -\xa1\x17\x9c%\x94\xdd\xf0\xb5^h\xcey|\x89\xd6;\ -\xca\xe0\x0d\xa1R/\xf5\x8a/}~V\xe7\xc1\xd3/\ -\x8b\x18\xcb\x97])2\xe8~-7\xf9\x5c\x10\x10\x1e\ -2\x12%}\x98\x18\x08Lh\x96z\xc8\xd9\xcf\xe3\xe0\ -1\x14\x18\x1f\xa6\x9dK;\xd92\xd3L\xa7\x02\x1a\xff\ +\xbbSZ\xa0\x85v@\xa3n\x08t\xe7\xe3\x9c{\xcf\ +\xbd\xbdw\xe6\xd9\x0d\x9aG4E\x0b\x1a\x03R\xbf8\ +\x93|\x10\x02\x03\x81%\x10\xb4\x10|\x89\xba\xa9\xdc\xc4\ +\x90F\x00r\x98l\xbaC\xf4\xea\x1e\xbd\xbf\x9f\xa3\xd7\ +\xaf\xde\xce\xaf\xae\xae\xd0\xcd\xb3N\xe7\xe1C\x96F\xb7\ +\x99\x94\x9c\xa1\x1f\x1d\xa4\xfe\x96\x94\x0d\x22\xa0a$=\ +d\x0d\xc7\x0e\xac\x9fw~\xaau\x82\x87\x02\xd2\xf4\x16\ +\x0b\xfd\xc2YB\xd9-_\xeb\x17\xcd9\x8f/a}\ +G\x19\xbc&T\xeaW\xbd\xe4K\x9f\x9f\xe5\x19\ +k\xd1G\xee9tBS\xec\xc7*o[\x08\xec@\ +K`MN\x09>2z^\x98\x5c\x16\x0d\xb2[N\ +k\x91u\xd2\xe4\xc2h\xf0sa\xce\xe3\xeb\xc5\xc9\xa5\ +\xd1P\xe4\xd2\x9cR|\x02A\x17\x1b\x9d2\xae\xef\x8c\ +\xad\xa0\x15\xd6v\xac\xc0tu\xb0:YFc\x1b\x9b\ +\xa4\xddf\xdf\xb2MS\x0b~F\x93\xd2\xba6\xfc\x91\ +I\x0c8\xc6\xbfS\xf6\xea\x14\xb1\x0c\xd7q\xdb\x151\ +\x0d\xdb\xb2O\x149\x80\xea\xf40\xdd\x91=\x9a\xb4'\ +8\xb1&\x96\xaf\x81\xd6\xab\xb1\xb3\xac\x0d\xdd\xb7,k\ +T)V\x0f\x19d\xf0\x99\x8bG\x10\xd5\x9aU\xa2\xae\ +(\x91\x91\x87\xcc\xa32Y\xd6\xae\x94\xc7\x944\xd6O\ +;Yk8\xbe\xac\xb6\xcfr\x93\xc0\x8bn\x11\xe9b\ +\xf6\xa8>\xffA~^@\x98K\xa9\xa7\xbb<\xf8\x17\ +\xd0\xbd\x8cyFf\x1b\x16\x9cq\x91\xb8S\xb7=+\ +\xec\xb1\xed\xd8\xd5\xb8\xed:\xf4\xe5A\xdb\xf7\xb6~C\ +\xb7\xf1yL\xaa\xcd\x89~W\xf15\xddDV\xfbZ\ +\xc0\xd9gJB\x903\x89eV\xa3n\x01+\xcd_\ +ET\x82\xb6\xa1\x1aOQ\xf1q\xb7\xbdTcv9\ +,y2\x88a!\xf7\xf3\xa6y\xba@\xe4\xf6\xd4W\ +(o\x0a\x1f*\xee\xdcQF\x8b\xa7z\xc7V\x96=\ +\xb1\xec\x89j\xf2\xe5\xd7\xf5\xdf\xf5\xc1\xe7\xea\x85^\xd6\ +\xdd\x986.\xa9;2\xed7FeNe\x0c\xff$\ +(F\xfd|s`T\x07;I\x93\x86\xc3\xce\x1f\x80\ +W\xcf\x0b{\x86\x86\x1a\xbd\xcd\xec\xa2\xf6 ?V\xe0\ +\x8dR\x9bF\xffw\x82\xa5\xe7\xd7\x94\xf3P\xe0ME\ +\x9bw4\x95\x97G\xa3a\xdb?|\xb5\xc6Gi\xb9\ +=\xfe\x9e\x15\xd2\xde\x95\x91\x04\x13BY\xb8MW\xb5\ +uh\xc3\xb2>!\x0a\x13\xcb\x99&I\xfeF\xba\xfc\ +w/*yqp\xa5\x96\x97\x12\xd6r\xa0\x8e\xf1!\ +\xf3P\x8e\xdb\xe8\xbc\xf6V\x90_\x92\xb0\xafn[\xbd\ +;LY\xf1\xe8y\x12\xfb\xcd\xdd\xbezq9\x91K\ +\x0a\xcc\xd2\x04\x0b`\xf2\xd8\xfe\x19\xc4\x10\xc8O\x14V\ +\x9a\xb7\x8bq\xd6\x1a\x0a\x0dv\xf1\xb3\xbb\xc2\xd5\x91\xf5\ +8\x8d~\x95\xe1(G\xf2k_\x04\xc1\xa3\xba\xf6\xf5\ +\xde\xe0%\xccq\xb8\xfb\x7fw\x0d\xc4\x22\xa4\xec\xb4\xeb\ +\xfd\xee\x09\xa64\xf0\xf2L\xda\x1fv~\x01\xd3a\xec\ +\xde\ " qt_resource_name = b"\ @@ -83,7 +84,7 @@ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x1a\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x9a\xa3;L\x07\ +\x00\x00\x01\x9c]]\x00Y\ " def qInitResources(): diff --git a/rare/resources/static_css/stylesheet.py b/rare/resources/static_css/stylesheet.py index c229e9e5cc..82f56713ff 100644 --- a/rare/resources/static_css/stylesheet.py +++ b/rare/resources/static_css/stylesheet.py @@ -56,19 +56,19 @@ def css_name(widget: Union[wrappertype, QObject, Type], subwidget: str = ""): # [Un]InstallButton style.QPushButton["#InstallButton"].setValues( - borderColor=QColor(0, 180, 0).name(), backgroundColor=QColor(0, 120, 0).name() + borderColor=QColor(0, 180, 0).name(), backgroundColor=QColor(0, 100, 0).name() ) style.QPushButton["#InstallButton"].hover.setValues( - borderColor=QColor(0, 135, 0).name(), backgroundColor=QColor(0, 90, 0).name() + borderColor=QColor(0, 135, 0).name(), backgroundColor=QColor(0, 70, 0).name() ) style.QPushButton["#InstallButton"].disabled.setValues( borderColor=QColor(0, 60, 0).name(), backgroundColor=QColor(0, 40, 0).name() ) style.QPushButton["#UninstallButton"].setValues( - borderColor=QColor(180, 0, 0).name(), backgroundColor=QColor(120, 0, 0).name() + borderColor=QColor(180, 0, 0).name(), backgroundColor=QColor(100, 0, 0).name() ) style.QPushButton["#UninstallButton"].hover.setValues( - borderColor=QColor(135, 0, 0).name(), backgroundColor=QColor(90, 0, 0).name() + borderColor=QColor(135, 0, 0).name(), backgroundColor=QColor(70, 0, 0).name() ) style.QPushButton["#UninstallButton"].disabled.setValues( borderColor=QColor(60, 0, 0).name(), backgroundColor=QColor(40, 0, 0).name() diff --git a/rare/resources/static_css/stylesheet.qss b/rare/resources/static_css/stylesheet.qss index a304b43600..f9f70906e5 100644 --- a/rare/resources/static_css/stylesheet.qss +++ b/rare/resources/static_css/stylesheet.qss @@ -30,11 +30,11 @@ QLabel#InfoLabel { } QPushButton#InstallButton { border-color: #00b400; - background-color: #007800; + background-color: #006400; } QPushButton#InstallButton:hover { border-color: #008700; - background-color: #005a00; + background-color: #004600; } QPushButton#InstallButton:disabled { border-color: #003c00; @@ -42,11 +42,11 @@ QPushButton#InstallButton:disabled { } QPushButton#UninstallButton { border-color: #b40000; - background-color: #780000; + background-color: #640000; } QPushButton#UninstallButton:hover { border-color: #870000; - background-color: #5a0000; + background-color: #460000; } QPushButton#UninstallButton:disabled { border-color: #3c0000; diff --git a/rare/utils/compat/steam.py b/rare/utils/compat/steam.py index 9258c7ecd9..9e0d694353 100644 --- a/rare/utils/compat/steam.py +++ b/rare/utils/compat/steam.py @@ -256,7 +256,7 @@ def find_steam_tools(steam_path: str, library: str) -> List[ProtonTool]: def find_compatibility_tools(steam_path: str) -> List[CompatibilityTool]: compatibilitytools_paths = { - data_dir().joinpath("compatibilitytools").as_posix(), + data_dir().joinpath("tools").as_posix(), os.path.expanduser("~/.local/share/umu/compatibilitytools"), os.path.expanduser(os.path.join(steam_path, "compatibilitytools.d")), os.path.expanduser("~/.steam/compatibilitytools.d"), diff --git a/rare/utils/compat/utils.py b/rare/utils/compat/utils.py index 414f155e30..1c7a692e2d 100644 --- a/rare/utils/compat/utils.py +++ b/rare/utils/compat/utils.py @@ -2,6 +2,7 @@ import platform as pf import subprocess from configparser import ConfigParser +from getpass import getuser from logging import getLogger from typing import Dict, List, Mapping, Tuple @@ -174,3 +175,19 @@ def get_host_environment(app_environment: Dict, silent: bool = False) -> Dict: # lk: pressure-vessel complains about this but it doesn't fail due to it # _environ["DISPLAY"] = "" return _environ + + +def create_compat_users(pfx: str): + pfx_users = os.path.join(pfx, "drive_c", "users") + os.makedirs(pfx_users, exist_ok=True) + steam_user = os.path.join(pfx_users, "steamuser") + unix_user = os.path.join(pfx_users, getuser()) + if not os.path.exists(steam_user) and not os.path.exists(unix_user): + os.makedirs(steam_user, exist_ok=True) + pwd = os.getcwd() + os.chdir(pfx_users) + if os.path.exists(steam_user) and not os.path.exists(unix_user): + os.symlink("steamuser", getuser()) + if not os.path.exists(steam_user) and os.path.exists(unix_user): + os.symlink(getuser(), "steamuser") + os.chdir(pwd) diff --git a/rare/widgets/indicator_edit.py b/rare/widgets/indicator_edit.py index 69b3b07f8f..e01c878668 100644 --- a/rare/widgets/indicator_edit.py +++ b/rare/widgets/indicator_edit.py @@ -371,6 +371,7 @@ def __init__( ) self.setObjectName(type(self).__name__) self.line_edit.setMinimumSize(QSize(250, 0)) + self.line_edit.setReadOnly(True) self.path_select = QPushButton(self) self.path_select.setObjectName(f"{type(self).__name__}Button") layout = self.layout()